1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {assertTrue} from '../base/logging'; 16import {PivotTree} from '../controller/pivot_table_redux_controller'; 17import {RecordConfig} from '../controller/record_config_types'; 18 19import { 20 AggregationAttrs, 21 PivotAttrs, 22 SubQueryAttrs, 23 TableAttrs 24} from './pivot_table_common'; 25 26/** 27 * A plain js object, holding objects of type |Class| keyed by string id. 28 * We use this instead of using |Map| object since it is simpler and faster to 29 * serialize for use in postMessage. 30 */ 31export interface ObjectById<Class extends{id: string}> { [id: string]: Class; } 32 33export type Timestamped<T> = { 34 [P in keyof T]: T[P]; 35}&{lastUpdate: number}; 36 37export type OmniboxMode = 'SEARCH'|'COMMAND'; 38 39export type OmniboxState = Timestamped<{omnibox: string; mode: OmniboxMode}>; 40 41export type VisibleState = 42 Timestamped<{startSec: number; endSec: number; resolution: number;}>; 43 44export interface AreaSelection { 45 kind: 'AREA'; 46 areaId: string; 47 // When an area is marked it will be assigned a unique note id and saved as 48 // an AreaNote for the user to return to later. id = 0 is the special id that 49 // is overwritten when a new area is marked. Any other id is a persistent 50 // marking that will not be overwritten. 51 // When not set, the area selection will be replaced with any 52 // new area selection (i.e. not saved anywhere). 53 noteId?: string; 54} 55 56export type AreaById = Area&{id: string}; 57 58export interface Area { 59 startSec: number; 60 endSec: number; 61 tracks: string[]; 62} 63 64export const MAX_TIME = 180; 65 66// 3: TrackKindPriority and related sorting changes. 67// 5: Move a large number of items off frontendLocalState and onto state. 68// 6: Common PivotTableConfig and pivot table specific PivotTableState. 69// 7: Split Chrome categories in two and add 'symbolize ksyms' flag. 70// 8: Rename several variables 71// "[...]HeapProfileFlamegraph[...]" -> "[...]Flamegraph[...]". 72// 9: Add a field to track last loaded recording profile name 73// 10: Change last loaded profile tracking type to accommodate auto-save. 74// 11: Rename updateChromeCategories to fetchChromeCategories. 75// 12: Add a field to cache mapping from UI track ID to trace track ID in order 76// to speed up flow arrows rendering. 77// 13: FlamegraphState changed to support area selection. 78// 14: Changed the type of uiTrackIdByTraceTrackId from `Map` to an object with 79// typed key/value because a `Map` does not preserve type during 80// serialisation+deserialisation. 81// 15: Added state for Pivot Table V2 82// 16: Added boolean tracking if the flamegraph modal was dismissed 83export const STATE_VERSION = 16; 84 85export const SCROLLING_TRACK_GROUP = 'ScrollingTracks'; 86 87export type EngineMode = 'WASM'|'HTTP_RPC'; 88 89export type NewEngineMode = 'USE_HTTP_RPC_IF_AVAILABLE'|'FORCE_BUILTIN_WASM'; 90 91export enum TrackKindPriority { 92 'MAIN_THREAD' = 0, 93 'RENDER_THREAD' = 1, 94 'GPU_COMPLETION' = 2, 95 'CHROME_IO_THREAD' = 3, 96 'CHROME_COMPOSITOR' = 4, 97 'ORDINARY' = 5 98} 99 100export type FlamegraphStateViewingOption = 101 'SPACE'|'ALLOC_SPACE'|'OBJECTS'|'ALLOC_OBJECTS'|'PERF_SAMPLES'; 102 103export interface CallsiteInfo { 104 id: number; 105 parentId: number; 106 depth: number; 107 name?: string; 108 totalSize: number; 109 selfSize: number; 110 mapping: string; 111 merged: boolean; 112 highlighted: boolean; 113 location?: string; 114} 115 116export interface TraceFileSource { 117 type: 'FILE'; 118 file: File; 119} 120 121export interface TraceArrayBufferSource { 122 type: 'ARRAY_BUFFER'; 123 buffer: ArrayBuffer; 124 title: string; 125 url?: string; 126 fileName?: string; 127 128 // |uuid| is set only when loading via ?local_cache_key=1234. When set, 129 // this matches global.state.traceUuid, with the exception of the following 130 // time window: When a trace T1 is loaded and the user loads another trace T2, 131 // this |uuid| will be == T2, but the globals.state.traceUuid will be 132 // temporarily == T1 until T2 has been loaded (consistently to what happens 133 // with all other state fields). 134 uuid?: string; 135 // if |localOnly| is true then the trace should not be shared or downloaded. 136 localOnly?: boolean; 137} 138 139export interface TraceUrlSource { 140 type: 'URL'; 141 url: string; 142} 143 144export interface TraceHttpRpcSource { 145 type: 'HTTP_RPC'; 146} 147 148export type TraceSource = 149 TraceFileSource|TraceArrayBufferSource|TraceUrlSource|TraceHttpRpcSource; 150 151export interface TrackState { 152 id: string; 153 engineId: string; 154 kind: string; 155 name: string; 156 labels?: string[]; 157 trackKindPriority: TrackKindPriority; 158 trackGroup?: string; 159 config: { 160 trackId?: number; 161 trackIds?: number[]; 162 }; 163} 164 165export interface TrackGroupState { 166 id: string; 167 engineId: string; 168 name: string; 169 collapsed: boolean; 170 tracks: string[]; // Child track ids. 171} 172 173export interface EngineConfig { 174 id: string; 175 mode?: EngineMode; // Is undefined until |ready| is true. 176 ready: boolean; 177 failed?: string; // If defined the engine has crashed with the given message. 178 source: TraceSource; 179} 180 181export interface QueryConfig { 182 id: string; 183 engineId: string; 184 query: string; 185} 186 187export interface PermalinkConfig { 188 requestId?: string; // Set by the frontend to request a new permalink. 189 hash?: string; // Set by the controller when the link has been created. 190 isRecordingConfig?: 191 boolean; // this permalink request is for a recording config only 192} 193 194export interface TraceTime { 195 startSec: number; 196 endSec: number; 197} 198 199export interface FrontendLocalState { 200 omniboxState: OmniboxState; 201 visibleState: VisibleState; 202} 203 204export interface Status { 205 msg: string; 206 timestamp: number; // Epoch in seconds (Date.now() / 1000). 207} 208 209export interface Note { 210 noteType: 'DEFAULT'; 211 id: string; 212 timestamp: number; 213 color: string; 214 text: string; 215} 216 217export interface AreaNote { 218 noteType: 'AREA'; 219 id: string; 220 areaId: string; 221 color: string; 222 text: string; 223} 224 225export interface NoteSelection { 226 kind: 'NOTE'; 227 id: string; 228} 229 230export interface SliceSelection { 231 kind: 'SLICE'; 232 id: number; 233} 234 235export interface CounterSelection { 236 kind: 'COUNTER'; 237 leftTs: number; 238 rightTs: number; 239 id: number; 240} 241 242export interface HeapProfileSelection { 243 kind: 'HEAP_PROFILE'; 244 id: number; 245 upid: number; 246 ts: number; 247 type: string; 248} 249 250export interface PerfSamplesSelection { 251 kind: 'PERF_SAMPLES'; 252 id: number; 253 upid: number; 254 ts: number; 255 type: string; 256} 257 258export interface FlamegraphState { 259 kind: 'FLAMEGRAPH_STATE'; 260 upids: number[]; 261 startNs: number; 262 endNs: number; 263 type: string; 264 viewingOption: FlamegraphStateViewingOption; 265 focusRegex: string; 266 expandedCallsite?: CallsiteInfo; 267} 268 269export interface CpuProfileSampleSelection { 270 kind: 'CPU_PROFILE_SAMPLE'; 271 id: number; 272 utid: number; 273 ts: number; 274} 275 276export interface ChromeSliceSelection { 277 kind: 'CHROME_SLICE'; 278 id: number; 279 table: string; 280} 281 282export interface ThreadStateSelection { 283 kind: 'THREAD_STATE'; 284 id: number; 285} 286 287type Selection = 288 (NoteSelection|SliceSelection|CounterSelection|HeapProfileSelection| 289 CpuProfileSampleSelection|ChromeSliceSelection|ThreadStateSelection| 290 AreaSelection|PerfSamplesSelection)&{trackId?: string}; 291export type SelectionKind = Selection['kind']; // 'THREAD_STATE' | 'SLICE' ... 292 293export interface LogsPagination { 294 offset: number; 295 count: number; 296} 297 298export interface RecordingTarget { 299 name: string; 300 os: TargetOs; 301} 302 303export interface AdbRecordingTarget extends RecordingTarget { 304 serial: string; 305} 306 307export interface Sorting { 308 column: string; 309 direction: 'DESC'|'ASC'; 310} 311 312export interface AggregationState { 313 id: string; 314 sorting?: Sorting; 315} 316 317export interface MetricsState { 318 availableMetrics?: string[]; // Undefined until list is loaded. 319 selectedIndex?: number; 320 requestedMetric?: string; // Unset after metric request is handled. 321} 322 323export interface PivotTableConfig { 324 availableColumns?: TableAttrs[]; // Undefined until list is loaded. 325 availableAggregations?: string[]; // Undefined until list is loaded. 326} 327 328export interface PivotTableState { 329 id: string; 330 name: string; 331 selectedPivots: PivotAttrs[]; 332 selectedAggregations: AggregationAttrs[]; 333 requestedAction?: // Unset after pivot table column request is handled. 334 {action: string, attrs?: SubQueryAttrs}; 335 isLoadingQuery: boolean; 336 traceTime?: TraceTime; 337 selectedTrackIds?: number[]; 338} 339 340// Auxiliary metadata needed to parse the query result, as well as to render it 341// correctly. Generated together with the text of query and passed without the 342// change to the query response. 343export interface PivotTableReduxQueryMetadata { 344 tableName: string; 345 pivotColumns: string[]; 346 aggregationColumns: string[]; 347} 348 349// Everything that's necessary to run the query for pivot table 350export interface PivotTableReduxQuery { 351 text: string; 352 metadata: PivotTableReduxQueryMetadata; 353} 354 355// Pivot table query result 356export interface PivotTableReduxResult { 357 // Hierarchical pivot structure on top of rows 358 tree: PivotTree; 359 // Copy of the query metadata from the request, bundled up with the query 360 // result to ensure the correct rendering. 361 metadata: PivotTableReduxQueryMetadata; 362} 363 364export interface PivotTableReduxState { 365 // Currently selected area, if null, pivot table is not going to be visible. 366 selectionArea: Area|null; 367 // Increasing identifier of the query request, used to avoid performing the 368 // same query more than once. 369 queryId: number; 370 // Query request 371 query: PivotTableReduxQuery|null; 372 // Query response 373 queryResult: PivotTableReduxResult|null; 374} 375 376export interface LoadedConfigNone { 377 type: 'NONE'; 378} 379 380export interface LoadedConfigAutomatic { 381 type: 'AUTOMATIC'; 382} 383 384export interface LoadedConfigNamed { 385 type: 'NAMED'; 386 name: string; 387} 388 389export type LoadedConfig = 390 LoadedConfigNone|LoadedConfigAutomatic|LoadedConfigNamed; 391 392export interface State { 393 // tslint:disable-next-line:no-any 394 [key: string]: any; 395 version: number; 396 nextId: number; 397 nextNoteId: number; 398 nextAreaId: number; 399 400 /** 401 * State of the ConfigEditor. 402 */ 403 recordConfig: RecordConfig; 404 displayConfigAsPbtxt: boolean; 405 lastLoadedConfig: LoadedConfig; 406 407 /** 408 * Open traces. 409 */ 410 newEngineMode: NewEngineMode; 411 engines: ObjectById<EngineConfig>; 412 traceTime: TraceTime; 413 traceUuid?: string; 414 trackGroups: ObjectById<TrackGroupState>; 415 tracks: ObjectById<TrackState>; 416 uiTrackIdByTraceTrackId: {[key: number]: string;}; 417 areas: ObjectById<AreaById>; 418 aggregatePreferences: ObjectById<AggregationState>; 419 visibleTracks: string[]; 420 scrollingTracks: string[]; 421 pinnedTracks: string[]; 422 debugTrackId?: string; 423 lastTrackReloadRequest?: number; 424 queries: ObjectById<QueryConfig>; 425 metrics: MetricsState; 426 permalink: PermalinkConfig; 427 notes: ObjectById<Note|AreaNote>; 428 status: Status; 429 currentSelection: Selection|null; 430 currentFlamegraphState: FlamegraphState|null; 431 logsPagination: LogsPagination; 432 traceConversionInProgress: boolean; 433 pivotTableConfig: PivotTableConfig; 434 pivotTable: ObjectById<PivotTableState>; 435 pivotTableRedux: PivotTableReduxState; 436 437 /** 438 * This state is updated on the frontend at 60Hz and eventually syncronised to 439 * the controller at 10Hz. When the controller sends state updates to the 440 * frontend the frontend has special logic to pick whichever version of this 441 * key is most up to date. 442 */ 443 frontendLocalState: FrontendLocalState; 444 445 // Show track perf debugging overlay 446 perfDebug: boolean; 447 448 // Show the sidebar extended 449 sidebarVisible: boolean; 450 451 // Hovered and focused events 452 hoveredUtid: number; 453 hoveredPid: number; 454 hoveredLogsTimestamp: number; 455 hoveredNoteTimestamp: number; 456 highlightedSliceId: number; 457 focusedFlowIdLeft: number; 458 focusedFlowIdRight: number; 459 pendingScrollId?: number; 460 461 searchIndex: number; 462 currentTab?: string; 463 464 /** 465 * Trace recording 466 */ 467 recordingInProgress: boolean; 468 recordingCancelled: boolean; 469 extensionInstalled: boolean; 470 flamegraphModalDismissed: boolean; 471 recordingTarget: RecordingTarget; 472 availableAdbDevices: AdbRecordingTarget[]; 473 lastRecordingError?: string; 474 recordingStatus?: string; 475 476 fetchChromeCategories: boolean; 477 chromeCategories: string[]|undefined; 478 analyzePageQuery?: string; 479} 480 481export const defaultTraceTime = { 482 startSec: 0, 483 endSec: 10, 484}; 485 486export declare type RecordMode = 487 'STOP_WHEN_FULL' | 'RING_BUFFER' | 'LONG_TRACE'; 488 489// 'Q','P','O' for Android, 'L' for Linux, 'C' for Chrome. 490export declare type TargetOs = 'S' | 'R' | 'Q' | 'P' | 'O' | 'C' | 'L' | 'CrOS'; 491 492export function isTargetOsAtLeast(target: RecordingTarget, osVersion: string) { 493 assertTrue(osVersion.length === 1); 494 return target.os >= osVersion; 495} 496 497export function isAndroidP(target: RecordingTarget) { 498 return target.os === 'P'; 499} 500 501export function isAndroidTarget(target: RecordingTarget) { 502 return ['Q', 'P', 'O'].includes(target.os); 503} 504 505export function isChromeTarget(target: RecordingTarget) { 506 return ['C', 'CrOS'].includes(target.os); 507} 508 509export function isCrOSTarget(target: RecordingTarget) { 510 return target.os === 'CrOS'; 511} 512 513export function isLinuxTarget(target: RecordingTarget) { 514 return target.os === 'L'; 515} 516 517export function isAdbTarget(target: RecordingTarget): 518 target is AdbRecordingTarget { 519 return !!(target as AdbRecordingTarget).serial; 520} 521 522export function hasActiveProbes(config: RecordConfig) { 523 const fieldsWithEmptyResult = 524 new Set<string>(['hpBlockClient', 'allAtraceApps']); 525 let key: keyof RecordConfig; 526 for (key in config) { 527 if (typeof (config[key]) === 'boolean' && config[key] === true && 528 !fieldsWithEmptyResult.has(key)) { 529 return true; 530 } 531 } 532 if (config.chromeCategoriesSelected.length > 0) { 533 return true; 534 } 535 return config.chromeHighOverheadCategoriesSelected.length > 0; 536} 537 538export function getDefaultRecordingTargets(): RecordingTarget[] { 539 return [ 540 {os: 'Q', name: 'Android Q+'}, 541 {os: 'P', name: 'Android P'}, 542 {os: 'O', name: 'Android O-'}, 543 {os: 'C', name: 'Chrome'}, 544 {os: 'CrOS', name: 'Chrome OS (system trace)'}, 545 {os: 'L', name: 'Linux desktop'} 546 ]; 547} 548 549export function getBuiltinChromeCategoryList(): string[] { 550 // List of static Chrome categories, last updated at 2021-09-09 from HEAD of 551 // Chromium's //base/trace_event/builtin_categories.h. 552 return [ 553 'accessibility', 554 'AccountFetcherService', 555 'android_webview', 556 'aogh', 557 'audio', 558 'base', 559 'benchmark', 560 'blink', 561 'blink.animations', 562 'blink.bindings', 563 'blink.console', 564 'blink.net', 565 'blink.resource', 566 'blink.user_timing', 567 'blink.worker', 568 'blink_gc', 569 'blink_style', 570 'Blob', 571 'browser', 572 'browsing_data', 573 'CacheStorage', 574 'Calculators', 575 'CameraStream', 576 'camera', 577 'cast_app', 578 'cast_perf_test', 579 'cast.mdns', 580 'cast.mdns.socket', 581 'cast.stream', 582 'cc', 583 'cc.debug', 584 'cdp.perf', 585 'chromeos', 586 'cma', 587 'compositor', 588 'content', 589 'content_capture', 590 'device', 591 'devtools', 592 'devtools.contrast', 593 'devtools.timeline', 594 'disk_cache', 595 'download', 596 'download_service', 597 'drm', 598 'drmcursor', 599 'dwrite', 600 'DXVA_Decoding', 601 'evdev', 602 'event', 603 'exo', 604 'extensions', 605 'explore_sites', 606 'FileSystem', 607 'file_system_provider', 608 'fonts', 609 'GAMEPAD', 610 'gpu', 611 'gpu.angle', 612 'gpu.capture', 613 'headless', 614 'hwoverlays', 615 'identity', 616 'ime', 617 'IndexedDB', 618 'input', 619 'io', 620 'ipc', 621 'Java', 622 'jni', 623 'jpeg', 624 'latency', 625 'latencyInfo', 626 'leveldb', 627 'loading', 628 'log', 629 'login', 630 'media', 631 'media_router', 632 'memory', 633 'midi', 634 'mojom', 635 'mus', 636 'native', 637 'navigation', 638 'net', 639 'netlog', 640 'offline_pages', 641 'omnibox', 642 'oobe', 643 'ozone', 644 'partition_alloc', 645 'passwords', 646 'p2p', 647 'page-serialization', 648 'paint_preview', 649 'pepper', 650 'PlatformMalloc', 651 'power', 652 'ppapi', 653 'ppapi_proxy', 654 'print', 655 'rail', 656 'renderer', 657 'renderer_host', 658 'renderer.scheduler', 659 'RLZ', 660 'safe_browsing', 661 'screenlock_monitor', 662 'segmentation_platform', 663 'sequence_manager', 664 'service_manager', 665 'ServiceWorker', 666 'sharing', 667 'shell', 668 'shortcut_viewer', 669 'shutdown', 670 'SiteEngagement', 671 'skia', 672 'sql', 673 'stadia_media', 674 'stadia_rtc', 675 'startup', 676 'sync', 677 'system_apps', 678 'test_gpu', 679 'thread_pool', 680 'toplevel', 681 'toplevel.flow', 682 'ui', 683 'v8', 684 'v8.execute', 685 'v8.wasm', 686 'ValueStoreFrontend::Backend', 687 'views', 688 'views.frame', 689 'viz', 690 'vk', 691 'wayland', 692 'webaudio', 693 'weblayer', 694 'WebCore', 695 'webrtc', 696 'xr', 697 'disabled-by-default-animation-worklet', 698 'disabled-by-default-audio', 699 'disabled-by-default-audio-worklet', 700 'disabled-by-default-base', 701 'disabled-by-default-blink.debug', 702 'disabled-by-default-blink.debug.display_lock', 703 'disabled-by-default-blink.debug.layout', 704 'disabled-by-default-blink.debug.layout.trees', 705 'disabled-by-default-blink.feature_usage', 706 'disabled-by-default-blink_gc', 707 'disabled-by-default-blink.image_decoding', 708 'disabled-by-default-blink.invalidation', 709 'disabled-by-default-cc', 710 'disabled-by-default-cc.debug', 711 'disabled-by-default-cc.debug.cdp-perf', 712 'disabled-by-default-cc.debug.display_items', 713 'disabled-by-default-cc.debug.picture', 714 'disabled-by-default-cc.debug.scheduler', 715 'disabled-by-default-cc.debug.scheduler.frames', 716 'disabled-by-default-cc.debug.scheduler.now', 717 'disabled-by-default-content.verbose', 718 'disabled-by-default-cpu_profiler', 719 'disabled-by-default-cpu_profiler.debug', 720 'disabled-by-default-devtools.screenshot', 721 'disabled-by-default-devtools.timeline', 722 'disabled-by-default-devtools.timeline.frame', 723 'disabled-by-default-devtools.timeline.inputs', 724 'disabled-by-default-devtools.timeline.invalidationTracking', 725 'disabled-by-default-devtools.timeline.layers', 726 'disabled-by-default-devtools.timeline.picture', 727 'disabled-by-default-file', 728 'disabled-by-default-fonts', 729 'disabled-by-default-gpu_cmd_queue', 730 'disabled-by-default-gpu.dawn', 731 'disabled-by-default-gpu.debug', 732 'disabled-by-default-gpu.decoder', 733 'disabled-by-default-gpu.device', 734 'disabled-by-default-gpu.service', 735 'disabled-by-default-gpu.vulkan.vma', 736 'disabled-by-default-histogram_samples', 737 'disabled-by-default-java-heap-profiler', 738 'disabled-by-default-layer-element', 739 'disabled-by-default-layout_shift.debug', 740 'disabled-by-default-lifecycles', 741 'disabled-by-default-loading', 742 'disabled-by-default-mediastream', 743 'disabled-by-default-memory-infra', 744 'disabled-by-default-memory-infra.v8.code_stats', 745 'disabled-by-default-mojom', 746 'disabled-by-default-net', 747 'disabled-by-default-network', 748 'disabled-by-default-paint-worklet', 749 'disabled-by-default-power', 750 'disabled-by-default-renderer.scheduler', 751 'disabled-by-default-renderer.scheduler.debug', 752 'disabled-by-default-sandbox', 753 'disabled-by-default-sequence_manager', 754 'disabled-by-default-sequence_manager.debug', 755 'disabled-by-default-sequence_manager.verbose_snapshots', 756 'disabled-by-default-skia', 757 'disabled-by-default-skia.gpu', 758 'disabled-by-default-skia.gpu.cache', 759 'disabled-by-default-skia.shaders', 760 'disabled-by-default-SyncFileSystem', 761 'disabled-by-default-system_stats', 762 'disabled-by-default-thread_pool_diagnostics', 763 'disabled-by-default-toplevel.ipc', 764 'disabled-by-default-user_action_samples', 765 'disabled-by-default-v8.compile', 766 'disabled-by-default-v8.cpu_profiler', 767 'disabled-by-default-v8.gc', 768 'disabled-by-default-v8.gc_stats', 769 'disabled-by-default-v8.ic_stats', 770 'disabled-by-default-v8.runtime', 771 'disabled-by-default-v8.runtime_stats', 772 'disabled-by-default-v8.runtime_stats_sampling', 773 'disabled-by-default-v8.stack_trace', 774 'disabled-by-default-v8.turbofan', 775 'disabled-by-default-v8.wasm.detailed', 776 'disabled-by-default-v8.wasm.turbofan', 777 'disabled-by-default-video_and_image_capture', 778 'disabled-by-default-viz.gpu_composite_time', 779 'disabled-by-default-viz.debug.overlay_planes', 780 'disabled-by-default-viz.hit_testing_flow', 781 'disabled-by-default-viz.overdraw', 782 'disabled-by-default-viz.quads', 783 'disabled-by-default-viz.surface_id_flow', 784 'disabled-by-default-viz.surface_lifetime', 785 'disabled-by-default-viz.triangles', 786 'disabled-by-default-webaudio.audionode', 787 'disabled-by-default-webrtc', 788 'disabled-by-default-worker.scheduler', 789 'disabled-by-default-xr.debug', 790 ]; 791} 792 793export function getContainingTrackId(state: State, trackId: string): null| 794 string { 795 const track = state.tracks[trackId]; 796 if (!track) { 797 return null; 798 } 799 const parentId = track.trackGroup; 800 if (!parentId) { 801 return null; 802 } 803 return parentId; 804} 805