diff --git a/docs-developer/CHANGELOG-formats.md b/docs-developer/CHANGELOG-formats.md index 21c7185a5a..551f33a9a9 100644 --- a/docs-developer/CHANGELOG-formats.md +++ b/docs-developer/CHANGELOG-formats.md @@ -6,6 +6,12 @@ Note that this is not an exhaustive list. Processed profile format upgraders can ## Processed profile format +### Version 59 + +A new optional `usedInnerWindowIDs` field was added to the `Thread` type. This field contains an array of inner window IDs. It is used for the tab selector dropdown in the profiler UI, together with the information from `profile.pages`. When a tab is selected in this dropdown, threads that don't have an inner window ID for the selected tab in their `usedInnerWindowIDs` field are hidden. The array is treated as a set - the order of items in it has no meaning. + +Profiles which don't use `profile.pages` also don't need to use the `thread.usedInnerWindowIDs` field. + ### Version 58 A new `SourceTable` has been added to `profile.shared.sources` to centralize all source file information. The `FuncTable.fileName` field has been replaced with `FuncTable.source`, which references indices in the shared sources table. This change allows storing a UUID per JS source, which will be used for fetching sources. diff --git a/jest.config.js b/jest.config.js index dbb1aa6adc..67cbc9e4ca 100644 --- a/jest.config.js +++ b/jest.config.js @@ -18,10 +18,14 @@ module.exports = { '!src/types/libdef/**', ], + transform: { + '\\.([jt]sx?|mjs)$': 'babel-jest', + }, + // Transform ESM modules to CommonJS for Jest // These packages ship as pure ESM and need to be transformed by Babel transformIgnorePatterns: [ - '/node_modules/(?!(query-string|decode-uri-component|iongraph-web|split-on-first|filter-obj|fetch-mock)/)', + '/node_modules/(?!(query-string|decode-uri-component|iongraph-web|split-on-first|filter-obj|fetch-mock|devtools-reps)/)', ], // Mock static assets (images, CSS, etc.) diff --git a/locales/be/app.ftl b/locales/be/app.ftl index 2d70f8efab..95bda10941 100644 --- a/locales/be/app.ftl +++ b/locales/be/app.ftl @@ -79,6 +79,9 @@ CallNodeContextMenu--transform-focus-function = Фокус на функцыі .title = { CallNodeContextMenu--transform-focus-function-title } CallNodeContextMenu--transform-focus-function-inverted = Фокус на функцыі (інвертавана) .title = { CallNodeContextMenu--transform-focus-function-title } + +## + CallNodeContextMenu--transform-focus-subtree = Фокус толькі на паддрэве .title = Фокус на паддрэве прывядзе да выдалення любога ўзору, які не ўключае гэтую канкрэтную частку дрэва выклікаў. Гэта выдаляе галіну дрэва выклікаў, але робіць гэта толькі для аднаго вузла выкліку. Усе іншыя выклікі функцый ігнаруюцца. # This is used as the context menu item to apply the "Focus on category" transform. @@ -569,7 +572,6 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = Неабмежавана MenuButtons--metaInfo--application = Праграма MenuButtons--metaInfo--name-and-version = Назва і версія: -MenuButtons--metaInfo--application-uptime = Час працы: MenuButtons--metaInfo--update-channel = Канал абнаўлення: MenuButtons--metaInfo--build-id = ID зборкі: MenuButtons--metaInfo--build-type = Тып зборкі: diff --git a/locales/de/app.ftl b/locales/de/app.ftl index 9ebd91f04e..62e72d124f 100644 --- a/locales/de/app.ftl +++ b/locales/de/app.ftl @@ -76,6 +76,21 @@ CallNodeContextMenu--transform-focus-function = Auf Funktion fokussieren .title = { CallNodeContextMenu--transform-focus-function-title } CallNodeContextMenu--transform-focus-function-inverted = Auf Funktion fokussieren (invertiert) .title = { CallNodeContextMenu--transform-focus-function-title } + +## The translation for "self" in these strings should match the translation used +## in CallTree--samples-self and CallTree--bytes-self. Alternatively it can be +## translated as "self values" or "self time" (though "self time" is less desirable +## because this menu item is also shown in "bytes" mode). + +CallNodeContextMenu--transform-focus-self-title = + Die Fokussierung auf sich selbst ähnelt der Fokussierung auf eine Funktion, behält jedoch nur Samples bei, + die zur Zeit der Funktion selbst beitragen. Samples in Angerufenen + werden verworfen und der Aufrufbaum wird auf die Funktion im Fokus zurückgesetzt. +CallNodeContextMenu--transform-focus-self = Nur auf sich selbst konzentrieren + .title = { CallNodeContextMenu--transform-focus-self-title } + +## + CallNodeContextMenu--transform-focus-subtree = Nur auf Unterbaum konzentrieren .title = Der Fokus auf einen Unterbaum entfernt jede Teilmenge, die diesen @@ -546,7 +561,8 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = Unbegrenzt MenuButtons--metaInfo--application = Anwendung MenuButtons--metaInfo--name-and-version = Name und Version: -MenuButtons--metaInfo--application-uptime = Uptime: +# The time between application startup and when the profiler was started +MenuButtons--metaInfo--application-uptime2 = Uptime: MenuButtons--metaInfo--update-channel = Update-Kanal: MenuButtons--metaInfo--build-id = Build-ID: MenuButtons--metaInfo--build-type = Build-Typ: @@ -861,6 +877,11 @@ TrackPower--tooltip-power-watt = { $value } W # $value (String) - the power value at this location TrackPower--tooltip-power-milliwatt = { $value } mW .label = Leistung +# This is used in the tooltip when the instant power value uses the microwatt unit. +# Variables: +# $value (String) - the power value at this location +TrackPower--tooltip-power-microwatt = { $value } μW + .label = Leistung # This is used in the tooltip when the power value uses the kilowatt unit. # Variables: # $value (String) - the power value at this location @@ -1010,6 +1031,12 @@ TransformNavigator--focus-subtree = Knoten fokussieren: { $item } # Variables: # $item (String) - Name of the function that transform applied to. TransformNavigator--focus-function = Fokussieren: { $item } +# "Focus self" transform. +# See: https://profiler.firefox.com/docs/#/./guide-filtering-call-trees?id=focus-on-function-self +# Also see the translation note above CallNodeContextMenu--transform-focus-self. +# Variables: +# $item (String) - Name of the function that transform applied to. +TransformNavigator--focus-self = Fokus auf sich selbst: { $item } # "Focus category" transform. The word "Focus" has the meaning of an adjective here. # See: https://profiler.firefox.com/docs/#/./guide-filtering-call-trees?id=focus-category # Variables: diff --git a/locales/el/app.ftl b/locales/el/app.ftl index 0b2651b9eb..b68c19404c 100644 --- a/locales/el/app.ftl +++ b/locales/el/app.ftl @@ -79,6 +79,9 @@ CallNodeContextMenu--transform-focus-function = Εστίαση στη συνάρ .title = { CallNodeContextMenu--transform-focus-function-title } CallNodeContextMenu--transform-focus-function-inverted = Εστίαση στη συνάρτηση (ανεστραμμένη) .title = { CallNodeContextMenu--transform-focus-function-title } + +## + CallNodeContextMenu--transform-focus-subtree = Εστίαση στο υπόδεντρο μόνο .title = Η εστίαση σε υπόδεντρο θα αφαιρέσει κάθε δείγμα που δεν περιλαμβάνει αυτό το @@ -333,6 +336,7 @@ Home--additional-content-title = Φόρτωση υπαρχόντων προφί Home--additional-content-content = Μπορείτε να σύρετε και να εναποθέσετε ένα αρχείο προφίλ εδώ για φόρτωση, ή: Home--compare-recordings-info = Μπορείτε επίσης να συγκρίνετε καταγραφές. Άνοιγμα περιβάλλοντος σύγκρισης. Home--your-recent-uploaded-recordings-title = Πρόσφατα μεταφορτωμένες καταγραφές +Home--dark-mode-title = Σκουρόχρωμη λειτουργία # We replace the elements such as and with links to the # documentation to use these tools. Home--load-files-from-other-tools2 = @@ -424,6 +428,13 @@ MarkerContextMenu--select-the-sender-thread = Επιλέξτε το νήμα α # $filter (String) - Search string that will be used to filter the markers. MarkerFiltersContextMenu--drop-samples-outside-of-markers-matching = Απόρριψη δειγμάτων εκτός των δεικτών που αντιστοιχούν στο «{ $filter }» +## MarkerCopyTableContextMenu +## This is the menu when the copy icon is clicked in Marker Chart and Marker +## Table panels. + +MarkerCopyTableContextMenu--copy-table-as-plain = Αντιγραφή πίνακα δεικτών ως απλού κειμένου +MarkerCopyTableContextMenu--copy-table-as-markdown = Αντιγραφή πίνακα δεικτών ως Markdown + ## MarkerSettings ## This is used in all panels related to markers. @@ -434,6 +445,12 @@ MarkerSettings--marker-filters = .title = Φίλτρα δείκτη MarkerSettings--copy-table = .title = Αντιγραφή πίνακα ως κειμένου +# This string is used when the user tries to copy a marker table with +# more than 10000 rows. +# Variable: +# $rows (Number) - Number of rows the marker table has +# $maxRows (Number) - Number of maximum rows that can be copied +MarkerSettings--copy-table-exceeed-max-rows = Ο αριθμός σειρών υπερβαίνει το όριο: { $rows } > { $maxRows }. Θα αντιγραφούν μόνο οι πρώτες { $maxRows } σειρές. ## MarkerSidebar ## This is the sidebar component that is used in Marker Table panel. @@ -551,7 +568,6 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = Απεριόριστη MenuButtons--metaInfo--application = Εφαρμογή MenuButtons--metaInfo--name-and-version = Όνομα και έκδοση: -MenuButtons--metaInfo--application-uptime = Χρόνος λειτουργίας: MenuButtons--metaInfo--update-channel = Κανάλι ενημερώσεων: MenuButtons--metaInfo--build-id = ID δομής: MenuButtons--metaInfo--build-type = Τύπος δομής: @@ -866,6 +882,11 @@ 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 instant power value uses the microwatt unit. +# Variables: +# $value (String) - the power value at this location +TrackPower--tooltip-power-microwatt = { $value } μW + .label = Ισχύς # This is used in the tooltip when the power value uses the kilowatt unit. # Variables: # $value (String) - the power value at this location @@ -1160,6 +1181,17 @@ AssemblyView--show-button = # Assembly refers to the low-level programming language. AssemblyView--hide-button = .title = Απόκρυψη προβολής assembly +# The "◀" button above the assembly view. +AssemblyView--prev-button = + .title = Προηγούμενο +# The "▶" button above the assembly view. +AssemblyView--next-button = + .title = Επόμενο +# The label showing the current position and total count above the assembly view. +# Variables: +# $current (Number) - The current position (1-indexed). +# $total (Number) - The total count. +AssemblyView--position-label = { $current } από { $total } ## UploadedRecordingsHome ## This is the page that displays all the profiles that user has uploaded. diff --git a/locales/en-CA/app.ftl b/locales/en-CA/app.ftl index 1452fdf931..c6480b0216 100644 --- a/locales/en-CA/app.ftl +++ b/locales/en-CA/app.ftl @@ -79,6 +79,9 @@ CallNodeContextMenu--transform-focus-function = Focus on function .title = { CallNodeContextMenu--transform-focus-function-title } CallNodeContextMenu--transform-focus-function-inverted = Focus on function (inverted) .title = { CallNodeContextMenu--transform-focus-function-title } + +## + CallNodeContextMenu--transform-focus-subtree = Focus on subtree only .title = Focusing on a subtree will remove any sample that does not include that @@ -570,7 +573,6 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = Unlimited MenuButtons--metaInfo--application = Application MenuButtons--metaInfo--name-and-version = Name and version: -MenuButtons--metaInfo--application-uptime = Uptime: MenuButtons--metaInfo--update-channel = Update channel: MenuButtons--metaInfo--build-id = Build ID: MenuButtons--metaInfo--build-type = Build type: diff --git a/locales/en-GB/app.ftl b/locales/en-GB/app.ftl index e4c74fd58c..f8e82fb3dd 100644 --- a/locales/en-GB/app.ftl +++ b/locales/en-GB/app.ftl @@ -79,6 +79,21 @@ CallNodeContextMenu--transform-focus-function = Focus on function .title = { CallNodeContextMenu--transform-focus-function-title } CallNodeContextMenu--transform-focus-function-inverted = Focus on function (inverted) .title = { CallNodeContextMenu--transform-focus-function-title } + +## The translation for "self" in these strings should match the translation used +## in CallTree--samples-self and CallTree--bytes-self. Alternatively it can be +## translated as "self values" or "self time" (though "self time" is less desirable +## because this menu item is also shown in "bytes" mode). + +CallNodeContextMenu--transform-focus-self-title = + Focusing on self is similar to focusing on a function, but only keeps samples + that contribute to the function’s self time. Samples in callees + are dropped and the call tree is re-rooted to the focused function. +CallNodeContextMenu--transform-focus-self = Focus on self only + .title = { CallNodeContextMenu--transform-focus-self-title } + +## + CallNodeContextMenu--transform-focus-subtree = Focus on subtree only .title = Focusing on a subtree will remove any sample that does not include that @@ -570,7 +585,6 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = Unlimited MenuButtons--metaInfo--application = Application MenuButtons--metaInfo--name-and-version = Name and version: -MenuButtons--metaInfo--application-uptime = Uptime: MenuButtons--metaInfo--update-channel = Update Channel: MenuButtons--metaInfo--build-id = Build ID: MenuButtons--metaInfo--build-type = Build Type: @@ -885,6 +899,11 @@ TrackPower--tooltip-power-watt = { $value } W # $value (String) - the power value at this location TrackPower--tooltip-power-milliwatt = { $value } mW .label = Power +# This is used in the tooltip when the instant power value uses the microwatt unit. +# Variables: +# $value (String) - the power value at this location +TrackPower--tooltip-power-microwatt = { $value } μW + .label = Power # This is used in the tooltip when the power value uses the kilowatt unit. # Variables: # $value (String) - the power value at this location @@ -1034,6 +1053,12 @@ TransformNavigator--focus-subtree = Focus Node: { $item } # Variables: # $item (String) - Name of the function that transform applied to. TransformNavigator--focus-function = Focus: { $item } +# "Focus self" transform. +# See: https://profiler.firefox.com/docs/#/./guide-filtering-call-trees?id=focus-on-function-self +# Also see the translation note above CallNodeContextMenu--transform-focus-self. +# Variables: +# $item (String) - Name of the function that transform applied to. +TransformNavigator--focus-self = Focus Self: { $item } # "Focus category" transform. The word "Focus" has the meaning of an adjective here. # See: https://profiler.firefox.com/docs/#/./guide-filtering-call-trees?id=focus-category # Variables: diff --git a/locales/en-US/app.ftl b/locales/en-US/app.ftl index 72757131d3..9892116a6c 100644 --- a/locales/en-US/app.ftl +++ b/locales/en-US/app.ftl @@ -24,6 +24,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
+ + + diff --git a/res/img/svg/theme-light.svg b/res/img/svg/theme-light.svg new file mode 100644 index 0000000000..94f363d2ff --- /dev/null +++ b/res/img/svg/theme-light.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/res/img/svg/theme-system.svg b/res/img/svg/theme-system.svg new file mode 100644 index 0000000000..634fe53df1 --- /dev/null +++ b/res/img/svg/theme-system.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/app-logic/constants.ts b/src/app-logic/constants.ts index be047a25b2..4679913070 100644 --- a/src/app-logic/constants.ts +++ b/src/app-logic/constants.ts @@ -12,7 +12,7 @@ export const GECKO_PROFILE_VERSION = 32; // The current version of the "processed" profile format. // Please don't forget to update the processed profile format changelog in // `docs-developer/CHANGELOG-formats.md`. -export const PROCESSED_PROFILE_VERSION = 58; +export const PROCESSED_PROFILE_VERSION = 59; // The following are the margin sizes for the left and right of the timeline. Independent // components need to share these values. diff --git a/src/components/app/AppHeader.css b/src/components/app/AppHeader.css index 71867d69b5..85457a9f8d 100644 --- a/src/components/app/AppHeader.css +++ b/src/components/app/AppHeader.css @@ -6,6 +6,7 @@ --internal-border-color: #ddd; display: flex; + align-items: center; justify-content: space-between; padding-bottom: 0.5em; border-bottom: 1px solid var(--internal-border-color); @@ -41,15 +42,19 @@ font-weight: normal; } +.appHeaderRightControls { + display: flex; + align-items: center; + gap: 0.5em; +} + .appHeaderGithubIcon { + display: inline-flex; + align-items: center; margin: 0 0.2em; transition: opacity 250ms var(--animation-curve); } -.appHeaderGithubIcon svg { - vertical-align: middle; -} - .appHeaderGithubIcon:hover { opacity: 0.5; } diff --git a/src/components/app/AppHeader.tsx b/src/components/app/AppHeader.tsx index 797c65471e..6dc0eb9649 100644 --- a/src/components/app/AppHeader.tsx +++ b/src/components/app/AppHeader.tsx @@ -9,6 +9,7 @@ import * as React from 'react'; import { InnerNavigationLink } from 'firefox-profiler/components/shared/InnerNavigationLink'; +import { ThemeToggle } from './ThemeToggle'; import './AppHeader.css'; import { Localized } from '@fluent/react'; @@ -42,29 +43,32 @@ export class AppHeader extends React.PureComponent<{}> { - - - + + + - - - - + + + + + + ); diff --git a/src/components/app/BottomBox.css b/src/components/app/BottomBox.css index d337882d52..dc80d0345b 100644 --- a/src/components/app/BottomBox.css +++ b/src/components/app/BottomBox.css @@ -33,7 +33,7 @@ /* Provide 3px extra grabbable surface on each side of the splitter */ .bottom-box .layout-splitter::before { position: absolute; - z-index: 1; + z-index: var(--z-bottom-box); display: block; content: ''; inset: 0 -3px; diff --git a/src/components/app/Home.css b/src/components/app/Home.css index 70b55808b2..fe2fb6c878 100644 --- a/src/components/app/Home.css +++ b/src/components/app/Home.css @@ -28,7 +28,7 @@ --internal-special-border-color: var(--grey-40); --internal-screenshot-border-color: var(--grey-40); --internal-screenshot-shadow-color: var(--grey-90); - --internal-button-disabled-foreground-color: var(--grey-10-a40); + --internal-button-disabled-foreground-color: var(--grey-10-a60); --internal-button-enabled-foreground-color: var(--blue-40); --internal-docs-icon: url(../../../res/img/svg/help-blue-light.svg); } diff --git a/src/components/app/KeyboardShortcut.css b/src/components/app/KeyboardShortcut.css index 3898f85c5a..8b9ba8fab2 100644 --- a/src/components/app/KeyboardShortcut.css +++ b/src/components/app/KeyboardShortcut.css @@ -8,6 +8,7 @@ --internal-shadow-color: rgb(0 0 0 / 0.4); position: absolute; + z-index: var(--z-keyboard-shortcuts); top: 0; left: 0; display: none; diff --git a/src/components/app/MenuButtons/MetaInfo.tsx b/src/components/app/MenuButtons/MetaInfo.tsx index 8defaf2ef9..799980acf4 100644 --- a/src/components/app/MenuButtons/MetaInfo.tsx +++ b/src/components/app/MenuButtons/MetaInfo.tsx @@ -353,7 +353,7 @@ class MetaInfoPanelImpl extends React.PureComponent { {meta.profilingStartTime ? (
- + Uptime: diff --git a/src/components/app/ProfileViewer.css b/src/components/app/ProfileViewer.css index 36b349703b..1fdf52b78f 100644 --- a/src/components/app/ProfileViewer.css +++ b/src/components/app/ProfileViewer.css @@ -110,11 +110,11 @@ } .profileViewerSplitter { - /* Create a stacking context, so that the KeyboardShortcut can overlay the profile - viewer. In addition, the built-in class uses position: absolute, which is not - appropriate here. */ + /* Position relative for layout. Don't set z-index here to avoid creating a + stacking context that would trap the context menu below the screenshot hover. + This allows both the screenshot hover and context menu to be compared + at the .profileViewer level, ensuring the context menu appears on top. */ position: relative; - z-index: 0; } .profileViewerSplitter > .layout-pane:not(.layout-pane-primary) { diff --git a/src/components/app/ProfileViewer.tsx b/src/components/app/ProfileViewer.tsx index dcfeafcb9c..e00a87117d 100644 --- a/src/components/app/ProfileViewer.tsx +++ b/src/components/app/ProfileViewer.tsx @@ -103,7 +103,6 @@ class ProfileViewerImpl extends PureComponent { } as React.CSSProperties) } > -
{hasZipFile ? ( + + ); + } + + override render() { + return ( +
+ {this._renderIconButton( + 'light', + 'ThemeToggle--light', + , + this._handleLightClick + )} + {this._renderIconButton( + 'system', + 'ThemeToggle--system', + , + this._handleSystemClick + )} + {this._renderIconButton( + 'dark', + 'ThemeToggle--dark', + , + this._handleDarkClick + )} +
+ ); + } +} + +export { ThemeToggle }; diff --git a/src/components/shared/ButtonWithPanel/ArrowPanel.css b/src/components/shared/ButtonWithPanel/ArrowPanel.css index afae8912e0..cf1c9fcca1 100644 --- a/src/components/shared/ButtonWithPanel/ArrowPanel.css +++ b/src/components/shared/ButtonWithPanel/ArrowPanel.css @@ -4,7 +4,7 @@ .arrowPanelAnchor { position: absolute; - z-index: 5; + z-index: var(--z-arrow-panel); top: 75%; left: 50%; } diff --git a/src/components/shared/ContextMenu.css b/src/components/shared/ContextMenu.css index 1a1a95d00e..25931c5f39 100644 --- a/src/components/shared/ContextMenu.css +++ b/src/components/shared/ContextMenu.css @@ -13,7 +13,7 @@ --internal-selected-disabled-background-color: var(--blue-50-a30); --internal-highlight-foreground-color: var(--grey-70); - z-index: 4; /* needs to be on a higher level than .overflowEdgeIndicatorEdge */ + z-index: var(--z-context-menu); display: none; min-width: 160px; max-width: 600px; diff --git a/src/components/shared/MarkerSettings.css b/src/components/shared/MarkerSettings.css index 2d189f96ea..73173133f4 100644 --- a/src/components/shared/MarkerSettings.css +++ b/src/components/shared/MarkerSettings.css @@ -66,7 +66,7 @@ .copyTableButtonWarningWrapper { /* Position */ position: fixed; - z-index: 4; + z-index: var(--z-copy-table-warning); top: 0; right: 0; left: 0; diff --git a/src/components/shared/PanelSearch.css b/src/components/shared/PanelSearch.css index 9021aedffc..257687f0a3 100644 --- a/src/components/shared/PanelSearch.css +++ b/src/components/shared/PanelSearch.css @@ -1,7 +1,7 @@ .panelSearchField { /* so that the introduction is on top of other panels, _but_ still keep it * below the input field (see below) */ - z-index: 1; + z-index: var(--z-panel-search); padding: 0 4px; } @@ -24,7 +24,10 @@ /* This is grey-90 with 10% opacity, according to the photon design document */ --internal-shadow-color: rgb(12 12 13 / 0.1); - z-index: -1; /* it needs to be below the input field */ + z-index: var( + --z-panel-search-introduction + ); /* it needs to be below the input field */ + padding: 0 4px; background-color: var(--base-background-color); box-shadow: 0 1px 4px var(--internal-shadow-color); diff --git a/src/components/shared/TreeView.css b/src/components/shared/TreeView.css index da1e77572d..8bcb1f74a0 100644 --- a/src/components/shared/TreeView.css +++ b/src/components/shared/TreeView.css @@ -76,7 +76,7 @@ .treeViewBody { position: relative; - z-index: 0; + z-index: var(--z-tree-view); overflow: auto; flex: 1; background-color: var(--base-background-color); @@ -96,7 +96,7 @@ .treeViewBodyInner0 { position: sticky; - z-index: 2; + z-index: var(--z-tree-view-inner); left: 0; } diff --git a/src/components/shared/Warning.css b/src/components/shared/Warning.css index 333c0b2f4f..b7ceba9aa3 100644 --- a/src/components/shared/Warning.css +++ b/src/components/shared/Warning.css @@ -5,7 +5,7 @@ .warningMessageBarWrapper { /* Position */ position: fixed; - z-index: 4; + z-index: var(--z-warning); top: 0; right: 0; left: 0; diff --git a/src/components/shared/chart/Viewport.css b/src/components/shared/chart/Viewport.css index 0c8bea78c1..4484a81e3c 100644 --- a/src/components/shared/chart/Viewport.css +++ b/src/components/shared/chart/Viewport.css @@ -21,7 +21,7 @@ --internal-shadow-color: rgb(64 115 140 / 0.2); position: absolute; - z-index: 1; + z-index: var(--z-chart-viewport-expanded); top: 0; left: 0; width: 100%; diff --git a/src/components/stack-chart/Canvas.tsx b/src/components/stack-chart/Canvas.tsx index 81fc837544..a30950153b 100644 --- a/src/components/stack-chart/Canvas.tsx +++ b/src/components/stack-chart/Canvas.tsx @@ -18,6 +18,7 @@ import { getForegroundColor, getBackgroundColor, } from '../../utils/colors'; +import { ValueSummaryReader } from 'devtools-reps'; import { TooltipCallNode } from '../tooltip/CallNode'; import { TooltipMarker } from '../tooltip/Marker'; @@ -623,6 +624,27 @@ class StackChartCanvasImpl extends React.PureComponent { timelineUnit === 'bytes' ? formatBytes(duration) : formatMilliseconds(duration); + let argumentSummaries = undefined; + if (timing.argumentValues) { + const argumentValuesIndex = timing.argumentValues[stackTimingIndex]; + if ( + argumentValuesIndex !== -1 && + thread.tracedValuesBuffer && + thread.tracedObjectShapes + ) { + const argSummaries = ValueSummaryReader.getArgumentSummaries( + thread.tracedValuesBuffer, + thread.tracedObjectShapes, + argumentValuesIndex + ); + // The API maybe needs work - getArgumentSummaries can return a string indicating + // that the argument summaries for a given call were missing (this can happen if they + // were overwritten in the underlying ring buffer) + if (typeof argSummaries !== 'string') { + argumentSummaries = argSummaries; + } + } + } return ( { callTreeSummaryStrategy="timing" durationText={durationText} displayStackType={displayStackType} + argumentValues={argumentSummaries} /> ); }; diff --git a/src/components/timeline/OverflowEdgeIndicator.css b/src/components/timeline/OverflowEdgeIndicator.css index a58fdf6fde..e4aa0a11b5 100644 --- a/src/components/timeline/OverflowEdgeIndicator.css +++ b/src/components/timeline/OverflowEdgeIndicator.css @@ -14,7 +14,7 @@ --internal-shadow-color: rgb(0 0 0 / 0.4); position: absolute; - z-index: 3; + z-index: var(--z-overflow-edge-indicator); opacity: 0; pointer-events: none; transition: 150ms ease-in-out; diff --git a/src/components/timeline/Selection.css b/src/components/timeline/Selection.css index 1f07df3970..47c1bbb72c 100644 --- a/src/components/timeline/Selection.css +++ b/src/components/timeline/Selection.css @@ -54,7 +54,7 @@ .timelineSelectionHoverLine { position: absolute; - z-index: 1; + z-index: var(--z-timeline-selection); top: 0; bottom: 0; width: 1px; @@ -64,7 +64,7 @@ .timelineSelectionOverlayTime { position: absolute; - z-index: 1; + z-index: var(--z-timeline-selection-overlay-time); padding: 3.25px 8px; border-radius: 0 4px 4px 0; margin-left: 1px; @@ -74,7 +74,7 @@ .timelineSelectionOverlay { position: absolute; - z-index: 2; + z-index: var(--z-timeline-selection-overlay); display: flex; overflow: hidden; flex-flow: row nowrap; @@ -110,7 +110,7 @@ .timelineSelectionGrippyRangeStart, .timelineSelectionGrippyRangeEnd { position: relative; - z-index: 3; + z-index: var(--z-timeline-selection-grippy); width: 0; padding: 3px; border: 1px solid var(--grippy-range-border-color); @@ -161,7 +161,7 @@ border-radius: 0 0 4px 4px; background-color: var(--internal-range-background-color); box-shadow: 0 2px 2px var(--base-shadow-color); - color: var(--base-foreground-color); + color: var(--internal-overlay-time-foreground-color); opacity: 1; pointer-events: none; transition: opacity 200ms; diff --git a/src/components/timeline/Track.css b/src/components/timeline/Track.css index e58e759daa..03abe79b9f 100644 --- a/src/components/timeline/Track.css +++ b/src/components/timeline/Track.css @@ -171,9 +171,7 @@ */ .timelineTrackLocalTracks::before { position: absolute; - - /* Place it above the Reorderable component, which has a z-index of 2. */ - z-index: 3; + z-index: var(--z-timeline-track-local); left: -15px; width: calc(100% + 15px); height: 100%; diff --git a/src/components/timeline/TrackContextMenu.css b/src/components/timeline/TrackContextMenu.css index 221de610d6..8fb2ba1d4d 100644 --- a/src/components/timeline/TrackContextMenu.css +++ b/src/components/timeline/TrackContextMenu.css @@ -29,7 +29,9 @@ } .react-contextmenu-item--selected .timelineTrackContextMenuPid { - color: var(--menu-foreground-color); + --internal-selected-foreground-color: #fff; + + color: var(--internal-selected-foreground-color); } .trackContextMenuSearchFilter { @@ -42,4 +44,8 @@ .timelineTrackContextMenuPid { --internal-foreground-color: var(--grey-10-a60); } + + .react-contextmenu-item--selected .timelineTrackContextMenuPid { + --internal-selected-foreground-color: var(--menu-foreground-color); + } } diff --git a/src/components/timeline/TrackScreenshots.css b/src/components/timeline/TrackScreenshots.css index 10d9495e0c..1d0bb448e7 100644 --- a/src/components/timeline/TrackScreenshots.css +++ b/src/components/timeline/TrackScreenshots.css @@ -41,7 +41,7 @@ .timelineTrackScreenshotHover { /* This positioning is defined through JavaScript */ position: absolute; - z-index: 4; /* Ensure this is higher than any other timeline element. */ + z-index: var(--z-timeline-track-screenshot); pointer-events: none; } diff --git a/src/components/timeline/TrackThread.css b/src/components/timeline/TrackThread.css index c25bd2d971..c4d9b85d99 100644 --- a/src/components/timeline/TrackThread.css +++ b/src/components/timeline/TrackThread.css @@ -4,7 +4,7 @@ .timelineTrackThread { position: relative; - z-index: 1; + z-index: var(--z-timeline-track-thread); display: flex; flex: 1; flex-flow: column nowrap; diff --git a/src/components/timeline/index.css b/src/components/timeline/index.css index e034c80932..5abd986b91 100644 --- a/src/components/timeline/index.css +++ b/src/components/timeline/index.css @@ -99,7 +99,7 @@ .timelineHeader { /* This should be on top of the overflow edge indicator that wraps the thread list, so border bottom will always be visible. */ - z-index: 1; + z-index: var(--z-timeline-header); display: flex; height: 20px; flex-direction: row; @@ -120,8 +120,7 @@ } .tracksContainer { - /* This should be below the timeline header. */ - z-index: 0; + z-index: var(--z-tracks-container); /* Moving the timeline 1 pixel below the timeline header. This way, the first * visible track's top border will be hidden as expected. */ diff --git a/src/components/tooltip/CallNode.tsx b/src/components/tooltip/CallNode.tsx index e77f765143..220fa723f2 100644 --- a/src/components/tooltip/CallNode.tsx +++ b/src/components/tooltip/CallNode.tsx @@ -31,6 +31,8 @@ import type { OneCategoryBreakdown, } from 'firefox-profiler/profile-logic/profile-data'; import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info'; +import { REPS, MODE } from 'devtools-reps'; +const { Rep } = REPS; import './CallNode.css'; import classNames from 'classnames'; @@ -129,6 +131,7 @@ type Props = { readonly timings?: TimingsForPath; readonly callTreeSummaryStrategy: CallTreeSummaryStrategy; readonly displayStackType: boolean; + readonly argumentValues?: Array; }; /** @@ -358,6 +361,7 @@ export class TooltipCallNode extends React.PureComponent { thread, durationText, categories, + argumentValues, displayData, timings, callTreeSummaryStrategy, @@ -426,6 +430,29 @@ export class TooltipCallNode extends React.PureComponent { ]; } + let argumentsElement = null; + if (argumentValues) { + argumentsElement = [ +
+ Arguments: +
, + argumentValues.length === 0 ? ( +
+ ) : ( +
+ {argumentValues.map((previewObject, index) => ( + + {Rep({ + object: previewObject, + mode: MODE.LONG, + })} + + ))} +
+ ), + ]; + } + // Finding current frame and parent frame URL(if there is). let pageAndParentPageURL; if (innerWindowIDToPageMap) { @@ -540,6 +567,7 @@ export class TooltipCallNode extends React.PureComponent { {pageAndParentPageURL} {fileName} {resource} + {argumentsElement} {this._renderCategoryTimings(timings)} diff --git a/src/components/tooltip/Tooltip.css b/src/components/tooltip/Tooltip.css index a2a3c150b2..230085ffc2 100644 --- a/src/components/tooltip/Tooltip.css +++ b/src/components/tooltip/Tooltip.css @@ -177,3 +177,16 @@ .sidebar .tooltipDetailSeparator { display: none; } + +.tooltipArgumentsLabel { + /* match tooltips label, without being aligned to the right */ + color: var(--grey-50); +} + +.tooltipArguments > span { + display: block; +} + +.tooltipArguments > span:nth-child(odd) { + background-color: rgb(0 0 0 / 0.05); +} diff --git a/src/components/tooltip/TrackPower.tsx b/src/components/tooltip/TrackPower.tsx index 062abd0aab..984d98f564 100644 --- a/src/components/tooltip/TrackPower.tsx +++ b/src/components/tooltip/TrackPower.tsx @@ -83,7 +83,7 @@ class TooltipTrackPowerImpl extends React.PureComponent { l10nIdKiloUnit: string, l10nIdUnit: string, l10nIdMilliUnit: string, - l10nIdMicroUnit?: string + l10nIdMicroUnit: string ): React.ReactElement { let value, l10nId, carbonValue; const carbon = this._computeCO2eFromPower(power); @@ -99,7 +99,7 @@ class TooltipTrackPowerImpl extends React.PureComponent { value = 0; carbonValue = 0; l10nId = l10nIdUnit; - } else if (power < 0.001 && l10nIdMicroUnit) { + } else if (power < 0.001) { value = formatNumber(power * 1000000); // Note: even though the power value is expressed in µWh, the carbon value // is still expressed in mg. @@ -190,7 +190,8 @@ class TooltipTrackPowerImpl extends React.PureComponent { power, 'TrackPower--tooltip-power-kilowatt', 'TrackPower--tooltip-power-watt', - 'TrackPower--tooltip-power-milliwatt' + 'TrackPower--tooltip-power-milliwatt', + 'TrackPower--tooltip-power-microwatt' )} {this.maybeRenderForPreviewSelection(previewSelection)} {this._formatPowerValue( diff --git a/src/index.tsx b/src/index.tsx index 51421b2551..30658b2910 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -13,6 +13,7 @@ import '../res/css/global.css'; import '../res/css/categories.css'; import '../res/css/network.css'; import 'react-splitter-layout/lib/index.css'; +import 'devtools-reps/reps.css'; // React imported for JSX in Root component import { createRoot } from 'react-dom/client'; @@ -93,7 +94,11 @@ if (svgFiltersElement) { const forcedColorsMql = window.matchMedia('(forced-colors: active)'); const darkSchemeMql = window.matchMedia('(prefers-color-scheme: dark)'); forcedColorsMql.addEventListener('change', defineSvgFiltersForColors); - darkSchemeMql.addEventListener('change', defineSvgFiltersForColors); + darkSchemeMql.addEventListener('change', () => { + defineSvgFiltersForColors(); + // Re-apply theme when system preference changes (for 'system' mode users) + initTheme(); + }); } const store = createStore(); diff --git a/src/profile-logic/merge-compare.ts b/src/profile-logic/merge-compare.ts index bfd339b3e3..b5f650baa9 100644 --- a/src/profile-logic/merge-compare.ts +++ b/src/profile-logic/merge-compare.ts @@ -67,6 +67,7 @@ import type { MarkerIndex, Milliseconds, Tid, + RawProfileSharedData, } from 'firefox-profiler/types'; /** @@ -134,40 +135,23 @@ export function mergeProfilesForDiffing( ); } - // First let's merge categories. We'll use the resulting maps when - // handling the thread data later. + // First, let's merge profile.meta.categories, profile.libs, and profile.shared. const { categories: newCategories, - translationMaps: translationMapsForCategories, - } = mergeCategories(profiles.map((profile) => profile.meta.categories)); + libs: newLibs, + shared: newShared, + translationMapsForCategories, + translationMapsForLibs, + translationMapsForStrings, + translationMapsForSources, + } = mergeSharedData(profiles); resultProfile.meta.categories = newCategories; - - const { - stringArray: newStringArray, - translationMaps: translationMapForStrings, - } = mergeStringArrays(profiles.map((profile) => profile.shared.stringArray)); - - // Then merge sources. - const { sources: newSources, translationMaps: translationMapForSources } = - mergeSources( - profiles.map((profile) => profile.shared.sources ?? null), - translationMapForStrings - ); - - // Then merge libs. - const { libs: newLibs, translationMaps: translationMapsForLibs } = mergeLibs( - profiles.map((profile) => profile.libs) - ); resultProfile.libs = newLibs; - - resultProfile.shared = { - stringArray: newStringArray, - sources: newSources, - }; + resultProfile.shared = newShared; // Then we loop over all profiles and do the necessary changes according // to the states we computed earlier. - const transformStacks: any = {}; + const transformStacks: TransformStacksPerThread = {}; const implementationFilters: ImplementationFilter[] = []; // These may be needed for filtering markers. let ipcCorrelations; @@ -203,22 +187,22 @@ export function mergeProfilesForDiffing( ...thread.funcTable, name: adjustStringIndexes( thread.funcTable.name, - translationMapForStrings[i] + translationMapsForStrings[i] ), source: adjustNullableSourceIndexes( thread.funcTable.source, - translationMapForSources[i] + translationMapsForSources[i] ), }; thread.resourceTable = { ...thread.resourceTable, name: adjustStringIndexes( thread.resourceTable.name, - translationMapForStrings[i] + translationMapsForStrings[i] ), host: adjustNullableStringIndexes( thread.resourceTable.host, - translationMapForStrings[i] + translationMapsForStrings[i] ), lib: adjustResourceTableLibs( thread.resourceTable.lib, @@ -229,7 +213,7 @@ export function mergeProfilesForDiffing( ...thread.nativeSymbols, name: adjustStringIndexes( thread.nativeSymbols.name, - translationMapForStrings[i] + translationMapsForStrings[i] ), libIndex: adjustNativeSymbolLibs( thread.nativeSymbols.libIndex, @@ -240,11 +224,11 @@ export function mergeProfilesForDiffing( ...thread.markers, name: adjustStringIndexes( thread.markers.name, - translationMapForStrings[i] + translationMapsForStrings[i] ), data: adjustMarkerDataStringIndexes( thread.markers.data, - translationMapForStrings[i], + translationMapsForStrings[i], stringIndexMarkerFieldsByDataType ), }; @@ -475,6 +459,50 @@ function mergeCategories(categoriesPerProfile: Array): { return { categories: newCategories, translationMaps }; } +export function mergeSharedData(profiles: Profile[]): { + categories: CategoryList; + libs: Lib[]; + shared: RawProfileSharedData; + translationMapsForCategories: TranslationMapForCategories[]; + translationMapsForLibs: TranslationMapForLibs[]; + translationMapsForStrings: TranslationMapForStrings[]; + translationMapsForSources: TranslationMapForSources[]; +} { + const { + categories: newCategories, + translationMaps: translationMapsForCategories, + } = mergeCategories(profiles.map((profile) => profile.meta.categories)); + + const { + stringArray: newStringArray, + translationMaps: translationMapsForStrings, + } = mergeStringArrays(profiles.map((profile) => profile.shared.stringArray)); + + const { libs: newLibs, translationMaps: translationMapsForLibs } = mergeLibs( + profiles.map((profile) => profile.libs) + ); + + const { sources: newSources, translationMaps: translationMapsForSources } = + mergeSources( + profiles.map((profile) => profile.shared.sources ?? null), + translationMapsForStrings + ); + const newShared: RawProfileSharedData = { + stringArray: newStringArray, + sources: newSources, + }; + + return { + categories: newCategories, + libs: newLibs, + shared: newShared, + translationMapsForCategories, + translationMapsForLibs, + translationMapsForStrings, + translationMapsForSources, + }; +} + function mergeStringArrays(stringArraysPerProfile: Array): { stringArray: string[]; translationMaps: TranslationMapForStrings[]; diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index a1049c0cab..755c639813 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -1015,6 +1015,10 @@ function _processSamples(geckoSamples: GeckoSampleStruct): RawSamplesTable { } } + if ('argumentValues' in geckoSamples) { + samples.argumentValues = geckoSamples.argumentValues; + } + if ('eventDelay' in geckoSamples) { samples.eventDelay = geckoSamples.eventDelay; } else if ('responsiveness' in geckoSamples) { @@ -1210,6 +1214,29 @@ function _processThread( ); const samples = _processSamples(geckoSamples); + // Compute usedInnerWindowIDs from the frameTable and the markers. + let usedInnerWindowIDs: number[] | undefined; + const usedInnerWindowIDsSet = new Set(); + for (let i = 0; i < frameTable.length; i++) { + const innerWindowID = frameTable.innerWindowID[i]; + if (innerWindowID !== null && innerWindowID !== 0) { + usedInnerWindowIDsSet.add(innerWindowID); + } + } + for (let i = 0; i < markers.length; i++) { + const data = markers.data[i]; + if (!data || !('innerWindowID' in data)) { + continue; + } + const innerWindowID = data.innerWindowID; + if (typeof innerWindowID === 'number' && innerWindowID !== 0) { + usedInnerWindowIDsSet.add(innerWindowID); + } + } + if (usedInnerWindowIDsSet.size !== 0) { + usedInnerWindowIDs = Array.from(usedInnerWindowIDsSet); + } + const newThread: RawThread = { name: thread.name, isMainThread: thread.name === 'GeckoMain', @@ -1245,6 +1272,10 @@ function _processThread( newThread.userContextId = thread.userContextId; } + if (usedInnerWindowIDs !== undefined) { + newThread.usedInnerWindowIDs = usedInnerWindowIDs; + } + if (jsAllocations) { // Only add the JS allocations if they exist. newThread.jsAllocations = jsAllocations; @@ -1255,6 +1286,14 @@ function _processThread( newThread.nativeAllocations = nativeAllocations; } + if (thread.tracedValues) { + newThread.tracedValuesBuffer = thread.tracedValues; + } + + if (thread.tracedObjectShapes) { + newThread.tracedObjectShapes = thread.tracedObjectShapes; + } + function processJsTracer() { // Optionally extract the JS Tracer information, if they exist. const { jsTracerEvents } = thread; diff --git a/src/profile-logic/processed-profile-versioning.ts b/src/profile-logic/processed-profile-versioning.ts index 2448b486e7..8e341a4fab 100644 --- a/src/profile-logic/processed-profile-versioning.ts +++ b/src/profile-logic/processed-profile-versioning.ts @@ -2693,6 +2693,39 @@ const _upgraders: { } profile.shared.sources = sourceTable; }, + [59]: (profile) => { + // Add usedInnerWindowIDs to each thread by computing it from the frameTable and the markers. + // This changes the profile format from having innerWindowIDs implicitly + // defined by the frameTable / markers to having them explicitly listed in + // thread.usedInnerWindowIDs. + for (const thread of profile.threads) { + const { frameTable, markers } = thread; + const usedInnerWindowIDsSet = new Set(); + + // Collect all unique innerWindowIDs from the frameTable + for (let i = 0; i < frameTable.length; i++) { + const innerWindowID = frameTable.innerWindowID[i]; + if (innerWindowID !== null && innerWindowID !== 0) { + usedInnerWindowIDsSet.add(innerWindowID); + } + } + for (let i = 0; i < markers.length; i++) { + const data = markers.data[i]; + if (!data || !('innerWindowID' in data)) { + continue; + } + const innerWindowID = data.innerWindowID; + if (typeof innerWindowID === 'number' && innerWindowID !== 0) { + usedInnerWindowIDsSet.add(innerWindowID); + } + } + + // Convert the set to an array and store it on the thread, if non-empty + if (usedInnerWindowIDsSet.size !== 0) { + thread.usedInnerWindowIDs = Array.from(usedInnerWindowIDsSet); + } + } + }, // If you add a new upgrader here, please document the change in // `docs-developer/CHANGELOG-formats.md`. }; diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 3be6925778..6e247fad55 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -1882,6 +1882,13 @@ export function filterThreadSamplesToRange( ); } + if (samples.argumentValues) { + newSamples.argumentValues = samples.argumentValues.slice( + beginSampleIndex, + endSampleIndex + ); + } + if (samples.threadId) { newSamples.threadId = samples.threadId.slice( beginSampleIndex, @@ -2008,6 +2015,13 @@ export function filterRawThreadSamplesToRange( ); } + if (samples.argumentValues) { + newSamples.argumentValues = samples.argumentValues.slice( + beginSampleIndex, + endSampleIndex + ); + } + if (samples.threadId) { newSamples.threadId = samples.threadId.slice( beginSampleIndex, @@ -2114,6 +2128,9 @@ export function filterCounterSamplesToRange( number: samples.number ? samples.number.slice(beginSampleIndex, endSampleIndex) : undefined, + argumentValues: samples.argumentValues + ? samples.argumentValues.slice(beginSampleIndex, endSampleIndex) + : undefined, }; return newCounter; @@ -2438,6 +2455,7 @@ export function computeSamplesTableFromRawSamplesTable( const { responsiveness, eventDelay, + argumentValues, stack, weight, weightType, @@ -2459,6 +2477,7 @@ export function computeSamplesTableFromRawSamplesTable( // These fields are copied from the raw samples table: responsiveness, eventDelay, + argumentValues, stack, weight, weightType, @@ -2479,7 +2498,8 @@ export function createThreadFromDerivedTables( samples: SamplesTable, stackTable: StackTable, stringTable: StringTable, - sources: SourceTable + sources: SourceTable, + tracedValuesBuffer: ArrayBuffer | undefined ): Thread { const { processType, @@ -2506,6 +2526,7 @@ export function createThreadFromDerivedTables( jsTracer, isPrivateBrowsing, userContextId, + tracedObjectShapes, } = rawThread; const thread: Thread = { @@ -2534,12 +2555,14 @@ export function createThreadFromDerivedTables( jsTracer, isPrivateBrowsing, userContextId, + tracedObjectShapes, // These fields are derived: samples, stackTable, stringTable, sources, + tracedValuesBuffer, }; return thread; } @@ -4098,17 +4121,19 @@ export function computeTabToThreadIndexesMap( return tabToThreadIndexesMap; } - // We need to iterate over all the samples and markers once to figure out - // which innerWindowIDs are present in each thread. This is probably not - // very cheap, but it'll allow us to not compute this information every - // time when we need it. + // Iterate over the usedInnerWindowIDs for each thread to figure out + // which threads are involved for each tab. for (let threadIdx = 0; threadIdx < threads.length; threadIdx++) { const thread = threads[threadIdx]; + const { usedInnerWindowIDs } = thread; + + if (!usedInnerWindowIDs) { + // No innerWindowIDs for this thread + continue; + } - // First go over the innerWindowIDs of the samples. - for (let i = 0; i < thread.frameTable.length; i++) { - const innerWindowID = thread.frameTable.innerWindowID[i]; - if (innerWindowID === null || innerWindowID === 0) { + for (const innerWindowID of usedInnerWindowIDs) { + if (innerWindowID === 0) { // Zero value also means null for innerWindowID. continue; } @@ -4127,38 +4152,6 @@ export function computeTabToThreadIndexesMap( } threadIndexes.add(threadIdx); } - - // Then go over the markers to find their innerWindowIDs. - for (let i = 0; i < thread.markers.length; i++) { - const markerData = thread.markers.data[i]; - - if (!markerData) { - continue; - } - - if ( - 'innerWindowID' in markerData && - markerData.innerWindowID !== null && - markerData.innerWindowID !== undefined && - // Zero value also means null for innerWindowID. - markerData.innerWindowID !== 0 - ) { - const innerWindowID = markerData.innerWindowID; - const tabID = innerWindowIDToTabMap.get(innerWindowID); - if (tabID === undefined) { - // We couldn't find the tab of this innerWindowID, this should - // never happen, it might indicate a bug in Firefox. - continue; - } - - let threadIndexes = tabToThreadIndexesMap.get(tabID); - if (!threadIndexes) { - threadIndexes = new Set(); - tabToThreadIndexesMap.set(tabID, threadIndexes); - } - threadIndexes.add(threadIdx); - } - } } return tabToThreadIndexesMap; diff --git a/src/profile-logic/sanitize.ts b/src/profile-logic/sanitize.ts index e9eefdd8f7..51f2d29728 100644 --- a/src/profile-logic/sanitize.ts +++ b/src/profile-logic/sanitize.ts @@ -411,6 +411,9 @@ function sanitizeThreadPII( delete newThread['eTLD+1']; } + delete newThread.tracedValuesBuffer; + delete newThread.tracedObjectShapes; + if (windowIdFromPrivateBrowsing.size > 0) { // In this block, we'll remove everything related to frame table entries // that have a innerWindowID with a isPrivateBrowsing flag. diff --git a/src/profile-logic/stack-timing.ts b/src/profile-logic/stack-timing.ts index 7ea7f20991..3f6f97c8f0 100644 --- a/src/profile-logic/stack-timing.ts +++ b/src/profile-logic/stack-timing.ts @@ -87,6 +87,9 @@ export type StackTiming = { sameWidthsStart: number[]; sameWidthsEnd: number[]; callNode: IndexIntoCallNodeTable[]; + // argumentValues is used by the JS Execution Tracing setting and allows + // displaying function calls' argument values. + argumentValues?: number[]; length: number; }; @@ -115,14 +118,20 @@ export function getStackTimingByDepth( } = callNodeTable; const stackTimingByDepth: StackTimingByDepth = Array.from( { length: maxDepthPlusOne }, - (): StackTiming => ({ - start: [], - end: [], - sameWidthsStart: [], - sameWidthsEnd: [], - callNode: [], - length: 0, - }) + (): StackTiming => { + const shape: StackTiming = { + start: [], + end: [], + sameWidthsStart: [], + sameWidthsEnd: [], + callNode: [], + length: 0, + }; + if ('argumentValues' in samples) { + shape.argumentValues = []; + } + return shape; + } ); const sameWidthsIndexToTimestampMap: SameWidthsIndexToTimestampMap = []; @@ -154,6 +163,7 @@ export function getStackTimingByDepth( let deepestOpenBoxDepth = -1; const openBoxStartTimeByDepth = new Float64Array(maxDepthPlusOne); const openBoxStartTickByDepth = new Float64Array(maxDepthPlusOne); + const openBoxArgsByDepth = new Int32Array(maxDepthPlusOne); let currentStackTick = 0; for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex++) { @@ -162,6 +172,14 @@ export function getStackTimingByDepth( continue; } + let sampleArgs: number = -1; + if ('argumentValues' in samples && samples.argumentValues !== undefined) { + const val = samples.argumentValues[sampleIndex]; + if (val !== null) { + sampleArgs = val; + } + } + const sampleTime = samples.time[sampleIndex]; // Phase 1: Commit open boxes which are not shared by the current call node, @@ -193,6 +211,10 @@ export function getStackTimingByDepth( stackTimingForThisDepth.sameWidthsStart[index] = startStackTick; stackTimingForThisDepth.sameWidthsEnd[index] = currentStackTick; stackTimingForThisDepth.callNode[index] = deepestOpenBoxCallNodeIndex; + if (stackTimingForThisDepth.argumentValues) { + stackTimingForThisDepth.argumentValues[index] = + openBoxArgsByDepth[deepestOpenBoxDepth]; + } deepestOpenBoxCallNodeIndex = callNodeTablePrefixColumn[deepestOpenBoxCallNodeIndex]; deepestOpenBoxDepth--; @@ -208,6 +230,12 @@ export function getStackTimingByDepth( deepestOpenBoxDepth++; openBoxStartTimeByDepth[deepestOpenBoxDepth] = sampleTime; openBoxStartTickByDepth[deepestOpenBoxDepth] = currentStackTick; + if ( + 'argumentValues' in samples && + samples.argumentValues !== undefined + ) { + openBoxArgsByDepth[deepestOpenBoxDepth] = sampleArgs; + } } } @@ -229,6 +257,10 @@ export function getStackTimingByDepth( stackTimingForThisDepth.sameWidthsStart[index] = startStackTick; stackTimingForThisDepth.sameWidthsEnd[index] = currentStackTick; stackTimingForThisDepth.callNode[index] = deepestOpenBoxCallNodeIndex; + if (stackTimingForThisDepth.argumentValues) { + stackTimingForThisDepth.argumentValues[index] = + openBoxArgsByDepth[deepestOpenBoxDepth]; + } deepestOpenBoxCallNodeIndex = callNodeTablePrefixColumn[deepestOpenBoxCallNodeIndex]; deepestOpenBoxDepth--; diff --git a/src/selectors/per-thread/thread.tsx b/src/selectors/per-thread/thread.tsx index fc8b619b89..e6fec6c67d 100644 --- a/src/selectors/per-thread/thread.tsx +++ b/src/selectors/per-thread/thread.tsx @@ -18,6 +18,7 @@ import { ensureExists, getFirstItemFromSet, } from '../../utils/types'; +import { base64StringToBytes } from '../../utils/base64'; import type { Thread, @@ -92,6 +93,12 @@ export function getBasicThreadSelectorsPerThread( ? ProfileSelectors.getProfile(state).threads[singleThreadIndex] : getMergedRawThread(state); + const getTracedValuesBuffer: Selector = + createSelector( + (state: State) => getRawThread(state).tracedValuesBuffer, + (tracedValuesBuffer: string | undefined) => + tracedValuesBuffer ? base64StringToBytes(tracedValuesBuffer) : undefined + ); const getRawSamplesTable: Selector = (state) => getRawThread(state).samples; const getSamplesTable: Selector = createSelector( @@ -147,6 +154,7 @@ export function getBasicThreadSelectorsPerThread( getStackTable, ProfileSelectors.getStringTable, ProfileSelectors.getSourceTable, + getTracedValuesBuffer, ProfileData.createThreadFromDerivedTables ); @@ -386,6 +394,7 @@ export function getBasicThreadSelectorsPerThread( getRawThread, getThread, getSamplesTable, + getTracedValuesBuffer, getSamplesWeightType, getNativeAllocations, getJsAllocations, diff --git a/src/test/components/StackChart.test.tsx b/src/test/components/StackChart.test.tsx index 19b0a69a56..e8a2769361 100644 --- a/src/test/components/StackChart.test.tsx +++ b/src/test/components/StackChart.test.tsx @@ -63,6 +63,7 @@ import { import { autoMockElementSize } from '../fixtures/mocks/element-size'; import type { CssPixels, Store } from 'firefox-profiler/types'; +import { unserializeProfileOfArbitraryFormat } from 'firefox-profiler/profile-logic/process-profile'; jest.useFakeTimers(); @@ -314,6 +315,21 @@ describe('StackChart', function () { }); }); +describe('ArgumentValues', () => { + it('shows argument values when a profile has them', async () => { + const profiler = await unserializeProfileOfArbitraryFormat( + require('../fixtures/upgrades/argument-values.json') + ); + const store = storeWithProfile(profiler); + const { getTooltip, moveMouse, findFillTextPosition } = setup(store); + + moveMouse(findFillTextPosition('bar')); + const tooltip = getTooltip(); + expect(within(ensureExists(tooltip)).getByText('bar')).toBeInTheDocument(); + expect(tooltip).toMatchSnapshot(); + }); +}); + describe('MarkerChart', function () { it('can turn on the show user timings', () => { const { getByLabelText, getState } = setupUserTimings({ diff --git a/src/test/components/TabSelectorMenu.test.tsx b/src/test/components/TabSelectorMenu.test.tsx index b8689d3b41..c1f2f618e5 100644 --- a/src/test/components/TabSelectorMenu.test.tsx +++ b/src/test/components/TabSelectorMenu.test.tsx @@ -32,21 +32,20 @@ describe('app/TabSelectorMenu', () => { threadCPUDelta: 'ns', }; - // Add some frames with innerWindowIDs now. Note that we only expand the - // innerWindowID array and not the others as we don't check them at all. + // Associate the threads with innerWindowIDs now. // // Thread 0 will be present in firstTabTabID. // Thread 1 be present in secondTabTabID. - profile.threads[0].frameTable.innerWindowID[0] = - extraPageData.parentInnerWindowIDsWithChildren; - profile.threads[0].frameTable.length++; + profile.threads[0].usedInnerWindowIDs = [ + extraPageData.parentInnerWindowIDsWithChildren, + ]; // Add a threadCPUDelta value for thread activity score. profile.threads[0].samples.threadCPUDelta = [1]; - profile.threads[1].frameTable.innerWindowID[0] = - extraPageData.secondTabInnerWindowIDs[0]; - profile.threads[1].frameTable.length++; + profile.threads[1].usedInnerWindowIDs = [ + extraPageData.secondTabInnerWindowIDs[0], + ]; // Add a threadCPUDelta value for thread activity score. This thread // should stay above the first thread. profile.threads[0].samples.threadCPUDelta = [2]; @@ -196,13 +195,11 @@ describe('app/TabSelectorMenu', () => { url: removeURLs(page.url, ``), })); - // Attach innerWindowIDs to the samples. - profile.threads[0].frameTable.innerWindowID[0] = - extraPageData.parentInnerWindowIDsWithChildren; - profile.threads[0].frameTable.length++; - profile.threads[0].frameTable.innerWindowID[1] = - extraPageData.secondTabInnerWindowIDs[0]; - profile.threads[0].frameTable.length++; + // Attach innerWindowIDs to the threads. + profile.threads[0].usedInnerWindowIDs = [ + extraPageData.parentInnerWindowIDsWithChildren, + extraPageData.secondTabInnerWindowIDs[0], + ]; const store = storeWithProfile(profile); render( diff --git a/src/test/components/TooltipCallnode.test.tsx b/src/test/components/TooltipCallnode.test.tsx index ee2853bbbc..ddf6f409b8 100644 --- a/src/test/components/TooltipCallnode.test.tsx +++ b/src/test/components/TooltipCallnode.test.tsx @@ -4,7 +4,7 @@ import { Provider } from 'react-redux'; -import { render } from 'firefox-profiler/test/fixtures/testing-library'; +import { render, screen } from 'firefox-profiler/test/fixtures/testing-library'; import { TooltipCallNode } from '../../components/tooltip/CallNode'; import { storeWithProfile } from '../fixtures/stores'; import { @@ -117,10 +117,12 @@ describe('TooltipCallNode', function () { const { frameTable } = profile.threads[threadIndex]; + const innerWindowID = + profile.pages[profile.pages.length - 1].innerWindowID; for (let i = 1; i < frameTable.length; i++) { - frameTable.innerWindowID[i] = - profile.pages[profile.pages.length - 1].innerWindowID; + frameTable.innerWindowID[i] = innerWindowID; } + profile.threads[threadIndex].usedInnerWindowIDs = [innerWindowID]; const callNodePath = [A, Bjs, Cjs]; const { dispatch, renderTooltip } = setup(profile); @@ -165,4 +167,72 @@ describe('TooltipCallNode', function () { expect(displayedUrl).toHaveTextContent(`${pageUrl} (private)`); }); }); + + describe('with argument values', function () { + function setupWithArguments(argumentValues?: Array) { + const { + profile, + funcNamesDictPerThread: [{ A, Bjs, Cjs }], + } = getProfileFromTextSamples(` + A + Bjs + Cjs + `); + const threadIndex = 0; + const callNodePath = [A, Bjs, Cjs]; + + const store = storeWithProfile(profile); + const { getState, dispatch } = store; + dispatch(changeSelectedCallNode(threadIndex, callNodePath)); + + const callTree = selectedThreadSelectors.getCallTree(getState()); + const callNodeIndex = ensureExists( + selectedThreadSelectors.getSelectedCallNodeIndex(getState()), + 'Unable to find a selected call node index.' + ); + const displayData = callTree.getDisplayData(callNodeIndex); + + return render( + + + + ); + } + + it('does not display arguments when argumentValues is undefined', () => { + setupWithArguments(undefined); + expect(screen.queryByText('Arguments:')).not.toBeInTheDocument(); + }); + + it('displays em dash when argumentValues is empty', () => { + const { getByText, container } = setupWithArguments([]); + expect(getByText('Arguments:')).toBeInTheDocument(); + expect(getByText('—')).toBeInTheDocument(); + expect(container.firstChild).toMatchSnapshot(); + }); + + it('displays argument values when provided', () => { + const argumentValues = [42, 'hello']; + const { getByText, container } = setupWithArguments(argumentValues); + expect(getByText('Arguments:')).toBeInTheDocument(); + expect(container.firstChild).toMatchSnapshot(); + }); + }); }); diff --git a/src/test/components/__snapshots__/CompareHome.test.tsx.snap b/src/test/components/__snapshots__/CompareHome.test.tsx.snap index b2f257169e..fb4484b036 100644 --- a/src/test/components/__snapshots__/CompareHome.test.tsx.snap +++ b/src/test/components/__snapshots__/CompareHome.test.tsx.snap @@ -24,27 +24,65 @@ exports[`app/CompareHome matches the snapshot 1`] = ` Web app for ⁨Firefox⁩ performance analysis - - - - - + + + + + + + + + +

- - - - - +
+ + + +
+ + + + + +

- - - - - +
+ + + +
+ + + + + +
- - - - - +
+ + + +
+ + + + + +
+
+
+
+ 36ms +
+
+ bar +
+
+
+
+
+
+ Stack Type: +
+
+ JavaScript +
+
+ Category: +
+
+ + JavaScript: JIT (baseline) +
+
+ Page URL: +
+
+ file://<Page #15> +
+
+ File: +
+
+ file://<URL>:11:13 +
+
+ Resource: +
+ file://<URL> +
+ Arguments: +
+
+ + 0 + +
+
+
+
+
+`; + exports[`CombinedChart renders combined stack chart 1`] = `
`; +exports[`TooltipCallNode with argument values displays argument values when provided 1`] = ` +
+
+
+ Fake Duration Text +
+
+ Cjs +
+
+
+
+
+
+ Stack Type: +
+
+ JavaScript +
+
+ Category: +
+
+ + JavaScript +
+
+ Arguments: +
+
+ + 42 + + + "hello" + +
+
+
+
+`; + +exports[`TooltipCallNode with argument values displays em dash when argumentValues is empty 1`] = ` +
+
+
+ Fake Duration Text +
+
+ Cjs +
+
+
+
+
+
+ Stack Type: +
+
+ JavaScript +
+
+ Category: +
+
+ + JavaScript +
+
+ Arguments: +
+
+ — +
+
+
+
+`; + exports[`TooltipCallNode with page information displays Page URL for iframe pages 1`] = `
- - - - - + + + +
+ + + + + +

- - - - - + + + +

+ + + + + +

({ + matches: false, + media: query, + onchange: null, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => { + return true; + }, + }), + }); } } diff --git a/src/test/fixtures/profiles/gecko-profile.ts b/src/test/fixtures/profiles/gecko-profile.ts index 872870eec9..c1d9b57219 100644 --- a/src/test/fixtures/profiles/gecko-profile.ts +++ b/src/test/fixtures/profiles/gecko-profile.ts @@ -934,6 +934,8 @@ function _createGeckoThread( 'RefreshDriverTick', // 21 'Navigation::Start', // 22 ], + tracedObjectShapes: [['Test']], + tracedValues: 'SSdtIGEgbGl0dGxlIHRlYWN1cA==', }; } diff --git a/src/test/fixtures/profiles/processed-profile.ts b/src/test/fixtures/profiles/processed-profile.ts index 5813a8e4e8..9d58ed0571 100644 --- a/src/test/fixtures/profiles/processed-profile.ts +++ b/src/test/fixtures/profiles/processed-profile.ts @@ -2046,8 +2046,10 @@ export function addInnerWindowIdToStacks( callNodesToDupe?: CallNodePath[] ) { const { stackTable, frameTable, samples } = thread; + const usedInnerWindowIDsSet = new Set(); for (const { innerWindowID, callNodes } of listOfOperations) { + usedInnerWindowIDsSet.add(innerWindowID); for (const callNode of callNodes) { const stackIndex = getStackIndexForCallNodePath(thread, callNode); const foundFrameIndex = stackTable.frame[stackIndex]; @@ -2120,6 +2122,9 @@ export function addInnerWindowIdToStacks( if (samples.responsiveness) { samples.responsiveness.push(samples.responsiveness[sampleIndex]); } + if (samples.argumentValues) { + samples.argumentValues.push(samples.argumentValues[sampleIndex]); + } if (samples.threadCPUDelta) { samples.threadCPUDelta.push(samples.threadCPUDelta[sampleIndex]); } @@ -2129,6 +2134,10 @@ export function addInnerWindowIdToStacks( samples.length++; } } + + if (usedInnerWindowIDsSet.size !== 0) { + thread.usedInnerWindowIDs = Array.from(usedInnerWindowIDsSet); + } } /** diff --git a/src/test/fixtures/stores.ts b/src/test/fixtures/stores.ts index 17e39b86b4..1c225d03f7 100644 --- a/src/test/fixtures/stores.ts +++ b/src/test/fixtures/stores.ts @@ -8,6 +8,7 @@ import { processGeckoProfile } from '../../profile-logic/process-profile'; import { getProfileFromTextSamples } from './profiles/processed-profile'; import type { Store, Profile } from 'firefox-profiler/types'; +import { PROCESSED_PROFILE_VERSION } from 'firefox-profiler/app-logic/constants'; export function blankStore() { return createStore(); @@ -18,6 +19,13 @@ export function storeWithProfile(profile?: Profile): Store { profile = processGeckoProfile(createGeckoProfileWithJsTimings()); profile.meta.symbolicated = true; } + + if (profile.meta.preprocessedProfileVersion !== PROCESSED_PROFILE_VERSION) { + throw new Error( + `storeWithProfile called with something that's not a fully-uprgaded processed profile!` + ); + } + const store = createStore(); store.dispatch(viewProfile(profile)); return store; diff --git a/src/test/fixtures/upgrades/argument-values.json b/src/test/fixtures/upgrades/argument-values.json new file mode 100644 index 0000000000..37183b18dc --- /dev/null +++ b/src/test/fixtures/upgrades/argument-values.json @@ -0,0 +1,2987 @@ +{ + "meta": { + "interval": 1, + "startTime": 1764804834257.5686, + "startTimeAsMachAbsoluteTimeNanoseconds": 77881103837250, + "abi": "aarch64-gcc3", + "extensions": { + "id": [], + "name": [], + "baseURL": [], + "length": 0 + }, + "misc": "rv:147.0", + "oscpu": "macOS 15.6.1", + "platform": "Macintosh", + "processType": 0, + "product": "Firefox", + "stackwalk": 0, + "debug": false, + "toolkit": "cocoa", + "version": 32, + "categories": [ + { + "name": "Idle", + "color": "transparent", + "subcategories": [ + "Other" + ] + }, + { + "name": "Other", + "color": "grey", + "subcategories": [ + "Other", + "Preference Read", + "Profiling" + ] + }, + { + "name": "Test", + "color": "darkgray", + "subcategories": [ + "Test" + ] + }, + { + "name": "Layout", + "color": "purple", + "subcategories": [ + "Other", + "Frame construction", + "Reflow", + "CSS parsing", + "Selector query", + "Style computation", + "Layout cleanup", + "Printing" + ] + }, + { + "name": "JavaScript", + "color": "yellow", + "subcategories": [ + "Other", + "Parsing", + "JIT Compile (baseline)", + "JIT Compile (ion)", + "Interpreter", + "JIT (baseline-interpreter)", + "JIT (baseline)", + "JIT (ion)", + "Builtin API", + "Wasm (ion)", + "Wasm (baseline)", + "Wasm (other)" + ] + }, + { + "name": "GC / CC", + "color": "orange", + "subcategories": [ + "Other", + "Minor GC", + "Major GC (Other)", + "Major GC (Mark)", + "Major GC (Sweep)", + "Major GC (Compact)", + "Unmark Gray", + "Barrier", + "CC (Free Snow White)", + "CC (Build Graph)", + "CC (Scan Roots)", + "CC (Collect White)", + "CC (Finalize)" + ] + }, + { + "name": "Network", + "color": "lightblue", + "subcategories": [ + "Other" + ] + }, + { + "name": "Graphics", + "color": "green", + "subcategories": [ + "Other", + "DisplayList building", + "DisplayList merging", + "Layer building", + "Tile allocation", + "WebRender display list", + "Rasterization", + "Flushing async paints", + "Image decoding", + "WebGPU" + ] + }, + { + "name": "DOM", + "color": "blue", + "subcategories": [ + "Other" + ] + }, + { + "name": "Android", + "color": "yellow", + "subcategories": [ + "Other" + ] + }, + { + "name": "AndroidX", + "color": "orange", + "subcategories": [ + "Other" + ] + }, + { + "name": "Java", + "color": "blue", + "subcategories": [ + "Other" + ] + }, + { + "name": "Mozilla", + "color": "green", + "subcategories": [ + "Other" + ] + }, + { + "name": "Kotlin", + "color": "purple", + "subcategories": [ + "Other" + ] + }, + { + "name": "Blocked", + "color": "lightblue", + "subcategories": [ + "Other" + ] + }, + { + "name": "Mailnews", + "color": "brown", + "subcategories": [ + "Other" + ] + }, + { + "name": "IPC", + "color": "lightgreen", + "subcategories": [ + "Other" + ] + }, + { + "name": "Media", + "color": "orange", + "subcategories": [ + "Other", + "Cubeb", + "Playback", + "Real-time rendering" + ] + }, + { + "name": "Accessibility", + "color": "brown", + "subcategories": [ + "Other" + ] + }, + { + "name": "Profiler", + "color": "lightred", + "subcategories": [ + "Other" + ] + }, + { + "name": "Timer", + "color": "grey", + "subcategories": [ + "Other" + ] + }, + { + "name": "Remote-Protocol", + "color": "grey", + "subcategories": [ + "Other" + ] + }, + { + "name": "Sandbox", + "color": "grey", + "subcategories": [ + "Other" + ] + }, + { + "name": "Telemetry", + "color": "grey", + "subcategories": [ + "Other" + ] + }, + { + "name": "ML", + "color": "magenta", + "subcategories": [ + "Other", + "Inference", + "Setup" + ] + } + ], + "preprocessedProfileVersion": 58, + "appBuildID": "20251203114256", + "configuration": { + "features": [ + "js", + "nostacksampling", + "tracing" + ], + "threads": [ + "GeckoMain", + "Compositor", + "Renderer", + "SwComposite", + "DOM Worker" + ], + "interval": 1, + "capacity": 134217728, + "activeTabID": 3 + }, + "physicalCPUs": 12, + "logicalCPUs": 12, + "CPUName": "Apple M3 Pro", + "symbolicated": true, + "updateChannel": "default", + "markerSchema": [ + { + "name": "Awake", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "CPU Time", + "format": "duration" + }, + { + "key": "QoS", + "label": "Quality of Service", + "format": "string" + } + ] + }, + { + "name": "FlowMarker", + "tooltipLabel": "{marker.name} (flow={marker.data.flow})", + "tableLabel": "{marker.name} (flow={marker.data.flow})", + "chartLabel": "{marker.name} (flow={marker.data.flow})", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "flow", + "label": "Flow", + "format": "flow-id" + } + ] + }, + { + "name": "Runnable", + "tableLabel": "{marker.data.name} runnable: {marker.data.runnable}", + "chartLabel": "{marker.data.name}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "name", + "label": "Runnable Name", + "format": "string" + }, + { + "key": "runnable", + "label": "Runnable", + "format": "terminating-flow-id" + } + ], + "description": "Marker representing a runnable being executed.", + "isStackBased": true + }, + { + "name": "subprocesspriority", + "tooltipLabel": "priority of child {marker.data.pid}: {marker.data.Priority}", + "tableLabel": "priority of child {marker.data.pid}: {marker.data.Priority}", + "chartLabel": "priority of child {marker.data.pid}: {marker.data.Priority}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "pid", + "format": "integer" + }, + { + "key": "Priority", + "format": "string" + }, + { + "key": "Marker cause", + "format": "string" + } + ] + }, + { + "name": "TextStack", + "tableLabel": "{marker.name} - {marker.data.name}", + "chartLabel": "{marker.data.name}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "name", + "label": "Details", + "format": "string" + } + ], + "isStackBased": true + }, + { + "name": "Hist", + "tooltipLabel": "{marker.data.id}[{marker.data.key}] {marker.data.val}", + "tableLabel": "{marker.data.id}[{marker.data.key}]: {marker.data.val}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "id", + "label": "Histogram Name", + "format": "unique-string" + }, + { + "key": "key", + "label": "Key", + "format": "string" + }, + { + "key": "val", + "label": "Sample", + "format": "integer" + } + ] + }, + { + "name": "Text", + "tableLabel": "{marker.name} - {marker.data.name}", + "chartLabel": "{marker.data.name}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "name", + "label": "Details", + "format": "string" + } + ] + }, + { + "name": "Styles", + "display": [ + "marker-chart", + "marker-table", + "timeline-overview" + ], + "fields": [ + { + "key": "elementsTraversed", + "label": "Elements traversed", + "format": "integer" + }, + { + "key": "elementsStyled", + "label": "Elements styled", + "format": "integer" + }, + { + "key": "elementsMatched", + "label": "Elements matched", + "format": "integer" + }, + { + "key": "stylesShared", + "label": "Styles shared", + "format": "integer" + }, + { + "key": "stylesReused", + "label": "Styles reused", + "format": "integer" + } + ] + }, + { + "name": "StackMarker", + "display": [ + "marker-chart", + "marker-table", + "timeline-overview" + ], + "fields": [], + "isStackBased": true + }, + { + "name": "NoPayloadUserData", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [] + }, + { + "name": "Task", + "tableLabel": "{marker.data.name} - priority: {marker.data.priorityName} ({marker.data.priority}) task: {marker.data.task}", + "chartLabel": "{marker.data.name}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "name", + "label": "Task Name", + "format": "string" + }, + { + "key": "priority", + "label": "Priority level", + "format": "integer" + }, + { + "key": "task", + "label": "Task", + "format": "terminating-flow-id" + }, + { + "key": "priorityName", + "label": "Priority Name", + "format": "string" + } + ], + "description": "Marker representing a task being executed in TaskController.", + "isStackBased": true + }, + { + "name": "DOMEvent", + "tooltipLabel": "{marker.data.eventType} - DOMEvent", + "tableLabel": "{marker.data.eventType} - {marker.data.target}", + "chartLabel": "{marker.data.eventType}", + "display": [ + "marker-chart", + "marker-table", + "timeline-overview" + ], + "fields": [ + { + "key": "target", + "label": "Event Target", + "format": "string" + }, + { + "key": "latency", + "label": "Latency", + "format": "duration" + }, + { + "key": "eventType", + "label": "Event Type", + "format": "string" + } + ], + "isStackBased": true + }, + { + "name": "VsyncTimestamp", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [] + }, + { + "name": "Preference", + "tableLabel": "{marker.data.prefName}: {marker.data.prefValue} ({marker.data.prefType})", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "prefName", + "label": "Name", + "format": "string" + }, + { + "key": "prefKind", + "label": "Kind", + "format": "string" + }, + { + "key": "prefType", + "label": "Type", + "format": "string" + }, + { + "key": "prefValue", + "label": "Value", + "format": "string" + } + ] + }, + { + "name": "FrameMessage", + "tooltipLabel": "FrameMessage - {marker.name}", + "tableLabel": "{marker.data.name}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "name", + "label": "Message Name", + "format": "unique-string" + }, + { + "key": "sync", + "label": "Sync", + "format": "string" + } + ] + }, + { + "name": "Scalar", + "tooltipLabel": "{marker.data.id}[{marker.data.key}] {marker.data.val}", + "tableLabel": "{marker.data.id}[{marker.data.key}]: {marker.data.val}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "id", + "label": "Scalar Name", + "format": "unique-string" + }, + { + "key": "key", + "label": "Key", + "format": "string" + }, + { + "key": "scalarType", + "label": "Type", + "format": "unique-string" + }, + { + "key": "val", + "label": "Value", + "format": "string" + } + ] + }, + { + "name": "IdlePurgePeek", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "status", + "label": "Status", + "format": "string" + } + ], + "description": "Check if we should purge memory" + }, + { + "name": "IdlePurge", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "num_calls", + "label": "Number of PurgeNow() calls", + "format": "integer" + }, + { + "key": "next", + "label": "Last result", + "format": "string" + } + ], + "description": "Purge memory from mozjemalloc in idle time" + }, + { + "name": "JSActorMessage", + "tooltipLabel": "JSActor - {marker.name}", + "tableLabel": "[{marker.data.actor}] {marker.data.name}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "actor", + "label": "Actor Name", + "format": "string" + }, + { + "key": "name", + "label": "Message Name", + "format": "string" + } + ] + }, + { + "name": "CSSAnimation", + "tableLabel": "{marker.data.Name}: {marker.data.properties}", + "chartLabel": "{marker.data.Name}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "Name", + "format": "string" + }, + { + "key": "properties", + "label": "Animated Properties", + "format": "string" + }, + { + "key": "oncompositor", + "label": "Can Run on Compositor", + "format": "string" + }, + { + "key": "Target", + "format": "string" + } + ] + }, + { + "name": "GCMinor", + "display": [ + "marker-chart", + "marker-table", + "timeline-memory" + ], + "fields": [], + "description": "A minor GC (aka nursery collection) to clear out the buffer used for recent allocations and move surviving data to the tenured (long-lived) heap." + }, + { + "name": "CCSlice", + "tooltipLabel": "{marker.name} (idle={marker.data.idle})", + "tableLabel": "{marker.name} (idle={marker.data.idle})", + "chartLabel": "{marker.name} (idle={marker.data.idle})", + "display": [ + "marker-chart", + "marker-table", + "timeline-memory" + ], + "fields": [ + { + "key": "idle", + "label": "Idle", + "format": "integer" + } + ], + "description": "Information for an individual CC slice." + }, + { + "name": "TimingDist", + "tooltipLabel": "{marker.data.cat}.{marker.data.id} {marker.data.label} {marker.data.duration}{marker.data.sample}", + "tableLabel": "{marker.data.cat}.{marker.data.id} {marker.data.label}: {marker.data.duration}{marker.data.sample}{marker.data.samples}", + "chartLabel": "{marker.data.cat}.{marker.data.id} {marker.data.label}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "cat", + "label": "Category", + "format": "unique-string" + }, + { + "key": "id", + "label": "Metric", + "format": "unique-string" + }, + { + "key": "label", + "label": "Label", + "format": "unique-string" + }, + { + "key": "timer_id", + "label": "TimerId", + "format": "integer" + }, + { + "key": "duration", + "label": "Duration", + "format": "string" + }, + { + "key": "sample", + "label": "Sample", + "format": "string" + }, + { + "key": "samples", + "label": "Samples", + "format": "string" + } + ] + }, + { + "name": "DistMetric", + "tooltipLabel": "{marker.data.cat}.{marker.data.id} {marker.data.label} {marker.data.sample}", + "tableLabel": "{marker.name} - {marker.data.cat}.{marker.data.id} {marker.data.label}: {marker.data.sample}{marker.data.samples}", + "chartLabel": "{marker.data.cat}.{marker.data.id}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "cat", + "label": "Category", + "format": "unique-string" + }, + { + "key": "id", + "label": "Metric", + "format": "unique-string" + }, + { + "key": "label", + "label": "Label", + "format": "unique-string" + }, + { + "key": "sample", + "label": "Sample", + "format": "string" + }, + { + "key": "samples", + "label": "Samples", + "format": "string" + } + ] + }, + { + "name": "IntLikeMetric", + "tooltipLabel": "{marker.data.cat}.{marker.data.id} {marker.data.label} {marker.data.val}", + "tableLabel": "{marker.name} - {marker.data.cat}.{marker.data.id} {marker.data.label}: {marker.data.val}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "cat", + "label": "Category", + "format": "unique-string" + }, + { + "key": "id", + "label": "Metric", + "format": "unique-string" + }, + { + "key": "label", + "label": "Label", + "format": "unique-string" + }, + { + "key": "val", + "label": "Value", + "format": "integer" + } + ] + }, + { + "name": "EventMetric", + "tooltipLabel": "{marker.data.id}", + "tableLabel": "{marker.data.id}: {marker.data.extra}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "id", + "label": "Metric", + "format": "unique-string" + }, + { + "key": "extra", + "label": "Extra", + "format": "string" + } + ] + }, + { + "name": "ProcessPriority", + "tooltipLabel": "priority: {marker.data.Priority}", + "tableLabel": "priority: {marker.data.Priority}", + "chartLabel": "priority: {marker.data.Priority}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "Priority", + "format": "string" + }, + { + "key": "Marker cause", + "format": "string" + } + ] + }, + { + "name": "TerminatingFlowStackMarker", + "tooltipLabel": "{marker.name} (flow={marker.data.flow})", + "tableLabel": "{marker.name} (flow={marker.data.flow})", + "chartLabel": "{marker.name} (flow={marker.data.flow})", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "flow", + "label": "Flow", + "format": "terminating-flow-id" + } + ], + "isStackBased": true + }, + { + "name": "CONTENT_FULL_PAINT_TIME", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [] + }, + { + "name": "CONTENT_FRAME_TIME", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [] + }, + { + "name": "CompositorAnimation", + "tableLabel": "{marker.data.property}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "pid", + "label": "Process Id", + "format": "integer" + }, + { + "key": "id", + "label": "Animation Id", + "format": "integer" + }, + { + "key": "property", + "label": "Animated Property", + "format": "string" + } + ] + }, + { + "name": "tracing", + "display": [ + "marker-chart", + "marker-table", + "timeline-overview" + ], + "fields": [ + { + "key": "category", + "label": "Type", + "format": "string" + } + ] + }, + { + "name": "UserTiming", + "tooltipLabel": "{marker.data.name}", + "tableLabel": "{marker.data.name}", + "chartLabel": "{marker.data.name}", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "name", + "label": "User Marker Name", + "format": "string" + }, + { + "key": "entryType", + "label": "Entry Type", + "format": "string" + }, + { + "key": "startMark", + "label": "Start Mark", + "format": "string" + }, + { + "key": "endMark", + "label": "End Mark", + "format": "string" + } + ], + "description": "UserTimingMeasure is created using the DOM API performance.measure()." + }, + { + "name": "MainThreadLongTask", + "display": [ + "marker-chart", + "marker-table" + ], + "fields": [ + { + "key": "category", + "label": "Type", + "format": "string" + } + ] + } + ], + "sampleUnits": { + "time": "ms", + "eventDelay": "ms", + "threadCPUDelta": "µs" + }, + "profilingStartTime": 324347.499109375, + "profilingEndTime": 324384.271151375 + }, + "libs": [ + { + "arch": "arm64", + "name": "XUL", + "path": "/Users/alexical/mozilla-unified/obj-opt/dist/Nightly.app/Contents/MacOS/XUL", + "debugName": "XUL", + "debugPath": "/Users/alexical/mozilla-unified/obj-opt/dist/Nightly.app/Contents/MacOS/XUL", + "breakpadId": "4C4C44EF55553144A1B2E8DA562EBF400", + "codeId": "4C4C44EF55553144A1B2E8DA562EBF40" + } + ], + "pages": [ + { + "tabID": 1, + "innerWindowID": 2, + "url": "chrome://browser/content/hiddenWindowMac.xhtml", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 2, + "innerWindowID": 4, + "url": "chrome://browser/content/browser.xhtml", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 2, + "innerWindowID": 6, + "url": "about:blank", + "embedderInnerWindowID": 4, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 4, + "innerWindowID": 10, + "url": "chrome://extensions/content/dummy.xhtml", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 5, + "innerWindowID": 10737418241, + "url": "moz-extension://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 6, + "innerWindowID": 10737418242, + "url": "moz-extension://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 7, + "innerWindowID": 10737418243, + "url": "moz-extension://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 8, + "innerWindowID": 10737418244, + "url": "moz-extension://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 9, + "innerWindowID": 10737418245, + "url": "moz-extension://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 10, + "innerWindowID": 10737418246, + "url": "moz-extension://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 14, + "innerWindowID": 17179869185, + "url": "http://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 12, + "innerWindowID": 15032385537, + "url": "http://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 11, + "innerWindowID": 6442450946, + "url": "about:newtab", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 13, + "innerWindowID": 21474836482, + "url": "file://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 15, + "innerWindowID": 21474836484, + "url": "file://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + }, + { + "tabID": 3, + "innerWindowID": 21474836485, + "url": "file://", + "embedderInnerWindowID": 0, + "isPrivateBrowsing": false, + "favicon": null + } + ], + "counters": [], + "profilerOverhead": [], + "shared": { + "stringArray": [ + "default-theme@mozilla.org", + "Extension \"System theme — auto\" (ID: default-theme@mozilla.org)", + "data-leak-blocker@mozilla.com", + "Extension \"Data Leak Blocker\" (ID: data-leak-blocker@mozilla.com)", + "webcompat@mozilla.org", + "Extension \"Web Compatibility Interventions\" (ID: webcompat@mozilla.org)", + "pictureinpicture@mozilla.org", + "Extension \"Picture-In-Picture\" (ID: pictureinpicture@mozilla.org)", + "newtab@mozilla.org", + "Extension \"New Tab\" (ID: newtab@mozilla.org)", + "addons-search-detection@mozilla.com", + "Extension \"Add-ons Search Detection\" (ID: addons-search-detection@mozilla.com)", + "formautofill@mozilla.org", + "Extension \"Form Autofill\" (ID: formautofill@mozilla.org)", + "ipp-activator@mozilla.com", + "Extension \"IPP Activator\" (ID: ipp-activator@mozilla.com)", + "(root)", + "(DOM) Event.type", + "(DOM) MouseEvent.screenX", + "(DOM) MouseEvent.screenY", + "mousedown", + "(DOM) MouseEvent.button", + "(DOM) MouseEvent.altKey", + "(DOM) MouseEvent.ctrlKey", + "(DOM) MouseEvent.metaKey", + "(DOM) MouseEvent.shiftKey", + "EventStateManager::PostHandleEvent", + "js::RunScript", + "XUL", + "Task RefreshDriver::EnsureTimerStarted::catch-up", + "RefreshDriver tick", + "Update the rendering Layout, content-visibility and resize observers", + "EventStateManager::PreHandleEvent", + "ElementStateChanged", + "EventDispatcher::Dispatch", + "RefreshObserver", + "TaskController::AddTask", + "Runnable", + "DOMEvent", + "Awake", + "XRE_InitChildProcess", + "Task NotifyObservers", + "Task PContent::Msg_AddPermission", + "PContent::Msg_AddPermission", + "IPC Accumulator", + "setTimeout", + "(DOM) Document.activeElement", + "file://", + "foo", + "bar", + "(DOM) JSWindowActorChild.constructor", + "(DOM) JSWindowActorChild.document", + "(DOM) JSWindowActorChild.contentWindow", + "(DOM) Window.getSelection", + "(DOM) Selection.__stringifier", + "(DOM) Event.timeStamp", + "(DOM) MouseEvent.isTrusted", + "(DOM) Event.defaultPrevented", + "(DOM) Window.setTimeout", + "(DOM) nsIAsyncShutdownBlocker.name", + "Task PBrowser::Msg_RealMouseEnterExitWidgetEvent", + "PBrowser::Msg_RealMouseEnterExitWidgetEvent", + "Task PBrowser::Msg_RealMouseMoveEvent", + "PBrowser::Msg_RealMouseMoveEvent", + "Task PBrowser::Msg_RealMouseButtonEvent", + "PBrowser::Msg_RealMouseButtonEvent", + "EventDispatcher::Dispatch mousedown", + "PContent::Msg_InsertNewFocusActionId", + "PWindowGlobal::Msg_UpdateDocumentHasUserInteracted", + "EventDispatcher::Dispatch mouseup", + "JSWindowActorProtocol::HandleEvent", + "resource:///actors/GenAIChild.sys.mjs", + "handleEvent", + "Window.setTimeout", + "Task PVsync::Msg_Notify", + "PVsync::Msg_Notify", + "f67ae7353bf8e5000", + "f67ae7353bf8e9600", + "ModuleEvaluation", + "ChromeUtils.importESModule", + "Preference Read", + "mozilla::base_profiler_markers_detail::AddMarkerToBuffer >(mozilla::ProfileChunkedBuffer&, mozilla::ProfilerStringView const&, mozilla::MarkerCategory const&, mozilla::MarkerOptions&&, bool (*)(mozilla::ProfileChunkedBuffer&, mozilla::StackCaptureOptions), nsTString const&)", + "profiler_capture_backtrace()", + "mozilla::base_profiler_markers_detail::AddMarkerToBuffer(mozilla::ProfileChunkedBuffer&, mozilla::ProfilerStringView const&, mozilla::MarkerCategory const&, mozilla::MarkerOptions&&, bool (*)(mozilla::ProfileChunkedBuffer&, mozilla::StackCaptureOptions), Flow const&)::{lambda(mozilla::ProfileChunkedBuffer&)#1}::operator()(mozilla::ProfileChunkedBuffer&) const", + "/Users/alexical/mozilla-unified/obj-opt/dist/include/mozilla/BaseProfilerMarkersDetail.h", + "mozilla::base_profiler_markers_detail::AddMarkerToBuffer >(mozilla::ProfileChunkedBuffer&, mozilla::ProfilerStringView const&, mozilla::MarkerCategory const&, mozilla::MarkerOptions&&, bool (*)(mozilla::ProfileChunkedBuffer&, mozilla::StackCaptureOptions), nsTString const&)::{lambda(mozilla::ProfileChunkedBuffer&)#1}::operator()(mozilla::ProfileChunkedBuffer&) const", + "/Users/alexical/mozilla-unified/tools/profiler/core/platform.cpp" + ], + "sources": { + "length": 4, + "uuid": [ + null, + null, + null, + null + ], + "filename": [ + 47, + 71, + 84, + 86 + ] + } + }, + "threads": [ + { + "name": "GeckoMain", + "isMainThread": true, + "processType": "tab", + "processName": "file:// Content", + "processStartupTime": 5584.521484375, + "processShutdownTime": null, + "registerTime": 5605.748109375, + "unregisterTime": null, + "tid": 2503164, + "pid": "39156", + "pausedRanges": [ + { + "startTime": 319713.765958, + "endTime": null, + "reason": "profiler-paused" + } + ], + "frameTable": { + "address": [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 4256519, + -1, + -1, + -1, + -1, + 75486643, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 4264179, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 4256519 + ], + "inlineDepth": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1 + ], + "category": [ + null, + 8, + 4, + 4, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 1, + 1, + null, + 1, + 1, + 8, + 3, + null, + 1, + 3, + 3, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + null, + 8, + 1, + 1, + 1, + 1, + 4, + 4, + 4, + 8, + 1, + 1, + null + ], + "subcategory": [ + null, + 0, + 0, + 6, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + null, + 0, + 0, + 0, + 5, + null, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + null, + 0, + 0, + 0, + 0, + 0, + 0, + 4, + 4, + 0, + 0, + 0, + null + ], + "func": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 42, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 54, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 31 + ], + "nativeSymbol": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 0, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 2, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 0 + ], + "innerWindowID": [ + 0, + 0, + 21474836485, + 21474836485, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "line": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 317, + null, + null, + null, + null, + 7903, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 303, + null, + null, + null, + null, + null, + null, + null, + 81, + null, + null, + null, + 303 + ], + "column": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 50, + null, + null, + null, + null + ], + "length": 55 + }, + "funcTable": { + "isJS": [ + false, + false, + true, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false + ], + "relevantForJS": [ + false, + true, + false, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + false, + false, + true, + false, + false, + true, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + true, + false, + false, + false + ], + "name": [ + 16, + 20, + 48, + 49, + 50, + 17, + 51, + 46, + 52, + 53, + 54, + 55, + 56, + 57, + 21, + 22, + 23, + 24, + 25, + 18, + 19, + 58, + 59, + 45, + 40, + 41, + 81, + 60, + 61, + 26, + 33, + 85, + 29, + 30, + 31, + 62, + 63, + 64, + 65, + 34, + 66, + 67, + 82, + 32, + 68, + 42, + 43, + 69, + 70, + 27, + 72, + 73, + 74, + 75, + 83 + ], + "resource": [ + -1, + -1, + 8, + 8, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 9, + -1, + -1, + -1, + -1, + 9, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 9, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 10, + -1, + -1, + -1, + 9 + ], + "source": [ + null, + null, + 0, + 0, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 2, + null, + null, + null, + null, + 2, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 3, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + 2 + ], + "lineNumber": [ + null, + null, + 19, + 11, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 47, + null, + null, + null, + null + ], + "columnNumber": [ + null, + null, + 13, + 13, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 14, + null, + null, + null, + null + ], + "length": 55 + }, + "nativeSymbols": { + "libIndex": [ + 0, + 0, + 0 + ], + "address": [ + 4256352, + 75486340, + 4264108 + ], + "name": [ + 81, + 82, + 83 + ], + "functionSize": [ + 840, + 540, + 432 + ], + "length": 3 + }, + "resourceTable": { + "lib": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + 0, + null + ], + "name": [ + 1, + 3, + 5, + 7, + 9, + 11, + 13, + 15, + 47, + 28, + 71 + ], + "host": [ + 0, + 2, + 4, + 6, + 8, + 10, + 12, + 14, + null, + null, + null + ], + "type": [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 5, + 1, + 5 + ], + "length": 11 + }, + "stackTable": { + "frame": [ + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 23, + 6, + 7, + 8, + 9, + 10, + 11, + 24, + 25, + 26, + 54, + 27, + 28, + 29, + 30, + 31, + 31, + 32, + 33, + 34, + 26, + 54, + 35, + 36, + 31, + 37, + 38, + 39, + 40, + 26, + 54, + 29, + 41, + 42, + 30, + 31, + 31, + 43, + 26, + 54, + 31, + 43, + 44, + 42, + 45, + 46, + 26, + 54, + 47, + 48, + 49, + 50, + 51, + 31, + 43, + 30, + 31, + 52, + 53, + 33, + 34, + 26, + 54 + ], + "prefix": [ + null, + null, + 0, + 0, + 2, + 2, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 26, + 26, + 26, + 26, + 26, + 26, + 0, + 34, + 35, + 36, + 34, + 38, + 39, + 40, + 39, + 41, + 34, + 44, + 45, + 46, + 47, + 34, + 49, + 50, + 34, + 52, + 53, + 54, + 55, + 56, + 53, + 58, + 59, + 58, + 53, + 61, + 45, + 64, + 65, + 55, + 53, + 68, + 69, + 34, + 71, + 72, + 73, + 54, + 75, + 76, + 77, + 78, + 79, + 39, + 81, + 82, + 34, + 84, + 85, + 86, + 87, + 88 + ], + "length": 90 + }, + "markers": { + "data": [ + { + "type": "Awake", + "QoS": "User Interactive" + }, + { + "innerWindowID": 21474836485, + "type": "DOMEvent", + "eventType": "mousedown", + "target": "html@10bc0c080", + "latency": 2.965583333333333 + }, + { + "type": "FlowMarker", + "flow": 77 + }, + { + "type": "TextStack", + "name": "" + }, + { + "type": "TextStack", + "name": "resource:///actors/GenAIChild.sys.mjs", + "cause": { + "tid": 2503164, + "time": 324383.877234375, + "stack": 57 + } + }, + { + "type": "Preference", + "prefName": "browser.drag_out_of_frame_style", + "prefKind": "User", + "prefType": "Int", + "prefValue": "1" + }, + { + "type": "TextStack", + "name": "" + }, + { + "type": "TextStack", + "name": "resource://gre/actors/AutoScrollChild.sys.mjs", + "cause": { + "tid": 2503164, + "time": 324384.231984375, + "stack": 57 + } + }, + { + "innerWindowID": 21474836485, + "type": "DOMEvent", + "eventType": "mousedown", + "target": "html@10bc0c080", + "latency": 2.965583333333333 + }, + { + "innerWindowID": 21474836485, + "type": "Text", + "name": "Coalesced input move flusher [Event]", + "cause": { + "tid": 2503164, + "time": 324347.262526375, + "stack": 62 + } + }, + { + "type": "Task", + "name": "PBrowser::Msg_RealMouseButtonEvent", + "priority": 6, + "priorityName": "InputHigh", + "task": 76 + }, + { + "type": "Awake", + "CPU Time": 37.453 + }, + null + ], + "name": [ + 39, + 38, + 36, + 78, + 79, + 80, + 78, + 79, + 38, + 35, + 37, + 39, + 44 + ], + "startTime": [ + 324347.219609375, + 324347.499109375, + 324383.871026375, + 324383.866109375, + 324383.823942375, + 324384.170276375, + 324384.230526375, + 324384.208942375, + 5584.521484375, + 324347.251067375, + 324347.233234375, + 5584.521484375, + 5584.521484375 + ], + "endTime": [ + 5584.521484375, + 5584.521484375, + 5584.521484375, + 324383.873359375, + 324383.874359375, + 5584.521484375, + 324384.231109375, + 324384.231776375, + 324384.271151375, + 324384.347234375, + 324384.353442375, + 324384.692192375, + 324576.394067375 + ], + "phase": [ + 2, + 2, + 0, + 1, + 1, + 0, + 1, + 1, + 3, + 1, + 1, + 3, + 3 + ], + "category": [ + 1, + 8, + 1, + 4, + 4, + 1, + 4, + 4, + 8, + 7, + 1, + 1, + 23 + ], + "length": 13 + }, + "samples": { + "length": 32, + "time": [ + 324347.532151375, + 324347.697817375, + 324347.735734375, + 324383.79677637503, + 324383.806276375, + 324383.811067375, + 324383.89902637503, + 324383.90765137505, + 324383.9553173751, + 324383.95594237506, + 324383.95960937504, + 324383.96073437505, + 324383.97785937507, + 324384.01777637505, + 324384.01919237507, + 324384.0209843751, + 324384.0264843751, + 324384.06756737514, + 324384.0863173751, + 324384.1059013751, + 324384.1337343751, + 324384.1485263751, + 324384.23594237515, + 324384.23819237517, + 324384.25665137515, + 324384.25690137513, + 324384.26069237513, + 324384.26073437516, + 324384.2628173752, + 324384.2628593752, + 324384.2653593752, + 324384.2655263752 + ], + "weight": null, + "weightType": "samples", + "stack": [ + 3, + 5, + 6, + 5, + 3, + 1, + 7, + 1, + 8, + 1, + 9, + 1, + 10, + 1, + 11, + 1, + 12, + 1, + 13, + 1, + 14, + 1, + 7, + 1, + 8, + 1, + 15, + 1, + 16, + 1, + 17, + 1 + ], + "eventDelay": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ], + "argumentValues": [ + null, + 6, + 24, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + "tracedValuesBuffer": "AgAAABIAAQAAAAwHAAAAAAYAAAABAAcAAQAAABE=", + "tracedObjectShapes": [ + [ + "MouseEvent" + ] + ] + } + ], + "profilingLog": { + "39133": { + "bufferGlobalController": { + "controllerCreationTime_TSms": 323947.267208, + "logBegin_TSms": 323947.267042, + "updates": [], + "updatesSchema": "0: pid, 1: chunkRelease_TSms, 3: chunkDiff" + }, + "profilingLogBegin_TSms": 323946.88225, + "profilingLogEnd_TSms": 325307.459125 + }, + "39136": { + "profilingLogBegin_TSms": 323451.186208, + "profilingLogEnd_TSms": 324804.74575 + }, + "39137": { + "profilingLogBegin_TSms": 323414.368792, + "profilingLogEnd_TSms": 324769.997375 + }, + "39138": { + "profilingLogBegin_TSms": 323030.653583, + "profilingLogEnd_TSms": 324388.046958 + }, + "39139": { + "profilingLogBegin_TSms": 323014.021875, + "profilingLogEnd_TSms": 324366.797167 + }, + "39143": { + "profilingLogBegin_TSms": 322645.606, + "profilingLogEnd_TSms": 323999.851042 + }, + "39144": { + "profilingLogBegin_TSms": 322335.497542, + "profilingLogEnd_TSms": 323689.253375 + }, + "39151": { + "profilingLogBegin_TSms": 321587.79575, + "profilingLogEnd_TSms": 322941.864333 + }, + "39152": { + "profilingLogBegin_TSms": 321587.792458, + "profilingLogEnd_TSms": 322941.85975 + }, + "39153": { + "profilingLogBegin_TSms": 321565.734083, + "profilingLogEnd_TSms": 322918.633625 + }, + "39156": { + "profilingLogBegin_TSms": 318363.045375, + "profilingLogEnd_TSms": 319719.776417 + }, + "39162": { + "profilingLogBegin_TSms": 314471.372125, + "profilingLogEnd_TSms": 315826.738375 + }, + "39378": { + "profilingLogBegin_TSms": 142455.614167, + "profilingLogEnd_TSms": 143810.263 + } + }, + "profileGatheringLog": { + "39133": { + "events": [ + [ + 325307.830042, + "Generated parent process profile, size:", + 804072 + ], + [ + 325307.831708, + "No exit profiles." + ], + [ + 325307.837125, + "Waiting for pending profile, pid:", + 39136 + ], + [ + 325307.837667, + "Waiting for pending profile, pid:", + 39137 + ], + [ + 325307.838083, + "Waiting for pending profile, pid:", + 39138 + ], + [ + 325307.838458, + "Waiting for pending profile, pid:", + 39139 + ], + [ + 325307.838833, + "Waiting for pending profile, pid:", + 39143 + ], + [ + 325307.839167, + "Waiting for pending profile, pid:", + 39144 + ], + [ + 325307.8395, + "Waiting for pending profile, pid:", + 39151 + ], + [ + 325307.839792, + "Waiting for pending profile, pid:", + 39152 + ], + [ + 325307.840125, + "Waiting for pending profile, pid:", + 39153 + ], + [ + 325307.840417, + "Waiting for pending profile, pid:", + 39156 + ], + [ + 325307.84075, + "Waiting for pending profile, pid:", + 39162 + ], + [ + 325307.841042, + "Waiting for pending profile, pid:", + 39378 + ], + [ + 325312.892, + "Got profile from pid, with size:", + 39136, + 370202 + ], + [ + 325313.827083, + "Got profile from pid, with size:", + 39144, + 369231 + ], + [ + 325314.569458, + "Got profile from pid, with size:", + 39139, + 370802 + ], + [ + 325315.407292, + "Got profile from pid, with size:", + 39153, + 374670 + ], + [ + 325316.251625, + "Got profile from pid, with size:", + 39143, + 374172 + ], + [ + 325317.081667, + "Got profile from pid, with size:", + 39152, + 376043 + ], + [ + 325317.775167, + "Got profile from pid, with size:", + 39151, + 376231 + ], + [ + 325318.563583, + "Got profile from pid, with size:", + 39378, + 371592 + ], + [ + 325319.515042, + "Got profile from pid, with size:", + 39137, + 481709 + ], + [ + 325320.320875, + "Got profile from pid, with size:", + 39138, + 761951 + ], + [ + 325321.083458, + "Got profile from pid, with size:", + 39162, + 371752 + ], + [ + 325321.857458, + "Got profile from pid, with size:", + 39156, + 541826 + ], + [ + 325322.156667, + "Finished gathering, total size:", + 5944267 + ] + ], + "profileGatheringLogBegin_TSms": 325298.33725, + "profileGatheringLogEnd_TSms": 325322.157125 + } + } +} \ No newline at end of file diff --git a/src/test/fixtures/utils.ts b/src/test/fixtures/utils.ts index 11b5f9955a..8e86ecf1a9 100644 --- a/src/test/fixtures/utils.ts +++ b/src/test/fixtures/utils.ts @@ -22,6 +22,7 @@ import { } from 'firefox-profiler/profile-logic/profile-data'; import { getProfileWithDicts } from './profiles/processed-profile'; import { StringTable } from '../../utils/string-table'; +import { base64StringToBytes } from '../../utils/base64'; import type { IndexIntoCallNodeTable, @@ -153,12 +154,16 @@ export function computeThreadFromRawThread( sampleUnits, referenceCPUDeltaPerMs ); + const tracedValuesBuffer = rawThread.tracedValuesBuffer + ? base64StringToBytes(rawThread.tracedValuesBuffer) + : undefined; return createThreadFromDerivedTables( rawThread, samples, stackTable, stringTable, - shared.sources + shared.sources, + tracedValuesBuffer ); } diff --git a/src/test/integration/symbolicator-cli/__snapshots__/symbolicator-cli.test.ts.snap b/src/test/integration/symbolicator-cli/__snapshots__/symbolicator-cli.test.ts.snap index 2e547d427a..afca09a6e0 100644 --- a/src/test/integration/symbolicator-cli/__snapshots__/symbolicator-cli.test.ts.snap +++ b/src/test/integration/symbolicator-cli/__snapshots__/symbolicator-cli.test.ts.snap @@ -87,7 +87,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -2108,7 +2108,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "a.out", "sampleUnits": Object { diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index 913438be86..585ec17655 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -418,7 +418,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Firefox", "sourceURL": "", @@ -880,6 +880,9 @@ Object { }, "tid": 0, "unregisterTime": null, + "usedInnerWindowIDs": Array [ + 2, + ], }, Object { "frameTable": Object { @@ -1492,6 +1495,9 @@ Array [ }, "tid": 0, "unregisterTime": null, + "usedInnerWindowIDs": Array [ + 2, + ], }, Object { "frameTable": Object { @@ -2979,6 +2985,8 @@ CallTree { }, }, "tid": 0, + "tracedObjectShapes": undefined, + "tracedValuesBuffer": undefined, "unregisterTime": null, "userContextId": undefined, }, @@ -3313,6 +3321,8 @@ Object { }, }, "tid": 0, + "tracedObjectShapes": undefined, + "tracedValuesBuffer": undefined, "unregisterTime": null, "userContextId": undefined, } @@ -3719,6 +3729,8 @@ Object { }, }, "tid": 0, + "tracedObjectShapes": undefined, + "tracedValuesBuffer": undefined, "unregisterTime": null, "userContextId": undefined, } @@ -4051,6 +4063,8 @@ Object { }, }, "tid": 0, + "tracedObjectShapes": undefined, + "tracedValuesBuffer": undefined, "unregisterTime": null, "userContextId": undefined, } @@ -4383,6 +4397,8 @@ Object { }, }, "tid": 0, + "tracedObjectShapes": undefined, + "tracedValuesBuffer": undefined, "unregisterTime": null, "userContextId": undefined, } diff --git a/src/test/store/profile-view.test.ts b/src/test/store/profile-view.test.ts index e69d5d8acc..46d1fee453 100644 --- a/src/test/store/profile-view.test.ts +++ b/src/test/store/profile-view.test.ts @@ -757,10 +757,10 @@ describe('actions/ProfileView', function () { // use a BitSet to keep track of something that's per-stack (such as whether a stack matches // the search filter), the BitSet needs at least two 32-bit slots. const { profile } = getProfileFromTextSamples(` - A[lib:K][file:S] A[lib:K][file:S] A[lib:K][file:S] D[lib:nNn][file:uV] C[lib:m][file:t] - B[lib:L][file:t] B[lib:L][file:t] E[lib:O][file:Pq] + A[lib:K][file:S] A[lib:K][file:S] A[lib:K][file:S] D[lib:nNn][file:uV] C[lib:m][file:t] + B[lib:L][file:t] B[lib:L][file:t] E[lib:O][file:Pq] A[lib:K][file:S] C[lib:m][file:t] - B[lib:L][file:t] D[lib:n][file:uV] + B[lib:L][file:t] D[lib:nNn][file:uV] B[lib:L][file:t] B[lib:L][file:t] B[lib:L][file:t] @@ -911,7 +911,7 @@ describe('actions/ProfileView', function () { ]); dispatch(ProfileView.changeCallTreeSearchString('NN')); const callTree_NN = selectedThreadSelectors.getCallTree(getState()); - // Keep all stacks which include function D, which has filename nNn + // Keep all stacks which include function D, which has lib name nNn expect(formatTree(callTree_NN)).toEqual([ '- A (total: 1, self: —)', ' - B (total: 1, self: —)', @@ -1979,6 +1979,7 @@ describe('snapshots of selectors/profile', function () { samplesThread.frameTable.innerWindowID[frameIdx] = innerWindowID; } } + samplesThread.usedInnerWindowIDs = [innerWindowID]; // Add in a thread with markers const markersThread = getThreadWithMarkers(profile.shared, [ diff --git a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap index 4ed44b5784..d0b166b1e9 100644 --- a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap @@ -591,7 +591,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "ART Trace (Android)", "sampleUnits": undefined, @@ -85564,7 +85564,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "ART Trace (Android)", "sampleUnits": undefined, @@ -334698,7 +334698,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 119159778.026, @@ -370180,7 +370180,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 119159778.026, @@ -405641,7 +405641,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 66155012.423, @@ -408272,7 +408272,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Chrome Trace", "sourceURL": "", @@ -410631,7 +410631,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 355035987.653, @@ -414163,7 +414163,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Chrome Trace", "sourceURL": "", @@ -419426,7 +419426,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Chrome Trace", "profilingEndTime": 66155012.423, @@ -420453,7 +420453,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -426392,7 +426392,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -441287,7 +441287,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -447491,7 +447491,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -465899,7 +465899,7 @@ Object { "keepProfileThreadOrder": true, "markerSchema": Array [], "platform": "Android", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "com.example.sampleapplication", "sourceCodeIsNotOnSearchfox": true, @@ -534973,7 +534973,7 @@ Object { "keepProfileThreadOrder": true, "markerSchema": Array [], "platform": "Android", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "com.example.sampleapplication", "sourceCodeIsNotOnSearchfox": true, @@ -606870,7 +606870,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "target/debug/examples/work_log (dhat)", "sourceURL": "", @@ -630465,7 +630465,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Flamegraph", "sourceURL": "", @@ -938348,7 +938348,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Flamegraph", "sourceURL": "", diff --git a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap index 03f2801ef5..b45e81d8d4 100644 --- a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap @@ -40,7 +40,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -9382,7 +9382,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -10924,7 +10924,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -12599,7 +12599,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 58, + "preprocessedProfileVersion": 59, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -13057,6 +13057,9 @@ Object { ], }, "unregisterTime": null, + "usedInnerWindowIDs": Array [ + 3, + ], }, Object { "frameTable": Object { @@ -13417,6 +13420,9 @@ Object { ], }, "unregisterTime": null, + "usedInnerWindowIDs": Array [ + 1, + ], }, Object { "frameTable": Object { @@ -13844,6 +13850,9 @@ Object { ], }, "unregisterTime": null, + "usedInnerWindowIDs": Array [ + 2, + ], }, ], } diff --git a/src/test/unit/__snapshots__/window-console.test.ts.snap b/src/test/unit/__snapshots__/window-console.test.ts.snap index 1bfd7bad1c..d55758c4da 100644 --- a/src/test/unit/__snapshots__/window-console.test.ts.snap +++ b/src/test/unit/__snapshots__/window-console.test.ts.snap @@ -36,7 +36,7 @@ Array [ %cwindow.experimental%c - The object that holds flags of all the experimental features. %cwindow.togglePseudoLocalization%c - Enable pseudo localizations by passing \\"accented\\" or \\"bidi\\" to this function, or disable using no parameters. %cwindow.toggleTimelineType%c - Toggle timeline graph type by passing \\"cpu-category\\", \\"category\\", or \\"stack\\". -%cwindow.toggleDarkMode%c - Toggle between dark mode and light mode. +%cwindow.toggleDarkMode%c - Cycle through theme preferences: system, light, dark. %cwindow.retrieveRawProfileDataFromBrowser%c - Retrieve the profile attached to the current tab and returns it. Use \\"await\\" to call it, and use saveToDisk to save it. %cwindow.extractGeckoLogs%c - Retrieve recorded logs in the current range, using the MOZ_LOG format. Use with \\"copy\\" or \\"saveToDisk\\". %cwindow.saveToDisk%c - Saves to a file the parameter passed to it, with an optional filename parameter. You can use that to save the profile returned by \\"retrieveRawProfileDataFromBrowser\\" or the data returned by \\"extractGeckoLogs\\". diff --git a/src/test/unit/dark-mode.test.ts b/src/test/unit/dark-mode.test.ts index 60d80d48f4..7fb618aa54 100644 --- a/src/test/unit/dark-mode.test.ts +++ b/src/test/unit/dark-mode.test.ts @@ -13,16 +13,10 @@ describe('isDarkMode', function () { }); jest.spyOn(Storage.prototype, 'getItem').mockImplementation(getItem); - const warn = jest.fn(() => {}); - jest.spyOn(console, 'warn').mockImplementation(warn); - + // When localStorage throws, it should default to system preference expect(isDarkMode()).toBe(false); expect(getItem).toHaveBeenCalledWith('theme'); - expect(warn).toHaveBeenCalledWith( - 'localStorage access denied', - expect.objectContaining({ message: 'dummy error' }) - ); }); it('listens to storage event', function () { diff --git a/src/test/unit/sanitize.test.ts b/src/test/unit/sanitize.test.ts index e51be4d509..ac89869166 100644 --- a/src/test/unit/sanitize.test.ts +++ b/src/test/unit/sanitize.test.ts @@ -883,6 +883,22 @@ describe('sanitizePII', function () { expect(marker.flags).toBe('0xf00ba4'); }); + it('should sanitize traced argument values', function () { + const { originalProfile, sanitizedProfile } = setup({}); + expect( + originalProfile.threads.filter((t) => t.tracedValuesBuffer).length + ).toBeGreaterThan(0); + expect( + originalProfile.threads.filter((t) => t.tracedValuesBuffer).length + ).toBeGreaterThan(0); + expect( + sanitizedProfile.threads.filter((t) => t.tracedValuesBuffer).length + ).toBe(0); + expect( + sanitizedProfile.threads.filter((t) => t.tracedObjectShapes).length + ).toBe(0); + }); + 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/@types/devtools-reps/index.d.ts b/src/types/@types/devtools-reps/index.d.ts new file mode 100644 index 0000000000..c7c11e7682 --- /dev/null +++ b/src/types/@types/devtools-reps/index.d.ts @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +declare module 'devtools-reps' { + export const MODE: { + readonly TINY: unique symbol; + readonly SHORT: unique symbol; + readonly LONG: unique symbol; + readonly HEADER: unique symbol; + }; + + export const REPS: any; + export function getRep(object: any, defaultRep?: any): any; + + export function parseURLEncodedText(text: string): any; + export function parseURLParams(url: string): any; + export function maybeEscapePropertyName(name: string): string; + export function getGripPreviewItems(grip: any): any[]; + + export const ValueSummaryReader: { + getArgumentSummaries: ( + valuesBuffer: ArrayBuffer, + shapes: Array, + valuesBufferIndex: number + ) => Array | string; + }; +} diff --git a/src/types/gecko-profile.ts b/src/types/gecko-profile.ts index fac9c4d1a0..30492c4d64 100644 --- a/src/types/gecko-profile.ts +++ b/src/types/gecko-profile.ts @@ -124,6 +124,7 @@ export type GeckoSamples = { stack: 0; time: 1; eventDelay: 2; + argumentValues?: 3; threadCPUDelta?: 3; }; data: Array< @@ -141,8 +142,14 @@ export type GeckoSamples = { // thread's event loop at the time that the sample was taken Milliseconds, ( + // Index into the values buffer containing a binary representation of the argumentValues + // It's present only when the JS Execution Tracing feature is enabled in Firefox + // OR // CPU usage value of the current thread. // It's present only when the CPU Utilization feature is enabled in Firefox. + // + // NOTE: these two options are mutually exclusive since CPU Utilization is + // mutually exclusive with JS Execution Tracing number | null ), ] @@ -160,6 +167,7 @@ export type GeckoSampleStructWithResponsiveness = { // versions may not have it or that feature could be disabled. No upgrader was // written for this change because it's a completely new data source. threadCPUDelta?: Array; + argumentValues?: Array; length: number; }; @@ -174,6 +182,7 @@ export type GeckoSampleStructWithEventDelay = { // versions may not have it or that feature could be disabled. No upgrader was // written for this change because it's a completely new data source. threadCPUDelta?: Array; + argumentValues?: Array; length: number; }; @@ -294,6 +303,8 @@ export type GeckoThread = { stackTable: GeckoStackTable; stringTable: string[]; jsTracerEvents?: JsTracerTable; + tracedValues?: string; + tracedObjectShapes?: Array; }; export type GeckoExtensionMeta = { diff --git a/src/types/profile-derived.ts b/src/types/profile-derived.ts index c0ba94655f..97231b487e 100644 --- a/src/types/profile-derived.ts +++ b/src/types/profile-derived.ts @@ -95,6 +95,7 @@ export type Thread = { // It's absent in Firefox 97 and before, or in Firefox 98+ when this thread // had no extra attribute at all. userContextId?: number; + tracedObjectShapes?: Array; // The fields below this comment are derived data, and not present on the RawThread // in the same form. @@ -108,6 +109,7 @@ export type Thread = { // The stack samples collected for this thread. This field is different from // RawThread in that the `time` column is always present. samples: SamplesTable; + tracedValuesBuffer?: ArrayBuffer; }; /** @@ -134,6 +136,7 @@ export type SamplesTable = { // This property isn't present in normal threads. However it's present for // merged threads, so that we know the origin thread for these samples. threadId?: Tid[]; + argumentValues?: Array; length: number; }; @@ -144,6 +147,7 @@ type SamplesLikeTableShape = { // See the WeightType type for more information. weight: null | number[]; weightType: WeightType; + argumentValues?: Array; length: number; }; @@ -160,6 +164,7 @@ export type CounterSamplesTable = { number?: number[]; // The count of the data, for instance for memory this would be bytes. count: number[]; + argumentValues?: Array; length: number; }; diff --git a/src/types/profile.ts b/src/types/profile.ts index 3e5e02466e..be184c51ac 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -117,6 +117,7 @@ export type RawSamplesTable = { time?: Milliseconds[]; // If the `time` column is not present, then the `timeDeltas` column must be present. timeDeltas?: Milliseconds[]; + argumentValues?: Array; // An optional weight array. If not present, then the weight is assumed to be 1. // See the WeightType type for more information. weight: null | number[]; @@ -164,6 +165,7 @@ export type UnbalancedNativeAllocationsTable = { weight: Bytes[]; weightType: 'bytes'; stack: Array; + argumentValues?: Array; length: number; }; @@ -503,6 +505,7 @@ export type RawCounterSamplesTable = { number?: number[]; // The count of the data, for instance for memory this would be bytes. count: number[]; + argumentValues?: Array; length: number; }; @@ -670,6 +673,12 @@ export type RawThread = { // It's absent in Firefox 97 and before, or in Firefox 98+ when this thread // had no extra attribute at all. userContextId?: number; + tracedValuesBuffer?: string; + tracedObjectShapes?: Array; + // If present, contains the list of innerWindowIDs for pages that this thread is + // related to (or, in practice, whose code may be executing in this thread). + // It's absent in profiles that don't use inner window IDs. + usedInnerWindowIDs?: InnerWindowID[]; }; export type ExtensionTable = { diff --git a/src/utils/base64.ts b/src/utils/base64.ts index 31571311ec..4dbff80a3a 100644 --- a/src/utils/base64.ts +++ b/src/utils/base64.ts @@ -25,3 +25,21 @@ export async function dataUrlToBytes(dataUrl: string): Promise { const res = await fetch(dataUrl); return res.arrayBuffer(); } + +function base64StringToBytesFallback(base64: string): ArrayBuffer { + const binaryString = atob(base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes.buffer; +} + +export function base64StringToBytes(base64: string): ArrayBuffer { + if ('fromBase64' in Uint8Array) { + // @ts-expect-error Uint8Array.fromBase64 is a relatively new API + return Uint8Array.fromBase64(base64).buffer; + } + + return base64StringToBytesFallback(base64); +} diff --git a/src/utils/dark-mode.ts b/src/utils/dark-mode.ts index 80f3d2d811..240bca12ea 100644 --- a/src/utils/dark-mode.ts +++ b/src/utils/dark-mode.ts @@ -1,23 +1,88 @@ +export type ThemePreference = 'system' | 'light' | 'dark'; + let _isDarkModeSetup = false; let _isDarkMode = false; -export function isDarkMode() { +export function getSystemTheme(): 'light' | 'dark' | null { + if (typeof window === 'undefined') { + return null; + } + return window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light'; +} + +export function getThemePreference(): ThemePreference { + try { + const stored = window.localStorage.getItem('theme'); + if (stored === 'light') { + return 'light'; + } + if (stored === 'dark') { + return 'dark'; + } + return 'system'; + } catch { + return 'system'; + } +} + +function _applyTheme(): void { + const preference = getThemePreference(); + let shouldBeDark = false; + + if (preference === 'dark') { + shouldBeDark = true; + } else if (preference === 'light') { + shouldBeDark = false; + } else { + // System preference + shouldBeDark = getSystemTheme() === 'dark'; + } + + _isDarkMode = shouldBeDark; + + if (shouldBeDark) { + document.documentElement.classList.add('dark-mode'); + } else { + document.documentElement.classList.remove('dark-mode'); + } +} + +export function setThemePreference(pref: ThemePreference): void { + try { + if (pref === 'system') { + window.localStorage.removeItem('theme'); + } else { + window.localStorage.setItem('theme', pref); + } + } catch (e) { + console.warn('localStorage access denied', e); + } + _applyTheme(); +} + +export function isDarkMode(): boolean { if (!_isDarkModeSetup) { try { - function readSetting() { - const theme = window.localStorage.getItem('theme'); - if (theme === 'dark') { - _isDarkMode = true; - } else { - _isDarkMode = false; - } - } - readSetting(); + _applyTheme(); + + // Listen for localStorage changes from other tabs window.addEventListener('storage', (event: StorageEvent) => { - if (event.key === 'theme') { - readSetting(); + if (event.key === 'theme' || event.key === null) { + _applyTheme(); } }); + + // Listen for system preference changes + window + .matchMedia('(prefers-color-scheme: dark)') + .addEventListener('change', () => { + // Only re-apply if user is using system preference + if (getThemePreference() === 'system') { + _applyTheme(); + } + }); } catch (e) { console.warn('localStorage access denied', e); } @@ -39,21 +104,15 @@ export function maybeLightDark(value: string | [string, string]): string { } export function initTheme() { - if (isDarkMode()) { - document.documentElement.classList.add('dark-mode'); - } + isDarkMode(); } export function setDarkMode() { - _isDarkMode = true; - window.localStorage.setItem('theme', 'dark'); - document.documentElement.classList.add('dark-mode'); + setThemePreference('dark'); } export function setLightMode() { - _isDarkMode = false; - window.localStorage.removeItem('theme'); - document.documentElement.classList.remove('dark-mode'); + setThemePreference('light'); } export function resetForTest() { diff --git a/src/utils/window-console.ts b/src/utils/window-console.ts index e9f77877a7..12ad179482 100644 --- a/src/utils/window-console.ts +++ b/src/utils/window-console.ts @@ -16,10 +16,10 @@ import { shortenUrl } from 'firefox-profiler/utils/shorten-url'; import { createBrowserConnection } from 'firefox-profiler/app-logic/browser-connection'; import { formatTimestamp } from 'firefox-profiler/utils/format-numbers'; import { togglePseudoStrategy } from 'firefox-profiler/components/app/AppLocalizationProvider'; +import type { ThemePreference } from 'firefox-profiler/utils/dark-mode'; import { - isDarkMode, - setDarkMode, - setLightMode, + getThemePreference, + setThemePreference, } from 'firefox-profiler/utils/dark-mode'; import type { CallTree } from 'firefox-profiler/profile-logic/call-tree'; @@ -200,17 +200,23 @@ export function addDataToWindowObject( }; target.toggleDarkMode = function () { - if (isDarkMode()) { - setLightMode(); - console.log(stripIndent` - ✅ Light mode is now enabled. - `); + const current = getThemePreference(); + let next: ThemePreference; + let message: string; + + if (current === 'system') { + next = 'light'; + message = '✅ Theme set to: light'; + } else if (current === 'light') { + next = 'dark'; + message = '✅ Theme set to: dark'; } else { - setDarkMode(); - console.log(stripIndent` - ✅ Dark mode is now enabled. - `); + next = 'system'; + message = '✅ Theme set to: system (follows OS preference)'; } + + setThemePreference(next); + console.log(message); }; target.retrieveRawProfileDataFromBrowser = async function (): Promise< @@ -412,7 +418,7 @@ export function logFriendlyPreamble() { %cwindow.experimental%c - The object that holds flags of all the experimental features. %cwindow.togglePseudoLocalization%c - Enable pseudo localizations by passing "accented" or "bidi" to this function, or disable using no parameters. %cwindow.toggleTimelineType%c - Toggle timeline graph type by passing "cpu-category", "category", or "stack". - %cwindow.toggleDarkMode%c - Toggle between dark mode and light mode. + %cwindow.toggleDarkMode%c - Cycle through theme preferences: system, light, dark. %cwindow.retrieveRawProfileDataFromBrowser%c - Retrieve the profile attached to the current tab and returns it. Use "await" to call it, and use saveToDisk to save it. %cwindow.extractGeckoLogs%c - Retrieve recorded logs in the current range, using the MOZ_LOG format. Use with "copy" or "saveToDisk". %cwindow.saveToDisk%c - Saves to a file the parameter passed to it, with an optional filename parameter. You can use that to save the profile returned by "retrieveRawProfileDataFromBrowser" or the data returned by "extractGeckoLogs". diff --git a/webpack.config.js b/webpack.config.js index 274766717a..d42c0cceab 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -58,6 +58,7 @@ const config = { path.join(__dirname, 'node_modules', 'photon-colors'), path.join(__dirname, 'node_modules', 'react-splitter-layout'), path.join(__dirname, 'node_modules', 'iongraph-web'), + path.join(__dirname, 'node_modules', 'devtools-reps'), ], }, { diff --git a/yarn.lock b/yarn.lock index bb95e2c2d4..5eeaeefdc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1131,10 +1131,10 @@ "@codemirror/view" "^6.0.0" crelt "^1.0.5" -"@codemirror/state@^6.0.0", "@codemirror/state@^6.5.0", "@codemirror/state@^6.5.3": - version "6.5.3" - resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.5.3.tgz#256e256d466f49ed0879d462031de8bd541e1403" - integrity sha512-MerMzJzlXogk2fxWFU1nKp36bY5orBG59HnPiz0G9nLRebWa0zXuv2siH6PLIHBvv5TH8CkQRqjBs0MlxCZu+A== +"@codemirror/state@^6.0.0", "@codemirror/state@^6.5.0", "@codemirror/state@^6.5.4": + version "6.5.4" + resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.5.4.tgz#f5be4b8c0d2310180d5f15a9f641c21ca69faf19" + integrity sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw== dependencies: "@marijn/find-cluster-break" "^1.0.0" @@ -1238,19 +1238,14 @@ resolved "https://registry.yarnpkg.com/@epic-web/invariant/-/invariant-1.0.0.tgz#1073e5dee6dd540410784990eb73e4acd25c9813" integrity sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA== -"@eslint-community/eslint-utils@^4.7.0", "@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": +"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== - -"@eslint-community/regexpp@^4.12.2": +"@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2": version "4.12.2" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== @@ -2172,10 +2167,10 @@ picocolors "^1.1.1" redent "^3.0.0" -"@testing-library/react@^16.3.1": - version "16.3.1" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.1.tgz#60a9f1f6a930399d9e41b506a8bf68dbf4831fe8" - integrity sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw== +"@testing-library/react@^16.3.2": + version "16.3.2" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.2.tgz#672883b7acb8e775fc0492d9e9d25e06e89786d0" + integrity sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g== dependencies: "@babel/runtime" "^7.12.5" @@ -2650,196 +2645,100 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@8.53.1": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz#f6640f6f8749b71d9ab457263939e8932a3c6b46" - integrity sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag== +"@typescript-eslint/eslint-plugin@8.54.0", "@typescript-eslint/eslint-plugin@^8.51.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz#d8899e5c2eccf5c4a20d01c036a193753748454d" + integrity sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ== dependencies: "@eslint-community/regexpp" "^4.12.2" - "@typescript-eslint/scope-manager" "8.53.1" - "@typescript-eslint/type-utils" "8.53.1" - "@typescript-eslint/utils" "8.53.1" - "@typescript-eslint/visitor-keys" "8.53.1" + "@typescript-eslint/scope-manager" "8.54.0" + "@typescript-eslint/type-utils" "8.54.0" + "@typescript-eslint/utils" "8.54.0" + "@typescript-eslint/visitor-keys" "8.54.0" ignore "^7.0.5" natural-compare "^1.4.0" ts-api-utils "^2.4.0" -"@typescript-eslint/eslint-plugin@^8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz#8985230730c0d955bf6aa0aed98c5c2c95102e1a" - integrity sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.51.0" - "@typescript-eslint/type-utils" "8.51.0" - "@typescript-eslint/utils" "8.51.0" - "@typescript-eslint/visitor-keys" "8.51.0" - ignore "^7.0.0" - natural-compare "^1.4.0" - ts-api-utils "^2.2.0" - -"@typescript-eslint/parser@8.53.1": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.53.1.tgz#58d4a70cc2daee2becf7d4521d65ea1782d6ec68" - integrity sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg== +"@typescript-eslint/parser@8.54.0", "@typescript-eslint/parser@^8.51.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.54.0.tgz#3d01a6f54ed247deb9982621f70e7abf1810bd97" + integrity sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA== dependencies: - "@typescript-eslint/scope-manager" "8.53.1" - "@typescript-eslint/types" "8.53.1" - "@typescript-eslint/typescript-estree" "8.53.1" - "@typescript-eslint/visitor-keys" "8.53.1" + "@typescript-eslint/scope-manager" "8.54.0" + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/typescript-estree" "8.54.0" + "@typescript-eslint/visitor-keys" "8.54.0" debug "^4.4.3" -"@typescript-eslint/parser@^8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.51.0.tgz#584fb8be3a867cbf980917aabed5f7528f615d6b" - integrity sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A== - dependencies: - "@typescript-eslint/scope-manager" "8.51.0" - "@typescript-eslint/types" "8.51.0" - "@typescript-eslint/typescript-estree" "8.51.0" - "@typescript-eslint/visitor-keys" "8.51.0" - debug "^4.3.4" - -"@typescript-eslint/project-service@8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.51.0.tgz#3cfef313d8bebbf4b2442675a4dd463cef4c8369" - integrity sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ== - dependencies: - "@typescript-eslint/tsconfig-utils" "^8.51.0" - "@typescript-eslint/types" "^8.51.0" - debug "^4.3.4" - -"@typescript-eslint/project-service@8.53.1": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.53.1.tgz#4e47856a0b14a1ceb28b0294b4badef3be1e9734" - integrity sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog== +"@typescript-eslint/project-service@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.54.0.tgz#f582aceb3d752544c8e1b11fea8d95d00cf9adc6" + integrity sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.53.1" - "@typescript-eslint/types" "^8.53.1" + "@typescript-eslint/tsconfig-utils" "^8.54.0" + "@typescript-eslint/types" "^8.54.0" debug "^4.4.3" -"@typescript-eslint/scope-manager@8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz#19b42f65680c21f7b6f40fe9024327f6bb1893c1" - integrity sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA== +"@typescript-eslint/scope-manager@8.54.0", "@typescript-eslint/scope-manager@^8.51.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz#307dc8cbd80157e2772c2d36216857415a71ab33" + integrity sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg== dependencies: - "@typescript-eslint/types" "8.51.0" - "@typescript-eslint/visitor-keys" "8.51.0" + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/visitor-keys" "8.54.0" -"@typescript-eslint/scope-manager@8.53.1", "@typescript-eslint/scope-manager@^8.51.0": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz#6c4b8c82cd45ae3b365afc2373636e166743a8fa" - integrity sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ== - dependencies: - "@typescript-eslint/types" "8.53.1" - "@typescript-eslint/visitor-keys" "8.53.1" - -"@typescript-eslint/tsconfig-utils@8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz#a575e9885e62dbd260fb64474eff1dae6e317515" - integrity sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw== - -"@typescript-eslint/tsconfig-utils@8.53.1", "@typescript-eslint/tsconfig-utils@^8.51.0", "@typescript-eslint/tsconfig-utils@^8.53.1": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz#efe80b8d019cd49e5a1cf46c2eb0cd2733076424" - integrity sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA== - -"@typescript-eslint/type-utils@8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz#ec165b0312a6025c2a2a3f39641e46ab4f049564" - integrity sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q== - dependencies: - "@typescript-eslint/types" "8.51.0" - "@typescript-eslint/typescript-estree" "8.51.0" - "@typescript-eslint/utils" "8.51.0" - debug "^4.3.4" - ts-api-utils "^2.2.0" +"@typescript-eslint/tsconfig-utils@8.54.0", "@typescript-eslint/tsconfig-utils@^8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz#71dd7ba1674bd48b172fc4c85b2f734b0eae3dbc" + integrity sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw== -"@typescript-eslint/type-utils@8.53.1": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz#95de2651a96d580bf5c6c6089ddd694284d558ad" - integrity sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w== +"@typescript-eslint/type-utils@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz#64965317dd4118346c2fa5ee94492892200e9fb9" + integrity sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA== dependencies: - "@typescript-eslint/types" "8.53.1" - "@typescript-eslint/typescript-estree" "8.53.1" - "@typescript-eslint/utils" "8.53.1" + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/typescript-estree" "8.54.0" + "@typescript-eslint/utils" "8.54.0" debug "^4.4.3" ts-api-utils "^2.4.0" -"@typescript-eslint/types@8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.51.0.tgz#6996e59d49e92fb893531bdc249f0d92a7bebdbb" - integrity sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag== - -"@typescript-eslint/types@8.53.1", "@typescript-eslint/types@^8.51.0", "@typescript-eslint/types@^8.53.1": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.53.1.tgz#101f203f0807a63216cceceedb815fabe21d5793" - integrity sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A== - -"@typescript-eslint/typescript-estree@8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz#b57f5157d1ac2127bd7c2c9ad8060fa017df4a1a" - integrity sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng== - dependencies: - "@typescript-eslint/project-service" "8.51.0" - "@typescript-eslint/tsconfig-utils" "8.51.0" - "@typescript-eslint/types" "8.51.0" - "@typescript-eslint/visitor-keys" "8.51.0" - debug "^4.3.4" - minimatch "^9.0.4" - semver "^7.6.0" - tinyglobby "^0.2.15" - ts-api-utils "^2.2.0" +"@typescript-eslint/types@8.54.0", "@typescript-eslint/types@^8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.54.0.tgz#c12d41f67a2e15a8a96fbc5f2d07b17331130889" + integrity sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA== -"@typescript-eslint/typescript-estree@8.53.1": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz#b6dce2303c9e27e95b8dcd8c325868fff53e488f" - integrity sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg== +"@typescript-eslint/typescript-estree@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz#3c7716905b2b811fadbd2114804047d1bfc86527" + integrity sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA== dependencies: - "@typescript-eslint/project-service" "8.53.1" - "@typescript-eslint/tsconfig-utils" "8.53.1" - "@typescript-eslint/types" "8.53.1" - "@typescript-eslint/visitor-keys" "8.53.1" + "@typescript-eslint/project-service" "8.54.0" + "@typescript-eslint/tsconfig-utils" "8.54.0" + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/visitor-keys" "8.54.0" debug "^4.4.3" minimatch "^9.0.5" semver "^7.7.3" tinyglobby "^0.2.15" ts-api-utils "^2.4.0" -"@typescript-eslint/utils@8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.51.0.tgz#b9a071cd210647f860a38873acf9bc5157bea56a" - integrity sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA== - dependencies: - "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/scope-manager" "8.51.0" - "@typescript-eslint/types" "8.51.0" - "@typescript-eslint/typescript-estree" "8.51.0" - -"@typescript-eslint/utils@8.53.1", "@typescript-eslint/utils@^8.0.0", "@typescript-eslint/utils@^8.51.0": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.53.1.tgz#81fe6c343de288701b774f4d078382f567e6edaa" - integrity sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg== +"@typescript-eslint/utils@8.54.0", "@typescript-eslint/utils@^8.0.0", "@typescript-eslint/utils@^8.51.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.54.0.tgz#c79a4bcbeebb4f571278c0183ed1cb601d84c6c8" + integrity sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA== dependencies: "@eslint-community/eslint-utils" "^4.9.1" - "@typescript-eslint/scope-manager" "8.53.1" - "@typescript-eslint/types" "8.53.1" - "@typescript-eslint/typescript-estree" "8.53.1" + "@typescript-eslint/scope-manager" "8.54.0" + "@typescript-eslint/types" "8.54.0" + "@typescript-eslint/typescript-estree" "8.54.0" -"@typescript-eslint/visitor-keys@8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz#d37f5c82b9bece2c8aeb3ba7bb836bbba0f92bb8" - integrity sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg== +"@typescript-eslint/visitor-keys@8.54.0": + version "8.54.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz#0e4b50124b210b8600b245dd66cbad52deb15590" + integrity sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA== dependencies: - "@typescript-eslint/types" "8.51.0" - eslint-visitor-keys "^4.2.1" - -"@typescript-eslint/visitor-keys@8.53.1": - version "8.53.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz#405f04959be22b9be364939af8ac19c3649b6eb7" - integrity sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg== - dependencies: - "@typescript-eslint/types" "8.53.1" + "@typescript-eslint/types" "8.54.0" eslint-visitor-keys "^4.2.1" "@ungap/structured-clone@^1.3.0": @@ -4834,6 +4733,16 @@ devtools-license-check@^0.9.0: dependencies: license-checker "^9.0.3" +devtools-reps@^0.27.3: + version "0.27.3" + resolved "https://registry.yarnpkg.com/devtools-reps/-/devtools-reps-0.27.3.tgz#a41cb6c1c5b4aed5d0bdcac7505b0e3e6ad41526" + integrity sha512-bG3kr0jOvYqx0xAyTvkq/w5cKU6aJm3J53gKetomgFZYrsZiW7Z7U2APHD9uylj2EZ5OrJZaovHkxDRqHqb01g== + dependencies: + prop-types "^15.7.2" + react "^16.8.6" + react-dom "^16.8.6" + react-dom-factories "^1.0.2" + dezalgo@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" @@ -6459,10 +6368,10 @@ html-tags@^5.1.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-5.1.0.tgz#ec7214b57b3e50e2a4cec39414454338a94291f8" integrity sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ== -html-webpack-plugin@^5.6.5: - version "5.6.5" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.5.tgz#d57defb83cabbf29bf56b2d4bf10b67b650066be" - integrity sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g== +html-webpack-plugin@^5.6.6: + version "5.6.6" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz#5321b9579f4a1949318550ced99c2a4a4e60cbaf" + integrity sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw== dependencies: "@types/html-minifier-terser" "^6.0.0" html-minifier-terser "^6.0.2" @@ -6667,7 +6576,7 @@ ignore@^5.0.0, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -ignore@^7.0.0, ignore@^7.0.5: +ignore@^7.0.5: version "7.0.5" resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== @@ -8221,10 +8130,10 @@ lockfile-lint-api@^5.9.2: debug "^4.3.4" object-hash "^3.0.0" -lockfile-lint@^4.14.1: - version "4.14.1" - resolved "https://registry.yarnpkg.com/lockfile-lint/-/lockfile-lint-4.14.1.tgz#3391ea68cf50c1a478ce113913c4c778768a4b9e" - integrity sha512-NW0Tk1qfldhbhJWQENYQWANdmlanXKxvTJYRYKn56INYjaP2M07Ua2SJYkUMS+ZbYwxDzul/C6pDsV/NEXrl+A== +lockfile-lint@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/lockfile-lint/-/lockfile-lint-5.0.0.tgz#052a11c150e328bb5fb548b071ebcd2df70614bc" + integrity sha512-QcVIVITLZAhWYHU2wbNSOMgwc6EN4Y2sy6mjgS5aikYyRzgDIfotXUsCrm38En+3fZpc58Yu7DF9dNeT/goi1A== dependencies: cosmiconfig "^9.0.0" debug "^4.3.4" @@ -10431,10 +10340,10 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@^3.7.4: - version "3.7.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.7.4.tgz#d2f8335d4b1cec47e1c8098645411b0c9dff9c0f" - integrity sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA== +prettier@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" + integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== pretty-bytes@^5.3.0: version "5.6.0" @@ -10482,7 +10391,7 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -10663,6 +10572,21 @@ rc@1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-dom-factories@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/react-dom-factories/-/react-dom-factories-1.0.2.tgz#eb7705c4db36fb501b3aa38ff759616aa0ff96e0" + integrity sha512-Bmic2N3oKji7vw9qjDr2dmwHvOATbFSnKy7EH0uT/qjvzIUsiXp6Yquk72LJ3WfMtRnq3ujXMMo7GsJeLPfFWw== + +react-dom@^16.8.6: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89" + integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + react-dom@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -10671,10 +10595,10 @@ react-dom@^18.3.1: loose-envify "^1.1.0" scheduler "^0.23.2" -react-intersection-observer@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-10.0.0.tgz#77613d4e819dd170fbac4821c126c046aab69978" - integrity sha512-JJRgcnFQoVXmbE5+GXr1OS1NDD1gHk0HyfpLcRf0575IbJz+io8yzs4mWVlfaqOQq1FiVjLvuYAdEEcrrCfveg== +react-intersection-observer@^10.0.2: + version "10.0.2" + resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-10.0.2.tgz#de63a87859f9b59f8e46ec29092d77ff69e2d20b" + integrity sha512-lAMzxVWrBko6SLd1jx6l84fVrzJu91hpxHlvD2as2Wec9mDCjdYXwc5xNOFBchpeBir0Y7AGBW+C/AYMa7CSFg== react-is@^16.13.1: version "16.13.1" @@ -10714,6 +10638,15 @@ react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" +react@^16.8.6: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + react@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" @@ -11336,6 +11269,14 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler@^0.23.2: version "0.23.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" @@ -11839,16 +11780,7 @@ string-length@^4.0.2: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11978,7 +11910,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11992,13 +11924,6 @@ strip-ansi@^0.3.0: dependencies: ansi-regex "^0.2.1" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -12444,7 +12369,7 @@ trough@^2.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-2.0.2.tgz#94a3aa9d5ce379fc561f6244905b3f36b7458d96" integrity sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w== -ts-api-utils@^2.2.0, ts-api-utils@^2.4.0: +ts-api-utils@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== @@ -12608,15 +12533,15 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript-eslint@^8.52.0: - version "8.53.1" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.53.1.tgz#e8d2888083af4638d2952b938d69458f54865921" - integrity sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg== +typescript-eslint@^8.54.0: + version "8.54.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.54.0.tgz#f4ef3b8882a5ddc2a41968e014194c178ab23f6a" + integrity sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ== dependencies: - "@typescript-eslint/eslint-plugin" "8.53.1" - "@typescript-eslint/parser" "8.53.1" - "@typescript-eslint/typescript-estree" "8.53.1" - "@typescript-eslint/utils" "8.53.1" + "@typescript-eslint/eslint-plugin" "8.54.0" + "@typescript-eslint/parser" "8.54.0" + "@typescript-eslint/typescript-estree" "8.54.0" + "@typescript-eslint/utils" "8.54.0" typescript@^5.9.3: version "5.9.3" @@ -13587,16 +13512,7 @@ workbox-window@7.4.0, workbox-window@^7.4.0: "@types/trusted-types" "^2.0.2" workbox-core "7.4.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==