From 5b2883b3631b95332562b5a40a2c09e4a50b419f Mon Sep 17 00:00:00 2001 From: "Christian W. Damus" Date: Tue, 10 Feb 2026 10:29:43 -0500 Subject: [PATCH] Add a hook to get early access to trace object before loading Perfetto plugins in host apps need to be able to access contextual information associated with the trace during loading of the trace and initialization of the plugin instance. This requires that a host app be able to associate information with the trace before the AppImpl's promise result from its various trace loading APIs is resolved at the completion of loading. Accordingly, add an optinoal call-back function parameter to the AppImpl load method implementations to provide this trace early access hook. For android-graphics/sokatoa#4633 Signed-off-by: Christian W. Damus --- ui/src/core/app_impl.ts | 125 +++++++++++++++++++++++++++++++++----- ui/src/core/load_trace.ts | 10 ++- 2 files changed, 120 insertions(+), 15 deletions(-) diff --git a/ui/src/core/app_impl.ts b/ui/src/core/app_impl.ts index 724cb3aafd4..9e758bf6a9d 100644 --- a/ui/src/core/app_impl.ts +++ b/ui/src/core/app_impl.ts @@ -31,7 +31,10 @@ import {CommandInvocation, CommandManagerImpl} from './command_manager'; import {embedderContext} from './embedder'; import {featureFlags} from './feature_flags'; import {loadTrace} from './load_trace'; -import {HierarchicalOmniboxManager, OmniboxManagerImpl} from './omnibox_manager'; +import { + HierarchicalOmniboxManager, + OmniboxManagerImpl, +} from './omnibox_manager'; import {PageManagerImpl} from './page_manager'; import {PerfManager} from './perf_manager'; import {PluginManagerImpl} from './plugin_manager'; @@ -282,34 +285,128 @@ export class AppImpl implements App { }; } - openTraceFromFile(file: File) { - return this.openTrace({type: 'FILE', file}); + openTraceFromFile(file: File): Promise; + openTraceFromFile( + file: File, + /** + * An optional call-back to be notified of the identity of the new trace as soon as it is created + * and before loading begins. Be careful how this is used: it may only be used for its identity + * because it is in an uninitialized state and loading it may yet fail. + */ + onTraceCreated?: (newTrace: TraceImpl) => void, + ): Promise; + openTraceFromFile( + file: File, + onTraceCreated?: (newTrace: TraceImpl) => void, + ) { + return this.openTrace({type: 'FILE', file}, onTraceCreated); } - openTraceFromMultipleFiles(files: ReadonlyArray) { - return this.openTrace({type: 'MULTIPLE_FILES', files}); + openTraceFromMultipleFiles(files: ReadonlyArray): Promise; + openTraceFromMultipleFiles( + files: ReadonlyArray, + /** + * An optional call-back to be notified of the identity of the new trace as soon as it is created + * and before loading begins. Be careful how this is used: it may only be used for its identity + * because it is in an uninitialized state and loading it may yet fail. + */ + onTraceCreated?: (newTrace: TraceImpl) => void, + ): Promise; + openTraceFromMultipleFiles( + files: ReadonlyArray, + onTraceCreated?: (newTrace: TraceImpl) => void, + ) { + return this.openTrace({type: 'MULTIPLE_FILES', files}, onTraceCreated); } - openTraceFromUrl(url: string, serializedAppState?: SerializedAppState) { - return this.openTrace({type: 'URL', url, serializedAppState}); + openTraceFromUrl( + url: string, + serializedAppState?: SerializedAppState, + ): Promise; + openTraceFromUrl( + url: string, + serializedAppState?: SerializedAppState, + /** + * An optional call-back to be notified of the identity of the new trace as soon as it is created + * and before loading begins. Be careful how this is used: it may only be used for its identity + * because it is in an uninitialized state and loading it may yet fail. + */ + onTraceCreated?: (newTrace: TraceImpl) => void, + ): Promise; + openTraceFromUrl( + url: string, + serializedAppState?: SerializedAppState, + onTraceCreated?: (newTrace: TraceImpl) => void, + ) { + return this.openTrace( + {type: 'URL', url, serializedAppState}, + onTraceCreated, + ); } - openTraceFromStream(stream: TraceStream) { - return this.openTrace({type: 'STREAM', stream}); + openTraceFromStream(stream: TraceStream): Promise; + openTraceFromStream( + stream: TraceStream, + /** + * An optional call-back to be notified of the identity of the new trace as soon as it is created + * and before loading begins. Be careful how this is used: it may only be used for its identity + * because it is in an uninitialized state and loading it may yet fail. + */ + onTraceCreated?: (newTrace: TraceImpl) => void, + ): Promise; + openTraceFromStream( + stream: TraceStream, + onTraceCreated?: (newTrace: TraceImpl) => void, + ) { + return this.openTrace({type: 'STREAM', stream}, onTraceCreated); } openTraceFromBuffer( args: OpenTraceArrayBufArgs, serializedAppState?: SerializedAppState, + ): Promise; + openTraceFromBuffer( + args: OpenTraceArrayBufArgs, + serializedAppState?: SerializedAppState, + /** + * An optional call-back to be notified of the identity of the new trace as soon as it is created + * and before loading begins. Be careful how this is used: it may only be used for its identity + * because it is in an uninitialized state and loading it may yet fail. + */ + onTraceCreated?: (newTrace: TraceImpl) => void, + ): Promise; + openTraceFromBuffer( + args: OpenTraceArrayBufArgs, + serializedAppState?: SerializedAppState, + onTraceCreated?: (newTrace: TraceImpl) => void, ) { - return this.openTrace({...args, type: 'ARRAY_BUFFER', serializedAppState}); + return this.openTrace( + {...args, type: 'ARRAY_BUFFER', serializedAppState}, + onTraceCreated, + ); } - openTraceFromHttpRpc(port?: string) { - return this.openTrace({type: 'HTTP_RPC', port}); + openTraceFromHttpRpc(port?: string): Promise; + openTraceFromHttpRpc( + port?: string, + /** + * An optional call-back to be notified of the identity of the new trace as soon as it is created + * and before loading begins. Be careful how this is used: it may only be used for its identity + * because it is in an uninitialized state and loading it may yet fail. + */ + onTraceCreated?: (newTrace: TraceImpl) => void, + ): Promise; + openTraceFromHttpRpc( + port?: string, + onTraceCreated?: (newTrace: TraceImpl) => void, + ) { + return this.openTrace({type: 'HTTP_RPC', port}, onTraceCreated); } - private async openTrace(src: TraceSource): Promise { + private async openTrace( + src: TraceSource, + onTraceCreated?: (newTrace: TraceImpl) => void, + ): Promise { if (src.type === 'ARRAY_BUFFER' && src.buffer instanceof Uint8Array) { // Even though the type of `buffer` is ArrayBuffer, it's possible to // accidentally pass a Uint8Array here, because the interface of @@ -351,7 +448,7 @@ export class AppImpl implements App { // - Call AppImpl.setActiveTrace(TraceImpl) // - Continue with the trace loading logic (track decider, plugins, etc) // - Resolve the promise when everything is done. - const trace = await loadTrace(this, src); + const trace = await loadTrace(this, src, onTraceCreated); this.omnibox.childFor(src).reset(/* focus= */ false); // loadTrace() internally will call setActiveTrace() and change our // _currentTrace in the middle of its execution. We cannot wait for diff --git a/ui/src/core/load_trace.ts b/ui/src/core/load_trace.ts index fad7bce2722..0c072e14fd0 100644 --- a/ui/src/core/load_trace.ts +++ b/ui/src/core/load_trace.ts @@ -127,11 +127,17 @@ let lastEngineId = 0; export async function loadTrace( app: AppImpl, traceSource: TraceSource, + /** + * An optional call-back to be notified of the identity of the new trace as soon as it is created + * and before loading begins. Be careful how this is used: it may only be used for its identity + * because it is in an uninitialized state and loading it may yet fail. + */ + onTraceCreated?: (newTrace: TraceImpl) => void, ): Promise { updateStatus(traceSource, app, 'Opening trace'); const engineId = `${++lastEngineId}`; const engine = await createEngine(app, engineId, traceSource); - return await loadTraceIntoEngine(app, traceSource, engine); + return await loadTraceIntoEngine(app, traceSource, engine, onTraceCreated); } async function createEngine( @@ -183,6 +189,7 @@ async function loadTraceIntoEngine( app: AppImpl, traceSource: TraceSource, engine: EngineBase, + onTraceCreated?: (newTrace: TraceImpl) => void, ): Promise { let traceStream: TraceStream | undefined; let serializedAppState = traceSource.serializedAppState; @@ -238,6 +245,7 @@ async function loadTraceIntoEngine( const traceDetails = await getTraceInfo(engine, app, traceSource); const trace = new TraceImpl(app, engine, traceDetails); + onTraceCreated?.(trace); app.setActiveTrace(trace); const visibleTimeSpan = await computeVisibleTime(