From 5cc87fa64f3b1a96e9890e091ffbe876efdec014 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Wed, 4 Feb 2026 16:50:38 +0000 Subject: [PATCH 01/30] Pontoon/Firefox Profiler: Update 20 localizations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Pin-guang Chen (zh-TW) Co-authored-by: Mark Heijl (nl) Co-authored-by: Nazım Can Altınova (de, tr) Co-authored-by: Andreas Pettersson (sv-SE) Co-authored-by: Francesco Lodolo [:flod] (it) --- locales/tr/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/tr/app.ftl b/locales/tr/app.ftl index 3db38bbc3d..488638f5cf 100644 --- a/locales/tr/app.ftl +++ b/locales/tr/app.ftl @@ -472,6 +472,8 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = Sınırsız MenuButtons--metaInfo--application = Uygulama MenuButtons--metaInfo--name-and-version = Ad ve sürüm: +# The time between application startup and when the profiler was started +MenuButtons--metaInfo--application-uptime2 = Çalışma süresi: MenuButtons--metaInfo--update-channel = Güncelleme kanalı: MenuButtons--metaInfo--build-id = Yapı kimliği: MenuButtons--metaInfo--build-type = Yapı tipi: From 6dc30839001f5cf51bb79e2ad72de184c475cf78 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Wed, 4 Feb 2026 17:00:32 +0000 Subject: [PATCH 02/30] Pontoon/Firefox Profiler: Update Turkish (tr), Italian (it) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nazım Can Altınova (tr) Co-authored-by: Francesco Lodolo [:flod] (it) --- locales/it/app.ftl | 10 ++++++++++ locales/tr/app.ftl | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/locales/it/app.ftl b/locales/it/app.ftl index 4a9763e083..f0c30fd84e 100644 --- a/locales/it/app.ftl +++ b/locales/it/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Wed, 4 Feb 2026 17:10:31 +0000 Subject: [PATCH 03/30] Pontoon/Firefox Profiler: Update Italian (it) Co-authored-by: Francesco Lodolo [:flod] (it) --- locales/it/app.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/it/app.ftl b/locales/it/app.ftl index f0c30fd84e..f0d95939cf 100644 --- a/locales/it/app.ftl +++ b/locales/it/app.ftl @@ -25,7 +25,7 @@ AppHeader--github-icon = ## They are used at the top right side of the home page to switch between themes. ThemeToggle--system = - .title = Segui le preferenze del tema di sistema + .title = Segui le impostazioni del tema di sistema ThemeToggle--light = .title = Usa tema chiaro ThemeToggle--dark = From 7d066e6dbdde355d06f351b3f31f0e8bd44838cd Mon Sep 17 00:00:00 2001 From: Pontoon Date: Wed, 4 Feb 2026 17:20:34 +0000 Subject: [PATCH 04/30] Pontoon/Firefox Profiler: Update English (Great Britain) (en-GB) Co-authored-by: Ian Neal (en-GB) --- locales/en-GB/app.ftl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/locales/en-GB/app.ftl b/locales/en-GB/app.ftl index f8e82fb3dd..3cc3c91c87 100644 --- a/locales/en-GB/app.ftl +++ b/locales/en-GB/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Wed, 4 Feb 2026 17:40:31 +0000 Subject: [PATCH 05/30] Pontoon/Firefox Profiler: Update Swedish (sv-SE) Co-authored-by: Andreas Pettersson (sv-SE) --- locales/sv-SE/app.ftl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/locales/sv-SE/app.ftl b/locales/sv-SE/app.ftl index e0eb93ba49..01a8526e29 100644 --- a/locales/sv-SE/app.ftl +++ b/locales/sv-SE/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Wed, 4 Feb 2026 18:00:30 +0000 Subject: [PATCH 06/30] Pontoon/Firefox Profiler: Update Interlingua (ia) Co-authored-by: Melo46 (ia) --- locales/ia/app.ftl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/locales/ia/app.ftl b/locales/ia/app.ftl index f8c653b767..d441b59677 100644 --- a/locales/ia/app.ftl +++ b/locales/ia/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Wed, 4 Feb 2026 18:30:36 +0000 Subject: [PATCH 07/30] Pontoon/Firefox Profiler: Update German (de) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michael Köhler (de) --- locales/de/app.ftl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/locales/de/app.ftl b/locales/de/app.ftl index 62e72d124f..c5b209fb1d 100644 --- a/locales/de/app.ftl +++ b/locales/de/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Wed, 4 Feb 2026 20:31:48 +0000 Subject: [PATCH 08/30] Pontoon/Firefox Profiler: Update Russian (ru) Co-authored-by: Valery Ledovskoy (ru) --- locales/ru/app.ftl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/locales/ru/app.ftl b/locales/ru/app.ftl index 038f7f6318..5992ddeb5e 100644 --- a/locales/ru/app.ftl +++ b/locales/ru/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Wed, 4 Feb 2026 22:20:30 +0000 Subject: [PATCH 09/30] Pontoon/Firefox Profiler: Update Turkish (tr) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nazım Can Altınova (tr) --- locales/tr/app.ftl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/locales/tr/app.ftl b/locales/tr/app.ftl index 75fd21eb9a..06a0b82161 100644 --- a/locales/tr/app.ftl +++ b/locales/tr/app.ftl @@ -963,6 +963,10 @@ TransformNavigator--collapse-direct-recursion-only = Yalnızca doğrudan özyine # Variables: # $item (String) - Name of the function that transform applied to. TransformNavigator--collapse-function-subtree = Alt ağacı daralt: { $item } +# "Drop samples outside of markers matching ..." transform. +# Variables: +# $item (String) - Search filter of the markers that transform will apply to. +TransformNavigator--drop-samples-outside-of-markers-matching = Eşleşen işaretçilerin dışındaki örnekleri bırakın: “{ $item }” ## "Bottom box" - a view which contains the source view and the assembly view, ## at the bottom of the profiler UI From 6b72f5108382e9812d78d6ac481ff97aa9050dad Mon Sep 17 00:00:00 2001 From: Pontoon Date: Thu, 5 Feb 2026 05:20:31 +0000 Subject: [PATCH 10/30] Pontoon/Firefox Profiler: Update Chinese (Taiwan) (zh-TW) Co-authored-by: Pin-guang Chen (zh-TW) --- locales/zh-TW/app.ftl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/locales/zh-TW/app.ftl b/locales/zh-TW/app.ftl index 21e0b9e253..9e11f57f32 100644 --- a/locales/zh-TW/app.ftl +++ b/locales/zh-TW/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Thu, 5 Feb 2026 06:50:30 +0000 Subject: [PATCH 11/30] Pontoon/Firefox Profiler: Update Dutch (nl) Co-authored-by: Mark Heijl (nl) --- locales/nl/app.ftl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/locales/nl/app.ftl b/locales/nl/app.ftl index 3712159e45..fdbf1e5080 100644 --- a/locales/nl/app.ftl +++ b/locales/nl/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Thu, 5 Feb 2026 08:40:32 +0000 Subject: [PATCH 12/30] Pontoon/Firefox Profiler: Update Chinese (China) (zh-CN) Co-authored-by: wxie (zh-CN) Co-authored-by: Olvcpr423 (zh-CN) --- locales/zh-CN/app.ftl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/locales/zh-CN/app.ftl b/locales/zh-CN/app.ftl index d9ac652335..c2c8ada65a 100644 --- a/locales/zh-CN/app.ftl +++ b/locales/zh-CN/app.ftl @@ -476,6 +476,8 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = 无限制 MenuButtons--metaInfo--application = 应用程序 MenuButtons--metaInfo--name-and-version = 名称和版本: +# The time between application startup and when the profiler was started +MenuButtons--metaInfo--application-uptime2 = 运行时间: MenuButtons--metaInfo--update-channel = 更新通道: MenuButtons--metaInfo--build-id = 构建 ID: MenuButtons--metaInfo--build-type = 构建类型: @@ -1083,6 +1085,12 @@ AssemblyView--show-button = # Assembly refers to the low-level programming language. AssemblyView--hide-button = .title = 隐藏汇编代码视图 +# The "◀" button above the assembly view. +AssemblyView--prev-button = + .title = 上一个 +# The "▶" button above the assembly view. +AssemblyView--next-button = + .title = 下一个 ## UploadedRecordingsHome ## This is the page that displays all the profiles that user has uploaded. From 6599bc2fca94dea1790ee8f1ea03cc922bdfb4c2 Mon Sep 17 00:00:00 2001 From: Valentin Gosu <1454649+valenting@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:02:12 +0100 Subject: [PATCH 13/30] Make extractGeckoLogs() not fake the D/ log level if the log module already contains it (#5811) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nazım Can Altınova --- src/test/unit/window-console.test.ts | 33 ++++++++++++++++++++++++++++ src/utils/window-console.ts | 8 +++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/test/unit/window-console.test.ts b/src/test/unit/window-console.test.ts index d2e1d84847..82c939f7c3 100644 --- a/src/test/unit/window-console.test.ts +++ b/src/test/unit/window-console.test.ts @@ -79,6 +79,39 @@ describe('console-accessible values on the window object', function () { `); }); + it('can extract gecko logs with log level already in module', function () { + const profile = getProfileWithMarkers([ + [ + 'LogMessages', + 170, + null, + { + type: 'Log', + module: 'D/nsHttp', + name: 'ParentChannelListener::ParentChannelListener [this=7fb5e19b98d0, next=7fb5f48f2320]', + }, + ], + [ + 'LogMessages', + 190, + null, + { + type: 'Log', + name: 'nsJARChannel::nsJARChannel [this=0x87f1ec80]\n', + module: 'D/nsJarProtocol', + }, + ], + ]); + const store = storeWithProfile(profile); + const target: MixedObject = {}; + addDataToWindowObject(store.getState, store.dispatch, target); + const result = (target as any).extractGeckoLogs(); + expect(result).toBe(stripIndent` + 1970-01-01 00:00:00.170000000 UTC - [Unknown Process 0: Empty]: D/nsHttp ParentChannelListener::ParentChannelListener [this=7fb5e19b98d0, next=7fb5f48f2320] + 1970-01-01 00:00:00.190000000 UTC - [Unknown Process 0: Empty]: D/nsJarProtocol nsJARChannel::nsJARChannel [this=0x87f1ec80] + `); + }); + describe('totalMarkerDuration', function () { function setup(): ExtraPropertiesOnWindowForConsole { jest.spyOn(console, 'log').mockImplementation(() => {}); diff --git a/src/utils/window-console.ts b/src/utils/window-console.ts index 12ad179482..0beb4651fd 100644 --- a/src/utils/window-console.ts +++ b/src/utils/window-console.ts @@ -323,8 +323,12 @@ export function addDataToWindowObject( ) { const strTimestamp = d2s(profile.meta.startTime + markerStartTime); const processName = thread.processName ?? 'Unknown Process'; - // TODO: lying about the log level as it's not available yet in the markers - const statement = `${strTimestamp} - [${processName} ${thread.pid}: ${thread.name}]: D/${(data as any).module} ${(data as any).name.trim()}`; + + // The log module may contain the log level for profiles captured after bug 1995503. + // If the log module does not contain /, we fake it to D/module + const logModule = (data as any).module; + const prefix = logModule.includes('/') ? '' : 'D/'; + const statement = `${strTimestamp} - [${processName} ${thread.pid}: ${thread.name}]: ${prefix}${logModule} ${(data as any).name.trim()}`; logs.push(statement); } } From ee910af6a6e0445bf80cfbb099f5e9707e980f93 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Thu, 5 Feb 2026 16:23:12 +0000 Subject: [PATCH 14/30] Pontoon/Firefox Profiler: Update Turkish (tr) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nazım Can Altınova (tr) Co-authored-by: Selim Şumlu (tr) --- locales/tr/app.ftl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/locales/tr/app.ftl b/locales/tr/app.ftl index 06a0b82161..5ebe92b9e4 100644 --- a/locales/tr/app.ftl +++ b/locales/tr/app.ftl @@ -21,6 +21,14 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Thu, 5 Feb 2026 16:30:33 +0000 Subject: [PATCH 15/30] Pontoon/Firefox Profiler: Update Turkish (tr) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Selim Şumlu (tr) --- locales/tr/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/tr/app.ftl b/locales/tr/app.ftl index 5ebe92b9e4..0308633f9b 100644 --- a/locales/tr/app.ftl +++ b/locales/tr/app.ftl @@ -28,6 +28,8 @@ ThemeToggle--system = .title = Sistem temasının tercihine uyum sağla ThemeToggle--light = .title = Açık temayı kullan +ThemeToggle--dark = + .title = Koyu temayı kullan ## AppViewRouter ## This is used for displaying errors when loading the application. From 87e970f7eef1c98a1975f3e232be332047b47b72 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Thu, 5 Feb 2026 17:50:31 +0000 Subject: [PATCH 16/30] Pontoon/Firefox Profiler: Update German (de) Co-authored-by: Ger (de) --- locales/de/app.ftl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de/app.ftl b/locales/de/app.ftl index c5b209fb1d..d0c5c54e61 100644 --- a/locales/de/app.ftl +++ b/locales/de/app.ftl @@ -572,7 +572,7 @@ MenuButtons--metaInfo--buffer-duration-unlimited = Unbegrenzt MenuButtons--metaInfo--application = Anwendung MenuButtons--metaInfo--name-and-version = Name und Version: # The time between application startup and when the profiler was started -MenuButtons--metaInfo--application-uptime2 = Uptime: +MenuButtons--metaInfo--application-uptime2 = Verfügbarkeit seit: MenuButtons--metaInfo--update-channel = Update-Kanal: MenuButtons--metaInfo--build-id = Build-ID: MenuButtons--metaInfo--build-type = Build-Typ: From cef27b1583166359057b31de08e46486cc365f84 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 15 Jan 2026 16:03:27 -0500 Subject: [PATCH 17/30] Make getNativeSymbolsForCallNode return a Set. --- src/profile-logic/bottom-box.ts | 8 +++++--- src/profile-logic/profile-data.ts | 6 +++--- src/test/unit/profile-data.test.ts | 8 +++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/profile-logic/bottom-box.ts b/src/profile-logic/bottom-box.ts index 4519ee5cc1..4ed96d56f4 100644 --- a/src/profile-logic/bottom-box.ts +++ b/src/profile-logic/bottom-box.ts @@ -60,14 +60,16 @@ export function getBottomBoxInfoForCallNode( callNodeFramePerStack, frameTable ); + const nativeSymbolsForCallNodeArr = [...nativeSymbolsForCallNode]; // If we have at least one native symbol to show assembly for, pick // the first one arbitrarily. - // TODO: If the we have more than one native symbol, pick the one + // TODO: If we have more than one native symbol, pick the one // with the highest total sample count. - const initialNativeSymbol = nativeSymbolsForCallNode.length !== 0 ? 0 : null; + const initialNativeSymbol = + nativeSymbolsForCallNodeArr.length !== 0 ? 0 : null; - const nativeSymbolInfosForCallNode = nativeSymbolsForCallNode.map( + const nativeSymbolInfosForCallNode = nativeSymbolsForCallNodeArr.map( (nativeSymbolIndex) => getNativeSymbolInfo( nativeSymbolIndex, diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 6e247fad55..83b252c830 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -4032,8 +4032,8 @@ export function calculateFunctionSizeLowerBound( export function getNativeSymbolsForCallNode( callNodeFramePerStack: Int32Array, frameTable: FrameTable -): IndexIntoNativeSymbolTable[] { - const set: Set = new Set(); +): Set { + const set = new Set(); for ( let stackIndex = 0; stackIndex < callNodeFramePerStack.length; @@ -4047,7 +4047,7 @@ export function getNativeSymbolsForCallNode( } } } - return [...set]; + return set; } /** diff --git a/src/test/unit/profile-data.test.ts b/src/test/unit/profile-data.test.ts index 96b85e6d94..9587a75ad6 100644 --- a/src/test/unit/profile-data.test.ts +++ b/src/test/unit/profile-data.test.ts @@ -1481,7 +1481,7 @@ describe('getNativeSymbolsForCallNode', function () { ); expect( getNativeSymbolsForCallNode(callNodeFramePerStackAB, thread.frameTable) - ).toEqual([symB]); + ).toEqual(new Set([symB])); const callNodeFramePerStackABC = getCallNodeFramePerStack( ensureExists(abc), @@ -1490,7 +1490,7 @@ describe('getNativeSymbolsForCallNode', function () { ); expect( getNativeSymbolsForCallNode(callNodeFramePerStackABC, thread.frameTable) - ).toEqual([symB]); + ).toEqual(new Set([symB])); }); it('finds multiple symbols', function () { @@ -1531,9 +1531,7 @@ describe('getNativeSymbolsForCallNode', function () { thread.stackTable ); expect( - new Set( - getNativeSymbolsForCallNode(callNodeFramePerStackC, thread.frameTable) - ) + getNativeSymbolsForCallNode(callNodeFramePerStackC, thread.frameTable) ).toEqual(new Set([symB, symD])); }); }); From 7ba1db4179b9764545aef8c91572479dfc5bc67a Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 15 Jan 2026 16:03:27 -0500 Subject: [PATCH 18/30] Make initialNativeSymbol an IndexIntoNativeSymbol and go back to the "entry index" only at the end of the function. --- src/profile-logic/bottom-box.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/profile-logic/bottom-box.ts b/src/profile-logic/bottom-box.ts index 4ed96d56f4..714bf497ed 100644 --- a/src/profile-logic/bottom-box.ts +++ b/src/profile-logic/bottom-box.ts @@ -66,8 +66,10 @@ export function getBottomBoxInfoForCallNode( // the first one arbitrarily. // TODO: If we have more than one native symbol, pick the one // with the highest total sample count. - const initialNativeSymbol = - nativeSymbolsForCallNodeArr.length !== 0 ? 0 : null; + let initialNativeSymbol = null; + if (nativeSymbolsForCallNodeArr.length !== 0) { + initialNativeSymbol = nativeSymbolsForCallNodeArr[0]; + } const nativeSymbolInfosForCallNode = nativeSymbolsForCallNodeArr.map( (nativeSymbolIndex) => @@ -93,9 +95,7 @@ export function getBottomBoxInfoForCallNode( samples, callNodeFramePerStack, frameTable, - initialNativeSymbol !== null - ? nativeSymbolsForCallNode[initialNativeSymbol] - : null + initialNativeSymbol ); const hottestInstructionAddress = mapGetKeyWithMaxValue(addressTimings); @@ -103,7 +103,10 @@ export function getBottomBoxInfoForCallNode( libIndex, sourceIndex, nativeSymbols: nativeSymbolInfosForCallNode, - initialNativeSymbol, + initialNativeSymbol: + initialNativeSymbol !== null + ? nativeSymbolsForCallNodeArr.indexOf(initialNativeSymbol) + : null, scrollToLineNumber: hottestLine, scrollToInstructionAddress: hottestInstructionAddress, highlightedLineNumber: null, From 0693702dcf107251e03bb2c77175313bac46589a Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Thu, 15 Jan 2026 16:03:27 -0500 Subject: [PATCH 19/30] When double-clicking a node, display the assembly code for the native symbol with the highest sample count. --- src/profile-logic/bottom-box.ts | 30 +++-- src/profile-logic/profile-data.ts | 40 ++++++ src/test/unit/profile-data.test.ts | 187 +++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+), 7 deletions(-) diff --git a/src/profile-logic/bottom-box.ts b/src/profile-logic/bottom-box.ts index 714bf497ed..a803a24978 100644 --- a/src/profile-logic/bottom-box.ts +++ b/src/profile-logic/bottom-box.ts @@ -16,6 +16,7 @@ import { getCallNodeFramePerStack, getNativeSymbolInfo, getNativeSymbolsForCallNode, + getTotalNativeSymbolTimingsForCallNode, } from './profile-data'; import { mapGetKeyWithMaxValue } from 'firefox-profiler/utils'; import { getTotalLineTimingsForCallNode } from './line-timings'; @@ -56,18 +57,33 @@ export function getBottomBoxInfoForCallNode( callNodeInfo, stackTable ); + + // If we have at least one native symbol to show assembly for, pick + // the one with the highest total. But first, create the full list of + // native symbols for this call node, including even those symbols + // that aren't hit by any samples in the current view, so that the + // list is stable regardless of the current preview selection. const nativeSymbolsForCallNode = getNativeSymbolsForCallNode( callNodeFramePerStack, frameTable ); - const nativeSymbolsForCallNodeArr = [...nativeSymbolsForCallNode]; - - // If we have at least one native symbol to show assembly for, pick - // the first one arbitrarily. - // TODO: If we have more than one native symbol, pick the one - // with the highest total sample count. let initialNativeSymbol = null; - if (nativeSymbolsForCallNodeArr.length !== 0) { + const nativeSymbolTimings = getTotalNativeSymbolTimingsForCallNode( + samples, + callNodeFramePerStack, + frameTable + ); + const hottestNativeSymbol = mapGetKeyWithMaxValue(nativeSymbolTimings); + if (hottestNativeSymbol !== undefined) { + nativeSymbolsForCallNode.add(hottestNativeSymbol); + initialNativeSymbol = hottestNativeSymbol; + } + const nativeSymbolsForCallNodeArr = [...nativeSymbolsForCallNode]; + nativeSymbolsForCallNodeArr.sort((a, b) => a - b); + if ( + nativeSymbolsForCallNodeArr.length !== 0 && + initialNativeSymbol === null + ) { initialNativeSymbol = nativeSymbolsForCallNodeArr[0]; } diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index 83b252c830..ceb60a8e11 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -4050,6 +4050,46 @@ export function getNativeSymbolsForCallNode( return set; } +/** + * Return the total of the sample weights per native symbol, by + * accumulating the weight from samples which contribute to the + * call node of interest's total time. + * callNodeFramePerStack needs to be a mapping from stackIndex to the + * corresponding frame in the call node of interest. + */ +export function getTotalNativeSymbolTimingsForCallNode( + samples: SamplesLikeTable, + callNodeFramePerStack: Int32Array, + frameTable: FrameTable +): Map { + const totalPerNativeSymbol = new Map(); + for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex++) { + const stack = samples.stack[sampleIndex]; + if (stack === null) { + continue; + } + const callNodeFrame = callNodeFramePerStack[stack]; + if (callNodeFrame === -1) { + // This sample does not contribute to the call node's total. Ignore. + continue; + } + + const nativeSymbol = frameTable.nativeSymbol[callNodeFrame]; + if (nativeSymbol === null) { + continue; + } + + const sampleWeight = + samples.weight !== null ? samples.weight[sampleIndex] : 1; + totalPerNativeSymbol.set( + nativeSymbol, + (totalPerNativeSymbol.get(nativeSymbol) ?? 0) + sampleWeight + ); + } + + return totalPerNativeSymbol; +} + /** * Convert a native symbol index into a NativeSymbolInfo object, to create * something that's meaningful outside of its associated thread. diff --git a/src/test/unit/profile-data.test.ts b/src/test/unit/profile-data.test.ts index 9587a75ad6..e28ce2de6d 100644 --- a/src/test/unit/profile-data.test.ts +++ b/src/test/unit/profile-data.test.ts @@ -26,6 +26,7 @@ import { getNativeSymbolInfo, computeTimeColumnForRawSamplesTable, getCallNodeFramePerStack, + getTotalNativeSymbolTimingsForCallNode, } from '../../profile-logic/profile-data'; import { resourceTypes } from '../../profile-logic/data-structures'; import { @@ -60,6 +61,8 @@ import type { RawProfileSharedData, IndexIntoFrameTable, IndexIntoSourceTable, + IndexIntoCategoryList, + IndexIntoNativeSymbolTable, } from 'firefox-profiler/types'; describe('string-table', function () { @@ -1536,6 +1539,190 @@ describe('getNativeSymbolsForCallNode', function () { }); }); +describe('getTotalNativeSymbolTimingsForCallNode', function () { + function getTimings( + thread: Thread, + callNodePath: CallNodePath, + defaultCategory: IndexIntoCategoryList, + isInverted: boolean + ): Map { + const { stackTable, frameTable, funcTable, samples } = thread; + const nonInvertedCallNodeInfo = getCallNodeInfo( + stackTable, + frameTable, + defaultCategory + ); + const callNodeInfo = isInverted + ? getInvertedCallNodeInfo( + nonInvertedCallNodeInfo, + defaultCategory, + funcTable.length + ) + : nonInvertedCallNodeInfo; + const callNodeIndex = ensureExists( + callNodeInfo.getCallNodeIndexFromPath(callNodePath), + 'invalid call node path' + ); + const callNodeFramePerStack = getCallNodeFramePerStack( + callNodeIndex, + callNodeInfo, + stackTable + ); + return getTotalNativeSymbolTimingsForCallNode( + samples, + callNodeFramePerStack, + frameTable + ); + } + + it('passes a basic test', function () { + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:file][sym:Asym:20:] + B[lib:file][sym:Bsym:30:] + `); + const [{ A, B }] = funcNamesDictPerThread; + const [{ Asym, Bsym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + + // Compute the timings for the root call node. + // One total hit at symbol Asym. + const timingsRoot = getTimings(thread, [A], defaultCategory, false); + expect(timingsRoot.get(Asym)).toBe(1); + expect(timingsRoot.size).toBe(1); // no other hits + + // Compute the timings for the child call node. + // One total hit at symbol Bsym. + const timingsChild = getTimings(thread, [A, B], defaultCategory, false); + expect(timingsChild.get(Bsym)).toBe(1); + expect(timingsChild.size).toBe(1); // no other hits + }); + + it('passes a basic test with recursion', function () { + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:file][sym:Asym:20:] + B[lib:file][sym:Bsym:30:] + A[lib:file][sym:A2sym:40:] + `); + + const [{ A, B }] = funcNamesDictPerThread; + const [{ Asym, A2sym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + + // Compute the timings for the root call node. + // One total hit at symbol Asym. + const timingsRoot = getTimings(thread, [A], defaultCategory, false); + expect(timingsRoot.get(Asym)).toBe(1); + expect(timingsRoot.size).toBe(1); // no other hits + + // Compute the timings for the leaf call node. + // One total hit at symbol A2sym. + // In particular, we shouldn't record a hit for symbol Asym, even though + // the frame in Asym is also in A. But it's in the wrong call node. + const timingsChild = getTimings(thread, [A, B, A], defaultCategory, false); + expect(timingsChild.get(A2sym)).toBe(1); + expect(timingsChild.size).toBe(1); // no other hits + }); + + it('passes a test where the same function is called via different call paths', function () { + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] + B[lib:one][sym:Bsym:30:] D[lib:one][sym:Dsym:40:] B[lib:one][sym:Bsym:30:] + C[lib:two][sym:Csym:10:] C[lib:two][sym:C2sym:50:] C[lib:two][sym:C3sym:60:] + D[lib:one][sym:Dsym:40:] + `); + + const [{ A, B, C }] = funcNamesDictPerThread; + const [{ Csym, C3sym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + + const timingsABC = getTimings(thread, [A, B, C], defaultCategory, false); + expect(timingsABC.get(Csym)).toBe(1); + expect(timingsABC.get(C3sym)).toBe(1); + expect(timingsABC.size).toBe(2); // no other hits + }); + + it('passes a test with an inverted thread', function () { + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] + B[lib:one][sym:Bsym:30:] D[lib:one][sym:Dsym:40:] B[lib:one][sym:Bsym:30:] + D[lib:one][sym:Dsym:40:] D[lib:one][sym:D2sym:50:] C[lib:two][sym:Csym:10:] + D[lib:one][sym:Dsym:40:] + `); + + const [{ C, D }] = funcNamesDictPerThread; + const [{ Csym, Dsym, D2sym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + // For the root D of the inverted tree, we have 3 native symbol hits. + const timingsD = getTimings(thread, [D], defaultCategory, true); + expect(timingsD.get(Dsym)).toBe(2); + expect(timingsD.get(D2sym)).toBe(1); + expect(timingsD.size).toBe(2); // no other hits + + // For the C call node which is a child (direct caller) of D, we have + // one hit at symbol Csym. + const timingsDC = getTimings(thread, [D, C], defaultCategory, true); + expect(timingsDC.get(Csym)).toBe(1); + expect(timingsDC.size).toBe(1); // no other hits + }); + + it('passes a test where a function is present in two different native symbols', function () { + // The funky part here is that the targeted call node has frames from two different native + // symbols: Two from native symbol Bsym, and one from native symbol Asym. That's + // because B is present both as its own native symbol (separate outer function) + // and as an inlined call from A. In other words, C has been inlined both into + // a standalone B and also into another copy of B which was inlined into A. + // + // This means that, if the user double clicks call node [A, B, C], there are two + // different symbols for which we may want to display the assembly code, and we + // should compute how much time is spent in each. + const { + derivedThreads, + funcNamesDictPerThread, + nativeSymbolsDictPerThread, + defaultCategory, + } = getProfileFromTextSamples(` + A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] A[lib:one][sym:Asym:20:] + B[lib:one][sym:Bsym:30:] B[lib:one][sym:Asym:20:][inl:1] B[lib:one][sym:Bsym:30:] E[lib:one][sym:Esym:30:] + C[lib:one][sym:Bsym:30:][inl:1] C[lib:one][sym:Asym:20:][inl:2] C[lib:one][sym:Bsym:30:] + D[lib:one][sym:Dsym:40:] + `); + + const [{ A, B, C }] = funcNamesDictPerThread; + const [{ Asym, Bsym }] = nativeSymbolsDictPerThread; + const [thread] = derivedThreads; + + const timingsABCForBsym = getTimings( + thread, + [A, B, C], + defaultCategory, + false + ); + expect(timingsABCForBsym.get(Asym)).toBe(1); + expect(timingsABCForBsym.get(Bsym)).toBe(2); + expect(timingsABCForBsym.size).toBe(2); // no other hits + }); +}); + describe('getNativeSymbolInfo', function () { it('calculates the correct native symbol info', function () { const { profile, nativeSymbolsDictPerThread } = getProfileFromTextSamples(` From d7bb19b6265108a155f0bc8300abf7ba11593dcc Mon Sep 17 00:00:00 2001 From: Pontoon Date: Thu, 5 Feb 2026 21:20:43 +0000 Subject: [PATCH 20/30] Pontoon/Firefox Profiler: Update Spanish (Chile) (es-CL) Co-authored-by: ravmn (es-CL) --- locales/es-CL/app.ftl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/locales/es-CL/app.ftl b/locales/es-CL/app.ftl index debacda26a..8b40048634 100644 --- a/locales/es-CL/app.ftl +++ b/locales/es-CL/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Thu, 5 Feb 2026 21:50:34 +0000 Subject: [PATCH 21/30] Pontoon/Firefox Profiler: Update Interlingua (ia) Co-authored-by: Martijn Dekker (ia) --- locales/ia/app.ftl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/ia/app.ftl b/locales/ia/app.ftl index d441b59677..c0f9ac82a0 100644 --- a/locales/ia/app.ftl +++ b/locales/ia/app.ftl @@ -341,8 +341,8 @@ Home--enable-button-unavailable = # This message can be seen on https://main--perf-html.netlify.app/ . Home--web-channel-unavailable = Iste instantia de profilator non ha potite connecter se al WebChannel. Isto usualmente significa que illo se executa sur un hospite differente de illo que es specificate in le preferentia devtools.performance.recording.ui-base-url. Si tu vole capturar nove profilos con iste instantia, e dar a illo le controlo programmatic del button de menu profilator, tu pote ir a about:config e cambiar le preferentia. Home--record-instructions = - Pro initiar profilar, clicca sur le button profila o usa le - vias breve de claviero. Le icone es blau quando un profilo es in registration. + Pro comenciar le profilage, clicca sur le button de profilage o usa le + claves de accesso directe. Le icone es blau quando un profilo se registra. Pulsa Capturar pro cargar le datos in profiler.firefox.com. Home--instructions-content = Registrar profilos de prestation require { -firefox-brand-name }. @@ -366,7 +366,7 @@ Home--load-files-from-other-tools2 = proprie importator. Home--install-chrome-extension = Installar le extension de Chrome Home--chrome-extension-instructions = Usa le extension de { -profiler-brand-name } pro Chrome pro capturar profilos de prestation in Chrome e analysar los in le { -profiler-brand-name }. -Home--chrome-extension-recording-instructions = Installar le extension ab le Boteca web de Chrome. Un vice installate, usar le icone barra del instrumentos del extensiones o le vias breve pro cessar de profilar. Tu alsi pote exportar profilos e cargar los ci pro analyse detaliate. +Home--chrome-extension-recording-instructions = Un vice installate, usar le icone de barra de instrumentos del extension o le accessos directe pro comenciar e terminar le profilage. Tu pote etiam exportar profilos e cargar los ci pro un analyse detaliate. ## IdleSearchField ## The component that is used for all the search inputs in the application. From 1d3e71aa69d3519dc8f85f24aa10e3b675417d1e Mon Sep 17 00:00:00 2001 From: Pontoon Date: Fri, 6 Feb 2026 02:20:35 +0000 Subject: [PATCH 22/30] Pontoon/Firefox Profiler: Update Chinese (China) (zh-CN) Co-authored-by: Olvcpr423 (zh-CN) --- locales/zh-CN/app.ftl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/locales/zh-CN/app.ftl b/locales/zh-CN/app.ftl index c2c8ada65a..722c8e8744 100644 --- a/locales/zh-CN/app.ftl +++ b/locales/zh-CN/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Fri, 6 Feb 2026 12:46:59 +0100 Subject: [PATCH 23/30] Use base 10 when calculating memory (#5810) --- src/test/components/TrackBandwidth.test.tsx | 12 +-- .../__snapshots__/TooltipMarker.test.tsx.snap | 22 +++--- src/test/unit/format-numbers.test.ts | 77 +++++++++++-------- src/test/unit/marker-schema.test.ts | 6 +- src/utils/format-numbers.ts | 14 ++-- 5 files changed, 72 insertions(+), 59 deletions(-) diff --git a/src/test/components/TrackBandwidth.test.tsx b/src/test/components/TrackBandwidth.test.tsx index 3f208ec09e..b8109c8693 100644 --- a/src/test/components/TrackBandwidth.test.tsx +++ b/src/test/components/TrackBandwidth.test.tsx @@ -164,7 +164,7 @@ describe('TrackBandwidth', function () { screen.getByText('Transfer speed for this sample:') ).toBeInTheDocument(); expect(screen.getByText(/speed/).nextSibling).toHaveTextContent( - '4.66GB\u2069 per second' + '5.00GB\u2069 per second' ); expect( @@ -176,14 +176,14 @@ describe('TrackBandwidth', function () { screen.getByText('Data transferred up to this time:') ).toBeInTheDocument(); expect(screen.getByText(/transferred up to/).nextSibling).toHaveTextContent( - /6.86MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ + /7.19MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ ); expect( screen.getByText('Data transferred in the visible range:') ).toBeInTheDocument(); expect(screen.getByText(/visible range/).nextSibling).toHaveTextContent( - /7.97MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ + /8.35MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ ); }); @@ -224,13 +224,13 @@ describe('TrackBandwidth', function () { moveMouseAtCounter(3, 0); // Note: Fluent adds isolation characters \u2068 and \u2069 around variables. expect(screen.getByText(/speed/).nextSibling).toHaveTextContent( - '95.4MB\u2069 per second' + '100MB\u2069 per second' ); expect(screen.getByText(/visible range:/).nextSibling).toHaveTextContent( - /7.97MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ + /8.35MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/ ); expect( screen.getByText(/current selection:/).nextSibling - ).toHaveTextContent(/4.77MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/); + ).toHaveTextContent(/5.00MB\u2069 \(\u2068\d+(\.\d+)?\u2069 g CO₂e\)/); }); }); diff --git a/src/test/components/__snapshots__/TooltipMarker.test.tsx.snap b/src/test/components/__snapshots__/TooltipMarker.test.tsx.snap index 0b2b81311d..011f064231 100644 --- a/src/test/components/__snapshots__/TooltipMarker.test.tsx.snap +++ b/src/test/components/__snapshots__/TooltipMarker.test.tsx.snap @@ -342,7 +342,7 @@ exports[`TooltipMarker renders properly network markers where content type is bl Requested bytes : - 45.9KB + 47.0KB
@@ -538,7 +538,7 @@ exports[`TooltipMarker renders properly network markers where content type is mi Requested bytes :
- 45.9KB + 47.0KB
@@ -744,7 +744,7 @@ exports[`TooltipMarker renders properly network markers with a preconnect part 2 Requested bytes :
- 45.9KB + 47.0KB
@@ -1014,7 +1014,7 @@ exports[`TooltipMarker renders properly network markers with a preconnect part c Requested bytes :
- 45.9KB + 47.0KB
@@ -1210,7 +1210,7 @@ exports[`TooltipMarker renders properly normal network markers 1`] = ` Requested bytes :
- 45.9KB + 47.0KB
@@ -3216,14 +3216,14 @@ exports[`TooltipMarker renders tooltips for various markers: GCMajor-16.5 1`] = GC heap size (pre - post) :
- 46.1MB - 36.3MB + 48.4MB - 38.1MB
Malloc heap size (pre - post) :
- 23.1MB - 12.1MB + 24.2MB - 12.7MB
@@ -3362,7 +3362,7 @@ exports[`TooltipMarker renders tooltips for various markers: GCMinor-15.5 1`] = Bytes tenured :
- 1.30MB / 1.97MB (66%) + 1.37MB / 2.06MB (66%)
@@ -3376,7 +3376,7 @@ exports[`TooltipMarker renders tooltips for various markers: GCMinor-15.5 1`] = Bytes used :
- 1.97MB / 16.0MB (12%) + 2.06MB / 16.8MB (12%)
@@ -3397,7 +3397,7 @@ exports[`TooltipMarker renders tooltips for various markers: GCMinor-15.5 1`] = Tenured allocation rate :
- 181MB/s + 190MB/s
@@ -3501,7 +3501,7 @@ exports[`TooltipMarker renders tooltips for various markers: GCSlice-17.5 1`] = Trigger (amt/trig) :
- 266MB / 245MB + 279MB / 257MB
diff --git a/src/test/unit/format-numbers.test.ts b/src/test/unit/format-numbers.test.ts index a6c47fbd08..19a0795245 100644 --- a/src/test/unit/format-numbers.test.ts +++ b/src/test/unit/format-numbers.test.ts @@ -28,27 +28,27 @@ describe('formatGigaBytes', () => { }); it('returns large values without fractional digits by default', () => { - expect(formatGigaBytes(1234567890123)).toBe('1,150GB'); + expect(formatGigaBytes(1234567890123)).toBe('1,235GB'); }); it('returns values with 2 fractional digits by default', () => { - expect(formatGigaBytes(1234567890)).toBe('1.15GB'); + expect(formatGigaBytes(1234567890)).toBe('1.23GB'); }); it('can return values with byte precision', () => { - expect(formatGigaBytes(1234567890, 3, 2, 1)).toBe('1GB 153MB 384KB 722B'); + expect(formatGigaBytes(1234567890, 3, 2, 1)).toBe('1GB 234MB 567KB 890B'); }); it('can return values with kilobyte precision', () => { - expect(formatGigaBytes(1234567890, 3, 2, 1024)).toBe('1GB 153MB 385KB'); + expect(formatGigaBytes(1234567890, 3, 2, 1000)).toBe('1GB 234MB 568KB'); }); it('can return values with megabyte precision', () => { - expect(formatGigaBytes(1234567890, 3, 2, 1024 ** 2)).toBe('1GB 153MB'); + expect(formatGigaBytes(1234567890, 3, 2, 1000 ** 2)).toBe('1GB 235MB'); }); it('can return values with gigabyte precision', () => { - expect(formatGigaBytes(1234567890, 3, 2, 1024 ** 3)).toBe('1GB'); + expect(formatGigaBytes(1234567890, 3, 2, 1000 ** 3)).toBe('1GB'); }); }); @@ -58,23 +58,23 @@ describe('formatMegaBytes', () => { }); it('returns large values without fractional digits by default', () => { - expect(formatMegaBytes(1234567890)).toBe('1,177MB'); + expect(formatMegaBytes(1234567890)).toBe('1,235MB'); }); it('returns values with 2 fractional digits by default', () => { - expect(formatMegaBytes(1234567)).toBe('1.18MB'); + expect(formatMegaBytes(1234567)).toBe('1.23MB'); }); it('can return values with byte precision', () => { - expect(formatMegaBytes(1234567, 3, 2, 1)).toBe('1MB 181KB 647B'); + expect(formatMegaBytes(1234567, 3, 2, 1)).toBe('1MB 234KB 567B'); }); it('can return values with kilobyte precision', () => { - expect(formatMegaBytes(1234567, 3, 2, 1024)).toBe('1MB 182KB'); + expect(formatMegaBytes(1234567, 3, 2, 1000)).toBe('1MB 235KB'); }); it('can return values with megabyte precision', () => { - expect(formatMegaBytes(1234567, 3, 2, 1024 ** 2)).toBe('1MB'); + expect(formatMegaBytes(1234567, 3, 2, 1000 ** 2)).toBe('1MB'); }); }); @@ -84,19 +84,19 @@ describe('formatKiloBytes', () => { }); it('returns large values without fractional digits by default', () => { - expect(formatKiloBytes(1234567)).toBe('1,206KB'); + expect(formatKiloBytes(1234567)).toBe('1,235KB'); }); it('returns values with 2 fractional digits by default', () => { - expect(formatKiloBytes(1234)).toBe('1.21KB'); + expect(formatKiloBytes(1234)).toBe('1.23KB'); }); it('can return values with byte precision', () => { - expect(formatKiloBytes(1234, 3, 2, 1)).toBe('1KB 210B'); + expect(formatKiloBytes(1234, 3, 2, 1)).toBe('1KB 234B'); }); it('can return values with kilobyte precision', () => { - expect(formatKiloBytes(1234, 3, 2, 1024)).toBe('1KB'); + expect(formatKiloBytes(1234, 3, 2, 1000)).toBe('1KB'); }); }); @@ -110,40 +110,53 @@ describe('formatBytes', () => { }); it('can return values with the kilobyte unit', () => { - expect(formatBytes(12345)).toBe('12.1KB'); + expect(formatBytes(12345)).toBe('12.3KB'); }); it('can return values with the megabyte unit', () => { - expect(formatBytes(1234567)).toBe('1.18MB'); + expect(formatBytes(1234567)).toBe('1.23MB'); }); it('can return values with the gigabyte unit', () => { - expect(formatBytes(1234567890)).toBe('1.15GB'); + expect(formatBytes(1234567890)).toBe('1.23GB'); }); it('can return values with byte precision', () => { - expect(formatBytes(12345, 3, 2, 1)).toBe('12KB 57B'); + expect(formatBytes(12345, 3, 2, 1)).toBe('12KB 345B'); }); it('can return values with kilobyte precision', () => { - expect(formatBytes(12345, 3, 2, 1024)).toBe('12KB'); + expect(formatBytes(12345, 3, 2, 1000)).toBe('12KB'); }); }); describe('findRoundBytesValueGreaterOrEqualTo', () => { - const expectedValues = [0, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000]; - - it('rounds small bytes values using base 10 round value', () => { + const expectedValues = [ + // minValue, expected + // minValue <= 1000 + [0, 0], + [3, 5], + [63, 100], + [511, 1000], + [1000, 2000], + + // 1000 < minValue <= 1000^4 + [2047, 4000], + [9999, 16000], + [123456, 128000], + [999999, 1000000], + [1_234_567, 2_000_000], + [1_234_567_891, 2_000_000_000], + + // minValue > 1000^4 + [1_234_567_891_234, 2_000_000_000_000], + [1_234_567_891_234_567, 2_000_000_000_000_000], + ]; + + it('correctly rounds bytes values using base 10 round value', () => { for (let i = 0; i < expectedValues.length; ++i) { - expect(findRoundBytesValueGreaterOrEqualTo(2 ** i - 1)).toBe( - expectedValues[i] - ); - } - }); - - it('rounds large bytes values using base 2 round value', () => { - for (let i = expectedValues.length; i < 40; ++i) { - expect(findRoundBytesValueGreaterOrEqualTo(2 ** i - 1)).toBe(2 ** i); + const [input, expected] = expectedValues[i]; + expect(findRoundBytesValueGreaterOrEqualTo(input)).toBe(expected); } }); }); diff --git a/src/test/unit/marker-schema.test.ts b/src/test/unit/marker-schema.test.ts index 5291f5b8c4..1fd6a8da0c 100644 --- a/src/test/unit/marker-schema.test.ts +++ b/src/test/unit/marker-schema.test.ts @@ -357,9 +357,9 @@ describe('marker schema formatting', function () { "bytes - 0B", "bytes - 10B", "bytes - 12B", - "bytes - 121KB", - "bytes - 118MB", - "bytes - 115GB", + "bytes - 123KB", + "bytes - 123MB", + "bytes - 123GB", "bytes - 0.000B", "integer - 0", "integer - 10", diff --git a/src/utils/format-numbers.ts b/src/utils/format-numbers.ts index 77ecb185a7..7592599a16 100644 --- a/src/utils/format-numbers.ts +++ b/src/utils/format-numbers.ts @@ -180,7 +180,7 @@ export function formatGigaBytes( maxFractionalDigits: number = 2, precision: number = Infinity ): string { - const bytesPerGigabyte = 1024 ** 3; + const bytesPerGigabyte = 1000 ** 3; if (precision === Infinity) { return ( formatNumber( @@ -210,7 +210,7 @@ export function formatMegaBytes( maxFractionalDigits: number = 2, precision: number = Infinity ): string { - const bytesPerMegabyte = 1024 ** 2; + const bytesPerMegabyte = 1000 ** 2; if (precision === Infinity) { return ( formatNumber( @@ -240,7 +240,7 @@ export function formatKiloBytes( maxFractionalDigits: number = 2, precision: number = Infinity ): string { - const bytesPerKilobyte = 1024; + const bytesPerKilobyte = 1000; if (precision === Infinity) { return ( formatNumber( @@ -274,14 +274,14 @@ export function formatBytes( // Use singles up to 10,000. I think 9,360B looks nicer than 9.36KB. // We use "0" for significantDigits because bytes will always be integers. return formatNumber(bytes, 0) + 'B'; - } else if (bytes < 1024 ** 2) { + } else if (bytes < 1000 ** 2) { return formatKiloBytes( bytes, significantDigits, maxFractionalDigits, precision ); - } else if (bytes < 1024 ** 3) { + } else if (bytes < 1000 ** 3) { return formatMegaBytes( bytes, significantDigits, @@ -542,10 +542,10 @@ function _findRoundValueGreaterOrEqualTo(minValue: number): number { export function findRoundBytesValueGreaterOrEqualTo(minValue: number): number { // Special case KB, MB, GB. - if (minValue > 1024 && minValue <= 1024 ** 4) { + if (minValue > 1000 && minValue <= 1000 ** 4) { for (const power of [1, 2, 3]) { for (const value of [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]) { - const number = value * 1024 ** power; + const number = value * 1000 ** power; if (minValue <= number) { return number; } From 3b40876b6d8aca68216cae590c5de179474e3b4b Mon Sep 17 00:00:00 2001 From: Pontoon Date: Fri, 6 Feb 2026 21:00:34 +0000 Subject: [PATCH 24/30] Pontoon/Firefox Profiler: Update English (Canada) (en-CA) Co-authored-by: Saurabh (en-CA) Co-authored-by: chutten (en-CA) --- locales/en-CA/app.ftl | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/locales/en-CA/app.ftl b/locales/en-CA/app.ftl index c6480b0216..820b584cf6 100644 --- a/locales/en-CA/app.ftl +++ b/locales/en-CA/app.ftl @@ -573,6 +573,8 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = Unlimited MenuButtons--metaInfo--application = Application MenuButtons--metaInfo--name-and-version = Name and version: +# The time between application startup and when the profiler was started +MenuButtons--metaInfo--application-uptime2 = Uptime: MenuButtons--metaInfo--update-channel = Update channel: MenuButtons--metaInfo--build-id = Build ID: MenuButtons--metaInfo--build-type = Build type: @@ -887,6 +889,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 @@ -1036,6 +1043,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: @@ -1181,6 +1194,17 @@ AssemblyView--show-button = # Assembly refers to the low-level programming language. AssemblyView--hide-button = .title = Hide the assembly view +# The "◀" button above the assembly view. +AssemblyView--prev-button = + .title = Previous +# The "▶" button above the assembly view. +AssemblyView--next-button = + .title = Next +# 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 } of { $total } ## UploadedRecordingsHome ## This is the page that displays all the profiles that user has uploaded. From 91d954428372371ec178bc42928349ff90227ff4 Mon Sep 17 00:00:00 2001 From: Pontoon Date: Fri, 6 Feb 2026 21:10:40 +0000 Subject: [PATCH 25/30] Pontoon/Firefox Profiler: Update English (Canada) (en-CA) Co-authored-by: chutten (en-CA) --- locales/en-CA/app.ftl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/locales/en-CA/app.ftl b/locales/en-CA/app.ftl index 820b584cf6..77fba81d92 100644 --- a/locales/en-CA/app.ftl +++ b/locales/en-CA/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Sun, 8 Feb 2026 21:50:32 +0000 Subject: [PATCH 26/30] Pontoon/Firefox Profiler: Update Greek (el) Co-authored-by: Jim Spentzos (el) --- locales/el/app.ftl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/locales/el/app.ftl b/locales/el/app.ftl index b68c19404c..13e10f9ba2 100644 --- a/locales/el/app.ftl +++ b/locales/el/app.ftl @@ -568,6 +568,8 @@ MenuButtons--metaInfo--buffer-duration-seconds = MenuButtons--metaInfo--buffer-duration-unlimited = Απεριόριστη MenuButtons--metaInfo--application = Εφαρμογή MenuButtons--metaInfo--name-and-version = Όνομα και έκδοση: +# The time between application startup and when the profiler was started +MenuButtons--metaInfo--application-uptime2 = Χρόνος λειτουργίας: MenuButtons--metaInfo--update-channel = Κανάλι ενημερώσεων: MenuButtons--metaInfo--build-id = ID δομής: MenuButtons--metaInfo--build-type = Τύπος δομής: From f3712feeab88ee0435a65da3c5dd678c5af002b4 Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Mon, 9 Feb 2026 06:55:01 +0900 Subject: [PATCH 27/30] Make the dark-mode selected timeline background more similar to non-selected color/contrast --- res/css/global.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/global.css b/res/css/global.css index 8ac01e02d1..a96cfb68c0 100644 --- a/res/css/global.css +++ b/res/css/global.css @@ -135,8 +135,8 @@ --panel-border-color: var(--grey-60); --selected-track-background-color: color-mix( in hsl, - var(--grey-90) 60%, - var(--teal-50) + var(--raised-background-color) 96%, + #edf6ff ); --tooltip-number-foreground-color: var(--grey-40); --colored-border-color: rgb(237 237 240 / 0.1); From 9f9a548916897305c3c6c9a10f83837649a37025 Mon Sep 17 00:00:00 2001 From: Tooru Fujisawa Date: Mon, 9 Feb 2026 07:02:49 +0900 Subject: [PATCH 28/30] Use slightly brigher colors for the timeline and category --- res/css/categories.css | 9 ++++++--- src/utils/colors.ts | 16 ++++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/res/css/categories.css b/res/css/categories.css index 1535799653..3cfa1c217c 100644 --- a/res/css/categories.css +++ b/res/css/categories.css @@ -21,10 +21,13 @@ } :root.dark-mode { - --category-color-purple: var(--purple-60); - --category-color-green: var(--green-80); + /* Colors based on photon colors. */ + --purple-55: #8a00eb; + --yellow-65: #be9b00; + --category-color-purple: var(--purple-55); + --category-color-green: var(--green-70); --category-color-orange: var(--orange-60); - --category-color-yellow: var(--yellow-70); + --category-color-yellow: var(--yellow-65); --category-color-magenta: var(--magenta-70); --category-color-gray: var(--grey-50); --category-color-grey: var(--grey-50); diff --git a/src/utils/colors.ts b/src/utils/colors.ts index 98c8be73e0..1fc7bd19ce 100644 --- a/src/utils/colors.ts +++ b/src/utils/colors.ts @@ -101,6 +101,10 @@ const DEFAULT_STYLE: ColorStyles = { gravity: 0, }; +// Colors based on photon colors. +export const PURPLE_55 = '#8a00eb'; +export const YELLOW_65 = '#be9b00'; + const PSEUDO_TRANSPARENT_STYLE: ColorStyles = { ...DEFAULT_STYLE, _selectedFillStyle: [GREY_30, GREY_70], @@ -170,15 +174,15 @@ const STYLE_MAP: { [key: string]: ColorStyles } = { }, green: { ...DEFAULT_STYLE, - _selectedFillStyle: [GREEN_60, GREEN_80], - _unselectedFillStyle: [GREEN_60 + '60', GREEN_80 + '60'], + _selectedFillStyle: [GREEN_60, GREEN_70], + _unselectedFillStyle: [GREEN_60 + '60', GREEN_70 + '60'], _selectedTextColor: '#fff', gravity: 4, }, purple: { ...DEFAULT_STYLE, - _selectedFillStyle: [PURPLE_70, PURPLE_60], - _unselectedFillStyle: [PURPLE_70 + '60', PURPLE_60 + '60'], + _selectedFillStyle: [PURPLE_70, PURPLE_55], + _unselectedFillStyle: [PURPLE_70 + '60', PURPLE_55 + '70'], _selectedTextColor: '#fff', gravity: 5, }, @@ -187,9 +191,9 @@ const STYLE_MAP: { [key: string]: ColorStyles } = { _selectedFillStyle: [ // This yellow has more contrast than YELLOW_50. '#ffe129', - YELLOW_70, + YELLOW_65, ], - _unselectedFillStyle: [YELLOW_50 + '70', YELLOW_60 + '70'], + _unselectedFillStyle: [YELLOW_50 + '70', YELLOW_65 + '85'], _selectedTextColor: ['#000', GREY_20], gravity: 6, }, From 9ff407078c2c24cfd12b556f5cca8e90bb37a37a Mon Sep 17 00:00:00 2001 From: Pontoon Date: Mon, 9 Feb 2026 18:20:30 +0000 Subject: [PATCH 29/30] Pontoon/Firefox Profiler: Update Portuguese (Brazil) (pt-BR) Co-authored-by: Marcelo Ghelman (pt-BR) --- locales/pt-BR/app.ftl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/locales/pt-BR/app.ftl b/locales/pt-BR/app.ftl index c409bab830..386884190c 100644 --- a/locales/pt-BR/app.ftl +++ b/locales/pt-BR/app.ftl @@ -21,6 +21,16 @@ AppHeader--app-header =
{ -profiler-brand-name }
Date: Mon, 9 Feb 2026 18:20:51 -0500 Subject: [PATCH 30/30] Remove unused function getEmptySamplesTableWithResponsiveness. --- src/profile-logic/data-structures.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index dfb9994e8e..acf6018fab 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -10,7 +10,6 @@ import { import type { RawThread, RawSamplesTable, - SamplesTable, FrameTable, RawStackTable, StackTable, @@ -93,26 +92,6 @@ export function getEmptySamplesTableWithEventDelay(): RawSamplesTable { }; } -/** - * Returns an empty samples table with responsiveness field instead of eventDelay. - * responsiveness is the older field and replaced with eventDelay. We should - * account for older profiles and use both of the flavors if needed. - */ -export function getEmptySamplesTableWithResponsiveness(): SamplesTable { - return { - // Important! - // If modifying this structure, please update all callers of this function to ensure - // that they are pushing on correctly to the data structure. These pushes may not - // be caught by the type system. - weightType: 'samples', - weight: null, - responsiveness: [], - stack: [], - time: [], - length: 0, - }; -} - export function getEmptyFrameTable(): FrameTable { return { // Important!