Ctrl+F in the Performance panel does not match entries on custom (extension) tracks

#540020488 Filed 2026-07-29 P3 / S3

Repro steps

1. Open DevTools on this page, go to the Performance panel and start recording.

2. Click Emit the four probe entries below (it burns ~200 ms of main thread so the entries are wide and easy to hover).

3. Stop the recording. In the flame chart, above Main, you get a custom track group Ctrl+F repro with a single track Probes holding four wide, non-overlapping blocks.

4. Press Ctrl+F and search for needle-abc.

The four probes

Each probe puts the exact same string needle-abc in a different place. Only the first one is reachable by the current search implementation, which is what makes the bug obvious.

Entry name in the flame chartWhere needle-abc livesBefore fixAfter fix
1 name needle-abcperformance.measure() name (control)matchmatch
2 tooltip probedevtools.tooltipTextno matchmatch
3 properties probedevtools.propertiesno matchmatch
4 userDetail probedetail.source (user detail)no matchmatch
Match counter

The counter in the search bar is the quickest proof. Searching needle-abc gives 1 match on a build without the fix and 4 matches with it. Probe 1 is the control: it proves the custom track is being searched at all, so probes 2-4 failing is about the content tokens, not about the track being skipped.

How to see the needle in the UI

Hover probe 2 - the popover title is the tooltip text and shows needle-abc.

Click probe 3 or 4 - the summary drawer at the bottom shows a Source row containing needle-abc.

So the string is right there in the UI for all four entries, yet only probe 1 is findable with Ctrl+F.

Expected vs actual

 Behaviour
ExpectedAll four probes count as matches and stay highlighted while the rest of the flame chart is dimmed.
ActualOnly probe 1 matches. Probes 2-4 are dimmed exactly like non-matching entries, so it looks like the filter ran over the custom track, but their content was never actually searched.
Root cause

TimelineUIUtils.testContentMatching() builds its search tokens from the event title, the profile call frame, the non-resolved URL and event.args. Extension entries are synthetic events created in ExtensionTraceDataHandler and they carry no args object at all - the text the user sees lives on event.devtoolsObj (tooltipText, properties) and on event.userDetail. None of that was searched, so only the raw entry name was matchable.

Emit trace data

idle - start a Performance recording first, then click the button.

Instrumentation used

const NEEDLE = 'https://static.januschka.com/i-540020488/needle-abc.mjs';

// probe 2: needle only in the tooltip text
performance.measure('2 tooltip probe', {
  start, end,
  detail: {
    devtools: {
      dataType: 'track-entry',
      track: 'Probes',
      trackGroup: 'Ctrl+F repro',
      color: 'tertiary',
      tooltipText: NEEDLE,
    },
  },
});

// probe 3: needle only in the properties shown in the summary drawer
performance.measure('3 properties probe', {
  start, end,
  detail: {
    devtools: {
      dataType: 'track-entry',
      track: 'Probes',
      trackGroup: 'Ctrl+F repro',
      color: 'warning',
      properties: [['Source', NEEDLE]],
    },
  },
});

// probe 4: needle only in the user detail
performance.measure('4 userDetail probe', {
  start, end,
  source: NEEDLE,
  detail: {
    source: NEEDLE,
    devtools: {
      dataType: 'track-entry',
      track: 'Probes',
      trackGroup: 'Ctrl+F repro',
      color: 'error',
    },
  },
});

Links

crbug.com/540020488

TimelineUIUtils.ts (testContentMatching)

ExtensionTraceDataHandler.ts