From 0c71bb8e65a6c42a2ca848a414ea729375d2a18a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Thu, 6 Aug 2026 17:00:08 +0200 Subject: [PATCH] Handle Text and Log marker payloads with their marker schema Firefox now outputs the Text marker's `name` and the Log marker's `message` as unique strings, so the payload holds a string table index instead of the text itself. The two Text marker PII sanitizers and the MOZ_LOG formatting read those fields directly and threw an error. This patch fixes these issues by always looking at the marker schema and not having any arbitrary assumptions about some certain marker types. It was a bad idea to have these custom handlings in the past instead of relying on the marker schema in the first place. --- src/profile-logic/marker-data.ts | 115 ++++++++++++--- src/profile-logic/marker-schema.ts | 41 ++++-- src/profile-logic/sanitize.ts | 36 ++++- src/profile-query/formatters/marker-info.ts | 18 ++- src/test/fixtures/profiles/marker-schema.ts | 4 +- .../__snapshots__/profile-view.test.ts.snap | 5 + src/test/unit/marker-data.test.ts | 67 +++++++++ .../unit/profile-query/marker-utils.test.ts | 30 ++++ src/test/unit/sanitize.test.ts | 135 +++++++++++++++++- src/types/markers.ts | 6 +- src/utils/window-console.ts | 9 +- 11 files changed, 424 insertions(+), 42 deletions(-) diff --git a/src/profile-logic/marker-data.ts b/src/profile-logic/marker-data.ts index 6861df3d86..5c196abd14 100644 --- a/src/profile-logic/marker-data.ts +++ b/src/profile-logic/marker-data.ts @@ -23,6 +23,7 @@ import { } from 'firefox-profiler/app-logic/constants'; import { getSchemaFromMarker, + isStringIndexMarkerField, markerPayloadMatchesSearch, markerSchemaFrontEndOnly, } from './marker-schema'; @@ -1468,15 +1469,56 @@ export function removePrefMarkerPreferenceValues( } /** - * Sanitize Text marker's name property for potential URLs. + * Apply a transformation to a Text marker's text. The schema tells us whether the + * payload holds the text inline or as a string table index. In the latter case the + * result is interned as a new string, as other markers and frames may share that + * entry. + */ +function _updateTextMarkerText( + payload: TextMarkerPayload, + stringIndexMarkerFieldsByDataType: Map, + stringTable: StringTable, + transform: (text: string) => string +): TextMarkerPayload { + // The casts below follow the storage layout the schema declares, which + // TypeScript can't verify from the payload type alone. + if ( + !isStringIndexMarkerField( + stringIndexMarkerFieldsByDataType, + payload.type, + 'name' + ) + ) { + return { ...payload, name: transform(payload.name as string) }; + } + + const nameIndex = payload.name as IndexIntoStringTable; + if (!stringTable.hasIndex(nameIndex)) { + return payload; + } + const text = stringTable.getString(nameIndex); + const newText = transform(text); + if (newText === text) { + return payload; + } + return { ...payload, name: stringTable.indexForString(newText) }; +} + +/** + * Sanitize Text marker's name property for potential URLs. Only for payloads + * holding their text inline, as the string table is sanitized as a whole. */ export function sanitizeTextMarker( - payload: TextMarkerPayload + payload: TextMarkerPayload, + stringIndexMarkerFieldsByDataType: Map, + stringTable: StringTable ): TextMarkerPayload { - return { - ...payload, - name: removeURLs(payload.name), - }; + return _updateTextMarkerText( + payload, + stringIndexMarkerFieldsByDataType, + stringTable, + removeURLs + ); } /** @@ -1484,20 +1526,26 @@ export function sanitizeTextMarker( */ export function sanitizeExtensionTextMarker( markerName: string, - payload: TextMarkerPayload + payload: TextMarkerPayload, + stringIndexMarkerFieldsByDataType: Map, + stringTable: StringTable ): TextMarkerPayload { if (['ExtensionParent', 'ExtensionChild'].includes(markerName)) { - return { - ...payload, - name: payload.name.replace(/^.*, (api_(call|event): )/, '$1'), - }; + return _updateTextMarkerText( + payload, + stringIndexMarkerFieldsByDataType, + stringTable, + (text) => text.replace(/^.*, (api_(call|event): )/, '$1') + ); } if (markerName === 'Extension Suspend') { - return { - ...payload, - name: payload.name.replace(/ by .*$/, ''), - }; + return _updateTextMarkerText( + payload, + stringIndexMarkerFieldsByDataType, + stringTable, + (text) => text.replace(/ by .*$/, '') + ); } return payload; @@ -1731,6 +1779,31 @@ export function formatLogTimestamp(absoluteMs: number): string { ); } +/** + * Resolve a new format Log marker's message. The schema tells us whether the + * payload holds the message inline or as an index into the string table, which + * is what newer profiles do. + */ +export function resolveLogMarkerMessage( + message: string | IndexIntoStringTable, + stringArray: string[], + stringIndexMarkerFieldsByDataType: Map +): string { + // The casts below follow the storage layout the schema declares, which + // TypeScript can't verify from the payload type alone. + if ( + !isStringIndexMarkerField( + stringIndexMarkerFieldsByDataType, + 'Log', + 'message' + ) + ) { + return message as string; + } + + return stringArray[message as IndexIntoStringTable] ?? ''; +} + /** * Format a Log marker payload into a MOZ_LOG canonical line. * @@ -1751,15 +1824,21 @@ export function formatLogStatement( threadName: string, data: LogMarkerPayload, moduleName: string, - stringArray: string[] + stringArray: string[], + stringIndexMarkerFieldsByDataType: Map ): string | null { if ('message' in data) { - if (!data.message) { + const message = resolveLogMarkerMessage( + data.message, + stringArray, + stringIndexMarkerFieldsByDataType + ); + if (!message) { return null; } const levelStr = stringArray[data.level] ?? ''; const levelLetter = LOG_LEVEL_STRING_TO_LETTER[levelStr] ?? 'D'; - return `${timestampStr} - [${processName} ${pid}: ${threadName}]: ${levelLetter}/${moduleName} ${data.message.trim()}`; + return `${timestampStr} - [${processName} ${pid}: ${threadName}]: ${levelLetter}/${moduleName} ${message.trim()}`; } if (!data.name) { return null; diff --git a/src/profile-logic/marker-schema.ts b/src/profile-logic/marker-schema.ts index 59454a280d..bb56860d7d 100644 --- a/src/profile-logic/marker-schema.ts +++ b/src/profile-logic/marker-schema.ts @@ -642,11 +642,7 @@ export function markerPayloadMatchesSearch( continue; } - if ( - payloadField.format === 'unique-string' || - payloadField.format === 'flow-id' || - payloadField.format === 'terminating-flow-id' - ) { + if (isStringIndexFormat(payloadField.format)) { if (typeof value !== 'number') { console.warn( `In marker ${marker.name}, the key ${payloadField.key} has an invalid value "${value}" as a unique string, it isn't a number.` @@ -671,6 +667,19 @@ export function markerPayloadMatchesSearch( return false; } +/** + * Whether this field format means the payload holds a string table index. + */ +export function isStringIndexFormat( + format: MarkerFormatType | undefined +): boolean { + return ( + format === 'unique-string' || + format === 'flow-id' || + format === 'terminating-flow-id' + ); +} + /** * Returns a map of marker schema name -> array of field keys, listing any fields * that contain indexes into the string table. If a marker schema has no such @@ -689,12 +698,7 @@ export function computeStringIndexMarkerFieldsByDataType( const { name, fields } = schema; const stringIndexFields = []; for (const field of fields) { - if ( - (field.format === 'unique-string' || - field.format === 'flow-id' || - field.format === 'terminating-flow-id') && - field.key - ) { + if (isStringIndexFormat(field.format) && field.key) { stringIndexFields.push(field.key); } } @@ -704,3 +708,18 @@ export function computeStringIndexMarkerFieldsByDataType( } return stringIndexMarkerFieldsByDataType; } + +/** + * Whether the schema of this marker data type declares the given payload field + * as a string table index. Takes the map computed by + * `computeStringIndexMarkerFieldsByDataType`. + */ +export function isStringIndexMarkerField( + stringIndexMarkerFieldsByDataType: Map, + dataType: string, + fieldKey: string +): boolean { + return ( + stringIndexMarkerFieldsByDataType.get(dataType)?.includes(fieldKey) ?? false + ); +} diff --git a/src/profile-logic/sanitize.ts b/src/profile-logic/sanitize.ts index d03304c11b..9bba0aedc4 100644 --- a/src/profile-logic/sanitize.ts +++ b/src/profile-logic/sanitize.ts @@ -18,7 +18,11 @@ import { sanitizeTextMarker, sanitizeFromMarkerSchema, } from './marker-data'; -import { getSchemaFromMarker } from './marker-schema'; +import { + computeStringIndexMarkerFieldsByDataType, + getSchemaFromMarker, + isStringIndexMarkerField, +} from './marker-schema'; import { filterRawThreadSamplesToRange, filterCounterSamplesToRange, @@ -141,6 +145,11 @@ export function sanitizePII( stringArray, }; + // Precompute the payload fields that hold string table indexes, so that the + // marker loop below doesn't have to walk the schema fields for every marker. + const stringIndexMarkerFieldsByDataType = + computeStringIndexMarkerFieldsByDataType(Object.values(markerSchemaByName)); + let stackFlags: Uint8Array | null = null; if (windowIdFromPrivateBrowsing.size > 0) { @@ -326,6 +335,7 @@ export function sanitizePII( PIIToBeRemoved, windowIdFromPrivateBrowsing, markerSchemaByName, + stringIndexMarkerFieldsByDataType, stackFlags ); @@ -441,6 +451,7 @@ function sanitizeThreadPII( PIIToBeRemoved: RemoveProfileInformation, windowIdFromPrivateBrowsing: Set, markerSchemaByName: MarkerSchemaByName, + stringIndexMarkerFieldsByDataType: Map, stackFlags: Uint8Array | null ): RawThread | null { if (PIIToBeRemoved.shouldRemoveThreads.has(threadIndex)) { @@ -509,9 +520,21 @@ function sanitizeThreadPII( markerTable.name[i] = stringTable.indexForString(sanitizedRequestStr); } - if (currentMarker.type === 'Text') { + if ( + currentMarker.type === 'Text' && + !isStringIndexMarkerField( + stringIndexMarkerFieldsByDataType, + 'Text', + 'name' + ) + ) { // Sanitize all the name fields of text markers in case they contain URLs. - markerTable.data[i] = sanitizeTextMarker(currentMarker); + // Newer profiles hold the text in the string table, sanitized above. + markerTable.data[i] = sanitizeTextMarker( + currentMarker, + stringIndexMarkerFieldsByDataType, + stringTable + ); // Re-assign the value of currentMarker as the marker may be // sanitized again to remove extension ids. currentMarker = markerTable.data[i]; @@ -524,10 +547,13 @@ function sanitizeThreadPII( currentMarker.type === 'Text' ) { const markerName = stringTable.getString(markerTable.name[i]); - // Sanitize extension ids out of known extension markers. + // Sanitize extension ids out of known extension markers. Unlike URLs, + // these aren't removed from the string table as a whole. markerTable.data[i] = sanitizeExtensionTextMarker( markerName, - currentMarker + currentMarker, + stringIndexMarkerFieldsByDataType, + stringTable ); } diff --git a/src/profile-query/formatters/marker-info.ts b/src/profile-query/formatters/marker-info.ts index ce1c2614fa..a28b166313 100644 --- a/src/profile-query/formatters/marker-info.ts +++ b/src/profile-query/formatters/marker-info.ts @@ -9,6 +9,7 @@ import { import { getProfile, getCategories, + getMarkerSchema, getMarkerSchemaByName, getStringTable, getCommittedRange, @@ -23,6 +24,7 @@ import { } from '../network-summary'; import { getThreadSelectors } from 'firefox-profiler/selectors/per-thread'; import { + computeStringIndexMarkerFieldsByDataType, formatFromMarkerSchema, getLabelGetter, } from 'firefox-profiler/profile-logic/marker-schema'; @@ -61,6 +63,7 @@ import { LOG_LETTER_TO_LEVEL, formatLogTimestamp, formatLogStatement, + resolveLogMarkerMessage, } from 'firefox-profiler/profile-logic/marker-data'; import { formatFunctionNameWithLibrary } from '../function-list'; import type { @@ -1369,6 +1372,9 @@ export function collectProfileLogs( const profile = getProfile(state); const profileStartTime = profile.meta.startTime; const stringArray = profile.shared.stringArray; + // The schema tells us which payload fields hold string table indexes. + const stringIndexMarkerFieldsByDataType = + computeStringIndexMarkerFieldsByDataType(getMarkerSchema(state)); // Resolve which thread indexes to include. const threadIndexes: Set | null = @@ -1423,13 +1429,18 @@ export function collectProfileLogs( let levelLetter: string; if ('message' in logData) { - if (!logData.message) { + const rawMessage = resolveLogMarkerMessage( + logData.message, + stringArray, + stringIndexMarkerFieldsByDataType + ); + if (!rawMessage) { continue; } moduleName = stringArray[markers.name[i]] ?? ''; const levelStr = stringArray[logData.level] ?? ''; levelLetter = LOG_LEVEL_STRING_TO_LETTER[levelStr] ?? 'D'; - message = logData.message.trim(); + message = rawMessage.trim(); } else { if (!logData.name) { continue; @@ -1473,7 +1484,8 @@ export function collectProfileLogs( threadName, logData, moduleName, - stringArray + stringArray, + stringIndexMarkerFieldsByDataType ); if (formatted !== null) { entries.push(formatted); diff --git a/src/test/fixtures/profiles/marker-schema.ts b/src/test/fixtures/profiles/marker-schema.ts index d5f2f61bfa..00dc94843b 100644 --- a/src/test/fixtures/profiles/marker-schema.ts +++ b/src/test/fixtures/profiles/marker-schema.ts @@ -114,8 +114,10 @@ export const markerSchemaForTests: MarkerSchema[] = [ fields: [ { key: 'module', label: 'Module', format: 'string' }, { key: 'name', label: 'Name', format: 'string' }, - // New format: level is a string table index ("Error"/"Warning"/"Info"/"Debug"/"Verbose"). + // New format: level ("Error"/"Warning"/"Info"/"Debug"/"Verbose") and + // message are string table indexes. { key: 'level', label: 'Level', format: 'unique-string' }, + { key: 'message', label: 'Message', format: 'unique-string' }, ], }, { diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index 50a5655d83..a088d078fb 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -283,6 +283,11 @@ Object { "key": "level", "label": "Level", }, + Object { + "format": "unique-string", + "key": "message", + "label": "Message", + }, ], "name": "Log", "tableLabel": "({marker.data.module}) {marker.data.name}", diff --git a/src/test/unit/marker-data.test.ts b/src/test/unit/marker-data.test.ts index 80c8c41ab0..9a5d06beff 100644 --- a/src/test/unit/marker-data.test.ts +++ b/src/test/unit/marker-data.test.ts @@ -15,7 +15,9 @@ import { processGeckoProfile } from '../../profile-logic/process-profile'; import { filterRawMarkerTableToRange, filterRawMarkerTableToRangeWithMarkersToDelete, + formatLogStatement, } from '../../profile-logic/marker-data'; +import { computeStringIndexMarkerFieldsByDataType } from '../../profile-logic/marker-schema'; import { createGeckoProfile, @@ -37,6 +39,9 @@ import { getEmptySharedData } from '../../profile-logic/data-structures'; import type { IndexIntoRawMarkerTable, + LogMarkerPayload, + MarkerFormatType, + MarkerSchema, Milliseconds, NetworkPayload, ScreenshotPayload, @@ -1392,3 +1397,65 @@ describe('filterRawMarkerTableToRangeWithMarkersToDelete', () => { expect(markerNames).toEqual(['A', 'B', 'E', 'G']); }); }); + +describe('formatLogStatement', function () { + const timestamp = '1970-01-01 00:00:00.170000000 UTC'; + const stringArray = ['', 'Error', 'the log message\n']; + + // The schema decides whether the payload holds the message inline or as an + // index into the string table. + function logSchema(messageFormat: MarkerFormatType): MarkerSchema { + return { + name: 'Log', + display: ['marker-chart', 'marker-table'], + fields: [ + { key: 'level', label: 'Level', format: 'unique-string' }, + { key: 'message', label: 'Message', format: messageFormat }, + ], + }; + } + + function format(data: LogMarkerPayload, messageFormat: MarkerFormatType) { + return formatLogStatement( + timestamp, + 'GeckoMain', + 1234, + 'GeckoMain', + data, + 'nsHttp', + stringArray, + computeStringIndexMarkerFieldsByDataType([logSchema(messageFormat)]) + ); + } + + it('formats a message held as an index into the string table', function () { + expect(format({ type: 'Log', level: 1, message: 2 }, 'unique-string')).toBe( + `${timestamp} - [GeckoMain 1234: GeckoMain]: E/nsHttp the log message` + ); + }); + + it('formats a message held inline, as older profiles do', function () { + expect( + format({ type: 'Log', level: 1, message: 'the log message\n' }, 'string') + ).toBe( + `${timestamp} - [GeckoMain 1234: GeckoMain]: E/nsHttp the log message` + ); + }); + + it('skips a marker whose message index resolves to an empty string', function () { + expect(format({ type: 'Log', level: 1, message: 0 }, 'unique-string')).toBe( + null + ); + }); + + it('formats a legacy message held in the name field', function () { + expect( + format( + { type: 'Log', module: 'D/nsHttp', name: 'the log message\n' }, + 'unique-string' + ) + ).toBe( + `${timestamp} - [GeckoMain 1234: GeckoMain]: D/nsHttp the log message` + ); + }); +}); diff --git a/src/test/unit/profile-query/marker-utils.test.ts b/src/test/unit/profile-query/marker-utils.test.ts index 763cb817ee..19e2acb5a2 100644 --- a/src/test/unit/profile-query/marker-utils.test.ts +++ b/src/test/unit/profile-query/marker-utils.test.ts @@ -7,6 +7,7 @@ import { computeRateStats, collectMarkerInfo, collectMarkerStack, + collectProfileLogs, collectThreadMarkers, collectThreadNetwork, } from 'firefox-profiler/profile-query/formatters/marker-info'; @@ -1237,3 +1238,32 @@ describe('collectThreadNetwork', function () { expect(result.requests[0].markerHandle).toBe('m-1'); }); }); + +describe('collectProfileLogs', function () { + it('filters on the text of a message held as a string table index', function () { + // The fixtures intern `level` and `message`, so these payloads hold string + // table indexes like the ones Firefox emits now. The search filter runs + // against the message, so it has to be resolved first. + const { store, threadMap } = setupWithMarkers([ + [ + 'nsHttp', + 170, + null, + { type: 'Log', level: 'Error', message: 'ParentChannelListener' }, + ], + [ + 'nsJarProtocol', + 190, + null, + { type: 'Log', level: 'Debug', message: 'nsJARChannel::nsJARChannel' }, + ], + ]); + + const { entries } = collectProfileLogs(store, threadMap, { + search: 'nsJARChannel', + }); + expect(entries).toEqual([ + '1970-01-01 00:00:00.190000000 UTC - [Unknown Process 0: Empty]: D/nsJarProtocol nsJARChannel::nsJARChannel', + ]); + }); +}); diff --git a/src/test/unit/sanitize.test.ts b/src/test/unit/sanitize.test.ts index b7937b9b46..8ab505fbcc 100644 --- a/src/test/unit/sanitize.test.ts +++ b/src/test/unit/sanitize.test.ts @@ -31,6 +31,7 @@ import { bytesToBase64, } from 'firefox-profiler/utils/base64'; import { ValueSummaryReader } from 'devtools-reps'; +import { StringTable } from 'firefox-profiler/utils/string-table'; import type { MarkerSchemaByName, RawThread, @@ -40,7 +41,8 @@ import type { describe('sanitizePII', function () { function setup( piiConfig: Partial, - originalProfile = processGeckoProfile(createGeckoProfile()) + originalProfile = processGeckoProfile(createGeckoProfile()), + extraMarkerSchemas: MarkerSchemaByName = {} ) { const defaultsPii: RemoveProfileInformation = { shouldRemoveThreads: new Set(), @@ -136,6 +138,7 @@ describe('sanitizePII', function () { }, ], }, + ...extraMarkerSchemas, }; // Mirror what the `getTracedValuesBuffer` selector hands to `sanitizePII` @@ -160,6 +163,136 @@ describe('sanitizePII', function () { }; } + // Mirrors what Firefox emits now: the schema declares `name` as a unique + // string, so the payload holds a string table index. + const uniqueStringTextSchema: MarkerSchemaByName = { + Text: { + name: 'Text', + tableLabel: '{marker.name} — {marker.data.name}', + display: ['marker-chart', 'marker-table'], + fields: [{ key: 'name', label: 'Details', format: 'unique-string' }], + }, + }; + + function setupWithUniqueStringTextMarkers( + piiConfig: Partial, + markers: Array<[string, string]> + ) { + const profile = getProfileWithMarkers( + markers.map(([markerName, text]) => [ + markerName, + 0, + 1, + { type: 'Text', name: text }, + ]) + ); + // The fixtures use their own schema, where Text holds its text inline, so + // both the schema and the payloads are replaced here. + profile.meta.markerSchema = [uniqueStringTextSchema.Text]; + const stringTable = StringTable.withBackingArray( + profile.shared.stringArray + ); + profile.threads[0].markers.data = markers.map(([, text]) => ({ + type: 'Text', + name: stringTable.indexForString(text), + })); + + const { sanitizedProfile } = setup( + piiConfig, + profile, + uniqueStringTextSchema + ); + const { stringArray } = sanitizedProfile.shared; + return sanitizedProfile.threads[0].markers.data.map((data) => { + if (!data || data.type !== 'Text') { + throw new Error('Expected a Text marker'); + } + if (typeof data.name !== 'number') { + throw new Error( + 'Expected the text to be an index into the string table' + ); + } + return stringArray[data.name]; + }); + } + + it('should sanitize the URLs inside text markers holding a unique string', function () { + // URLs are removed from the whole string table, so nothing else is needed. + expect( + setupWithUniqueStringTextMarkers({ shouldRemoveUrls: true }, [ + [ + 'Extension Suspend', + 'onBeforeRequest https://profiler.firefox.com/ by extension', + ], + ]) + ).toEqual(['onBeforeRequest https:// by extension']); + }); + + it('should sanitize extension ids inside text markers holding a unique string', function () { + expect( + setupWithUniqueStringTextMarkers({ shouldRemoveExtensions: true }, [ + [ + 'ExtensionParent', + 'formautofill@mozilla.org, api_call: runtime.onUpdateAvailable.addListener', + ], + [ + 'ExtensionChild', + 'formautofill@mozilla.org, api_call: runtime.onUpdateAvailable.addListener', + ], + ]) + ).toEqual([ + 'api_call: runtime.onUpdateAvailable.addListener', + 'api_call: runtime.onUpdateAvailable.addListener', + ]); + }); + + it('should sanitize both URLs and extension ids inside text markers holding a unique string', function () { + expect( + setupWithUniqueStringTextMarkers( + { shouldRemoveUrls: true, shouldRemoveExtensions: true }, + [ + [ + 'Extension Suspend', + 'onBeforeRequest https://profiler.firefox.com/ by extension', + ], + ] + ) + ).toEqual(['onBeforeRequest https://']); + }); + + it('should not alter other strings when sanitizing a shared text marker string', function () { + // The string table is shared, so the sanitized text has to be interned as a + // new string instead of replacing an entry others may point to. + const text = + 'formautofill@mozilla.org, api_call: runtime.onUpdateAvailable.addListener'; + const profile = getProfileWithMarkers([ + ['ExtensionParent', 0, 1, { type: 'Text', name: text }], + ['SomeOtherMarker', 0, 1, { type: 'Text', name: text }], + ]); + profile.meta.markerSchema = [uniqueStringTextSchema.Text]; + const stringTable = StringTable.withBackingArray( + profile.shared.stringArray + ); + const textIndex = stringTable.indexForString(text); + profile.threads[0].markers.data = [ + { type: 'Text', name: textIndex }, + { type: 'Text', name: textIndex }, + ]; + + const { sanitizedProfile } = setup( + { shouldRemoveExtensions: true }, + profile, + uniqueStringTextSchema + ); + + const { stringArray } = sanitizedProfile.shared; + const [sanitized, untouched] = sanitizedProfile.threads[0].markers.data.map( + (data) => stringArray[(data as any).name] + ); + expect(sanitized).toBe('api_call: runtime.onUpdateAvailable.addListener'); + expect(untouched).toBe(text); + }); + it('should sanitize the threads if they are provided', function () { const { originalProfile, sanitizedProfile } = setup({ shouldRemoveThreads: new Set([0, 2]), diff --git a/src/types/markers.ts b/src/types/markers.ts index e3eb9217b5..25cac35a6a 100644 --- a/src/types/markers.ts +++ b/src/types/markers.ts @@ -624,7 +624,8 @@ export type UserTimingMarkerPayload = { export type TextMarkerPayload = { type: 'Text'; - name: string; + // A string table index in newer profiles, the text itself in older ones. + name: string | IndexIntoStringTable; cause?: CauseBacktrace; innerWindowID?: number; }; @@ -658,7 +659,8 @@ export type LogMarkerPayload = type: 'Log'; // String table index resolving to "Error", "Warning", "Info", "Debug", or "Verbose". level: number; - message: string; + // A string table index in newer profiles, the message itself in older ones. + message: string | IndexIntoStringTable; color?: string; }; diff --git a/src/utils/window-console.ts b/src/utils/window-console.ts index e88a025c4f..ac749ab519 100644 --- a/src/utils/window-console.ts +++ b/src/utils/window-console.ts @@ -28,6 +28,7 @@ import { formatLogTimestamp, formatLogStatement, } from 'firefox-profiler/profile-logic/marker-data'; +import { computeStringIndexMarkerFieldsByDataType } from 'firefox-profiler/profile-logic/marker-schema'; // Despite providing a good libdef for Object.defineProperty, Flow still // special-cases the `value` property: if it's missing it throws an error. Using @@ -287,6 +288,11 @@ export function addDataToWindowObject( const profile = selectorsForConsole.profile.getProfile(getState()); const range = selectorsForConsole.profile.getPreviewSelectionRange(getState()); + // The schema tells us which payload fields hold string table indexes. + const stringIndexMarkerFieldsByDataType = + computeStringIndexMarkerFieldsByDataType( + selectorsForConsole.profile.getMarkerSchema(getState()) + ); for (const thread of profile.threads) { const { markers } = thread; @@ -315,7 +321,8 @@ export function addDataToWindowObject( thread.name, data, moduleName, - stringArray + stringArray, + stringIndexMarkerFieldsByDataType ); if (statement !== null) { logs.push(statement);