chrome-devtools-frontend 1.0.1716465 → 1.0.1717196
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/devtools-model-management/SKILL.md +4 -4
- package/.agents/skills/devtools-setting-migration/SKILL.md +1 -1
- package/.agents/skills/devtools-ux-writing-refactor/SKILL.md +5 -6
- package/.agents/skills/devtools-verification/SKILL.md +9 -5
- package/.agents/skills/fix-tests/SKILL.md +1 -1
- package/.agents/skills/foundation-test-migration/SKILL.md +2 -2
- package/.agents/skills/ui-eng-vision-test-scaffolder/SKILL.md +4 -5
- package/docs/README.md +1 -1
- package/docs/contributing/README.md +3 -3
- package/docs/cookbook/dependencies.md +1 -1
- package/docs/get_the_code.md +35 -26
- package/dt +7 -0
- package/dt.bat +8 -0
- package/front_end/core/common/ParsedURL.ts +1 -1
- package/front_end/core/sdk/PageResourceLoader.ts +141 -21
- package/front_end/core/sdk/ResourceTreeModel.ts +34 -10
- package/front_end/generated/Deprecation.ts +1 -1
- package/front_end/generated/InspectorBackendCommands.ts +1 -0
- package/front_end/generated/protocol-mapping.d.ts +8 -1
- package/front_end/generated/protocol-proxy-api.d.ts +7 -1
- package/front_end/generated/protocol.ts +8 -1
- package/front_end/models/ai_assistance/README.md +2 -2
- package/front_end/models/ai_assistance/agents/NetworkAgent.snapshot.txt +2 -2
- package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +1 -1
- package/front_end/models/ai_assistance/contexts/RequestContext.snapshot.txt +2 -2
- package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +7 -0
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +9 -6
- package/front_end/models/ai_assistance/performance/AIQueries.ts +2 -1
- package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +62 -23
- package/front_end/models/bindings/ResourceScriptMapping.ts +21 -2
- package/front_end/models/stack_trace/StackTrace.ts +10 -0
- package/front_end/models/stack_trace/StackTraceImpl.ts +15 -0
- package/front_end/models/trace/Processor.ts +7 -9
- package/front_end/models/trace/extras/StackTraceForEvent.ts +1 -2
- package/front_end/models/trace/extras/TraceTree.ts +7 -2
- package/front_end/models/trace/handlers/AnimationFramesHandler.ts +4 -3
- package/front_end/models/trace/handlers/AsyncJSCallsHandler.ts +10 -9
- package/front_end/models/trace/handlers/ImagePaintingHandler.ts +6 -6
- package/front_end/models/trace/handlers/InitiatorsHandler.ts +6 -6
- package/front_end/models/trace/handlers/InvalidationsHandler.ts +2 -4
- package/front_end/models/trace/handlers/LayerTreeHandler.ts +3 -3
- package/front_end/models/trace/handlers/MetaHandler.ts +1 -1
- package/front_end/models/trace/handlers/NetworkRequestsHandler.ts +5 -5
- package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +9 -9
- package/front_end/models/trace/handlers/ScriptsHandler.ts +2 -2
- package/front_end/models/trace/handlers/UserInteractionsHandler.ts +1 -1
- package/front_end/models/trace/handlers/WarningsHandler.ts +3 -3
- package/front_end/models/trace/handlers/helpers.ts +8 -5
- package/front_end/models/trace/helpers/Timing.ts +17 -12
- package/front_end/models/trace/helpers/Trace.ts +41 -14
- package/front_end/models/trace/insights/DuplicatedJavaScript.ts +12 -2
- package/front_end/models/trace/lantern/core/NetworkAnalyzer.ts +15 -1
- package/front_end/models/trace/types/TraceEvents.ts +213 -143
- package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +46 -0
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +1 -1
- package/front_end/panels/application/AppManifestView.ts +26 -4
- package/front_end/panels/application/serviceWorkersView.css +1 -1
- package/front_end/panels/comments/CommentsPane.ts +81 -32
- package/front_end/panels/comments/commentsPane.css +42 -1
- package/front_end/panels/elements/DOMTreeElementWidget.ts +508 -747
- package/front_end/panels/elements/DOMTreeWidget.ts +418 -502
- package/front_end/panels/elements/StylePropertiesSection.ts +1 -1
- package/front_end/panels/layer_viewer/Layers3DView.ts +6 -6
- package/front_end/panels/lighthouse/LighthouseProtocolService.ts +3 -0
- package/front_end/panels/recorder/recordingView.css +1 -1
- package/front_end/panels/settings/SettingsScreen.ts +6 -1
- package/front_end/panels/sources/DebuggerPlugin.ts +1 -1
- package/front_end/panels/sources/SourcesPanel.ts +511 -374
- package/front_end/panels/sources/sourcesPanel.css +6 -31
- package/front_end/panels/timeline/CountersGraph.ts +5 -4
- package/front_end/panels/timeline/Initiators.ts +1 -1
- package/front_end/panels/timeline/TimelineFlameChartView.ts +17 -19
- package/front_end/panels/timeline/TimelineMiniMap.ts +11 -19
- package/front_end/panels/timeline/TimelinePanel.ts +90 -43
- package/front_end/panels/timeline/TimelineUIUtils.ts +2 -2
- package/front_end/panels/timeline/components/BreadcrumbsUI.ts +109 -87
- package/front_end/panels/timeline/components/LayoutShiftDetails.ts +3 -3
- package/front_end/panels/timeline/components/breadcrumbsUI.css +43 -24
- package/front_end/panels/timeline/overlays/components/TimeRangeOverlay.ts +21 -11
- package/front_end/panels/timeline/utils/EntryNodes.ts +1 -1
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/source-map-scopes-codec/README.chromium +2 -2
- package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts +2 -0
- package/front_end/third_party/source-map-scopes-codec/package/_dist/src/decode/decode.d.ts.map +1 -1
- package/front_end/third_party/source-map-scopes-codec/package/deno.json +1 -1
- package/front_end/third_party/source-map-scopes-codec/package/package.json +1 -1
- package/front_end/third_party/source-map-scopes-codec/package/src/codec.js +2 -2
- package/front_end/third_party/source-map-scopes-codec/package/src/codec.ts +2 -2
- package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +19 -1
- package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js.map +1 -1
- package/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.ts +26 -4
- package/front_end/ui/comments/CommentAnchorResolver.ts +2 -0
- package/front_end/ui/components/buttons/Button.ts +9 -1
- package/front_end/ui/components/buttons/button.css +4 -0
- package/front_end/ui/components/helpers/scheduled-render.ts +1 -1
- package/front_end/ui/legacy/GlassPane.ts +1 -1
- package/front_end/ui/legacy/SplitWidget.ts +1 -1
- package/front_end/ui/legacy/Toolbar.ts +1 -107
- package/front_end/ui/legacy/UIUtils.ts +133 -11
- package/front_end/ui/legacy/Widget.ts +5 -1
- package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +5 -2
- package/front_end/ui/legacy/components/object_ui/objectPopover.css +1 -1
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +47 -15
- package/front_end/ui/legacy/components/perf_ui/TimelineGrid.ts +1 -1
- package/front_end/ui/legacy/components/utils/Linkifier.ts +2 -2
- package/front_end/ui/legacy/theme_support/ThemeSupport.ts +6 -5
- package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
- package/front_end/ui/visual_logging/LoggingDriver.ts +4 -4
- package/package.json +3 -3
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
ResourceTreeModel,
|
|
19
19
|
} from './ResourceTreeModel.js';
|
|
20
20
|
import {cacheDisabledSettingDescriptor, enableRemoteFileLoadingSettingDescriptor} from './SDKSettings.js';
|
|
21
|
+
import {SecurityOrigin} from './SecurityOrigin.js';
|
|
21
22
|
import type {Target} from './Target.js';
|
|
22
23
|
import {TargetManager} from './TargetManager.js';
|
|
23
24
|
|
|
@@ -26,6 +27,17 @@ const UIStrings = {
|
|
|
26
27
|
* @description Error message for canceled source map loads.
|
|
27
28
|
*/
|
|
28
29
|
loadCanceledDueToReloadOf: 'Load canceled due to reload of inspected page',
|
|
30
|
+
/**
|
|
31
|
+
* @description Error message in the Developer resources panel when DevTools refuses to load a
|
|
32
|
+
* resource (e.g. a source map) for a frame that is no longer available and is cross-origin with
|
|
33
|
+
* the page.
|
|
34
|
+
*/
|
|
35
|
+
crossOriginFrameLoadBlocked: 'Can’t load resource for an unavailable cross-origin frame',
|
|
36
|
+
/**
|
|
37
|
+
* @description Error message in the Developer resources panel when an http(s) resource cannot be
|
|
38
|
+
* loaded because no target is available.
|
|
39
|
+
*/
|
|
40
|
+
noTargetForHttpLoad: 'Can’t load http(s) resource without a target',
|
|
29
41
|
} as const;
|
|
30
42
|
const str_ = i18n.i18n.registerUIStrings('core/sdk/PageResourceLoader.ts', UIStrings);
|
|
31
43
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
@@ -280,20 +292,34 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
280
292
|
if (isExtensionInitiator(initiator)) {
|
|
281
293
|
throw new Error('Invalid initiator');
|
|
282
294
|
}
|
|
283
|
-
const
|
|
295
|
+
const hasExplicitTarget = Boolean(initiator.target);
|
|
296
|
+
// Fall back to the primary page target when the initiator has no explicit target
|
|
297
|
+
// so that resources loaded for detached frames remain scoped to the primary page
|
|
298
|
+
// and go through target-based CSP checks instead of host bindings.
|
|
299
|
+
let effectiveInitiator: PageResourceLoadInitiator = initiator;
|
|
300
|
+
if (!initiator.target) {
|
|
301
|
+
const primaryTarget = this.#targetManager.primaryPageTarget();
|
|
302
|
+
if (primaryTarget) {
|
|
303
|
+
effectiveInitiator = {
|
|
304
|
+
...initiator,
|
|
305
|
+
target: primaryTarget,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const key = PageResourceLoader.makeKey(url, effectiveInitiator);
|
|
284
310
|
const pageResource: PageResource = {
|
|
285
311
|
success: null,
|
|
286
312
|
size: null,
|
|
287
313
|
duration: null,
|
|
288
314
|
url,
|
|
289
|
-
initiator,
|
|
315
|
+
initiator: effectiveInitiator,
|
|
290
316
|
};
|
|
291
317
|
this.#pageResources.set(key, pageResource);
|
|
292
318
|
this.dispatchEventToListeners(Events.UPDATE);
|
|
293
319
|
const startTime = performance.now();
|
|
294
320
|
try {
|
|
295
|
-
await this.acquireLoadSlot(
|
|
296
|
-
const resultPromise = this.dispatchLoad(url,
|
|
321
|
+
await this.acquireLoadSlot(effectiveInitiator.target);
|
|
322
|
+
const resultPromise = this.dispatchLoad(url, effectiveInitiator, isBinary, hasExplicitTarget);
|
|
297
323
|
const result = await resultPromise;
|
|
298
324
|
pageResource.errorMessage = result.errorDescription.message;
|
|
299
325
|
pageResource.success = result.success;
|
|
@@ -312,7 +338,7 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
312
338
|
throw e;
|
|
313
339
|
} finally {
|
|
314
340
|
pageResource.duration = performance.now() - startTime;
|
|
315
|
-
this.releaseLoadSlot(
|
|
341
|
+
this.releaseLoadSlot(effectiveInitiator.target);
|
|
316
342
|
this.dispatchEventToListeners(Events.UPDATE);
|
|
317
343
|
}
|
|
318
344
|
}
|
|
@@ -320,6 +346,8 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
320
346
|
#resolveFrameTarget(initiator: PageResourceLoadInitiator): {
|
|
321
347
|
frameTarget: Target|null,
|
|
322
348
|
frameId: Protocol.Page.FrameId|null,
|
|
349
|
+
initiatorFrameId: Protocol.Page.FrameId|null,
|
|
350
|
+
usedFallbackMainFrame: boolean,
|
|
323
351
|
} {
|
|
324
352
|
let frameTarget: Target|null = initiator.target;
|
|
325
353
|
let parentFrameId: Protocol.Page.FrameId|null = null;
|
|
@@ -327,21 +355,88 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
327
355
|
parentFrameId = parentFrameId ?? frameTarget.targetInfo()?.parentFrameId ?? null;
|
|
328
356
|
frameTarget = frameTarget.parentTarget();
|
|
329
357
|
}
|
|
330
|
-
const
|
|
331
|
-
|
|
358
|
+
const resourceTreeModel = frameTarget?.model(ResourceTreeModel);
|
|
359
|
+
const mainFrameId = resourceTreeModel?.mainFrame?.id ?? null;
|
|
360
|
+
|
|
361
|
+
// Normalize empty-string frame IDs (used by ScriptsHandler for unassociated scripts)
|
|
362
|
+
// to null and fall back to the parent frame ID discovered from a worker or subtarget.
|
|
363
|
+
let candidateFrameId: Protocol.Page.FrameId|null = null;
|
|
364
|
+
if (initiator.frameId) {
|
|
365
|
+
candidateFrameId = initiator.frameId;
|
|
366
|
+
} else if (parentFrameId) {
|
|
367
|
+
candidateFrameId = parentFrameId;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (frameTarget && frameTarget !== initiator.target && !candidateFrameId) {
|
|
371
|
+
// The initiator is a non-frame target (e.g. a service worker or a shared worker) that is not
|
|
372
|
+
// owned by any single frame. Don't attribute the load to the page's main frame, as the browser
|
|
373
|
+
// would then issue the request with the main frame's origin and credentials. Load through the
|
|
374
|
+
// initiator's own target instead, where the browser attributes the request to the worker's origin.
|
|
375
|
+
return {frameTarget: initiator.target, frameId: null, initiatorFrameId: null, usedFallbackMainFrame: false};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Keep candidateFrameId only if ResourceTreeModel has not populated frames yet
|
|
379
|
+
// (mainFrame is null) or if candidateFrameId still exists in the active frame tree.
|
|
380
|
+
let validFrameId: Protocol.Page.FrameId|null = null;
|
|
381
|
+
if (candidateFrameId) {
|
|
382
|
+
if (!resourceTreeModel?.mainFrame || resourceTreeModel.frameForId(candidateFrameId)) {
|
|
383
|
+
validFrameId = candidateFrameId;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Track whether we had to fall back to the main frame because the initiator's
|
|
388
|
+
// frame was missing or detached, so dispatchLoad can verify same-origin before
|
|
389
|
+
// issuing a network load in the main frame's context.
|
|
390
|
+
let usedFallbackMainFrame = false;
|
|
391
|
+
if (!validFrameId && mainFrameId) {
|
|
392
|
+
usedFallbackMainFrame = true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Use the validated frame ID if available; otherwise fall back to the main frame ID
|
|
396
|
+
// so that CDP's Network.loadNetworkResource and getSecurityIsolationStatus can
|
|
397
|
+
// enforce the page's CSP even after the original frame was detached.
|
|
398
|
+
const frameId = validFrameId ?? mainFrameId;
|
|
399
|
+
// Preserve the candidate frame ID (before mainFrameId fallback) so
|
|
400
|
+
// #isSameOriginWithPrimaryPage can detect detached or unassociated frames and
|
|
401
|
+
// disallow host-bindings fallback.
|
|
402
|
+
return {frameTarget, frameId, initiatorFrameId: candidateFrameId, usedFallbackMainFrame};
|
|
332
403
|
}
|
|
333
404
|
|
|
334
|
-
#isSameOriginWithPrimaryPage(
|
|
405
|
+
#isSameOriginWithPrimaryPage(
|
|
406
|
+
hasExplicitTarget: boolean,
|
|
407
|
+
frameTarget: Target|null,
|
|
408
|
+
initiatorFrameId: Protocol.Page.FrameId|null,
|
|
409
|
+
): boolean {
|
|
410
|
+
// Disallow host-bindings fallback when the initiator had no explicit target.
|
|
411
|
+
if (!hasExplicitTarget) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
335
414
|
const primaryFrame = this.#targetManager.primaryPageTarget()?.model(ResourceTreeModel)?.mainFrame;
|
|
336
|
-
|
|
415
|
+
// Look up the original initiator frame in frameTarget's active frame tree; if the frame
|
|
416
|
+
// was detached, frameForId returns null and we fail closed below.
|
|
417
|
+
const initiatorFrame =
|
|
418
|
+
initiatorFrameId ? frameTarget?.model(ResourceTreeModel)?.frameForId(initiatorFrameId) : null;
|
|
337
419
|
if (!primaryFrame || !initiatorFrame) {
|
|
338
420
|
return false;
|
|
339
421
|
}
|
|
340
422
|
return initiatorFrame.securityOrigin().isSameOriginWith(primaryFrame.securityOrigin());
|
|
341
423
|
}
|
|
342
424
|
|
|
343
|
-
|
|
344
|
-
|
|
425
|
+
#isInitiatorUrlSameOriginWithMainFrame(
|
|
426
|
+
initiator: PageResourceLoadInitiator,
|
|
427
|
+
frameTarget: Target|null,
|
|
428
|
+
): boolean {
|
|
429
|
+
const mainFrame = frameTarget?.model(ResourceTreeModel)?.mainFrame;
|
|
430
|
+
if (!mainFrame || !initiator.initiatorUrl) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
// Compare the target main frame's security origin against the initiator script's URL origin
|
|
434
|
+
// to ensure cross-origin detached frame requests are not loaded under mainFrame.id.
|
|
435
|
+
return mainFrame.securityOrigin().isSameOriginWith(SecurityOrigin.create(initiator.initiatorUrl));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private async dispatchLoad(url: Platform.DevToolsPath.UrlString, initiator: PageResourceLoadInitiator,
|
|
439
|
+
isBinary: boolean, hasExplicitTarget: boolean): Promise<{
|
|
345
440
|
success: boolean,
|
|
346
441
|
content: string|Uint8Array<ArrayBuffer>,
|
|
347
442
|
errorDescription: Host.ResourceLoader.LoadErrorDescription,
|
|
@@ -355,18 +450,32 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
355
450
|
return await this.#loadOverride(url);
|
|
356
451
|
}
|
|
357
452
|
const parsedURL = new Common.ParsedURL.ParsedURL(url);
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
453
|
+
const isHttp = Boolean(parsedURL && (parsedURL.scheme === 'http' || parsedURL.scheme === 'https'));
|
|
454
|
+
const loadThroughTarget = this.getLoadThroughTargetSetting().get();
|
|
455
|
+
const eligibleForLoadFromTarget =
|
|
456
|
+
Boolean(loadThroughTarget && parsedURL && parsedURL.scheme !== 'file' && parsedURL.scheme !== 'data' &&
|
|
457
|
+
parsedURL.scheme !== 'devtools' && initiator.target);
|
|
361
458
|
Host.userMetrics.developerResourceScheme(this.getDeveloperResourceScheme(parsedURL));
|
|
362
|
-
if (eligibleForLoadFromTarget) {
|
|
363
|
-
const isHttp = parsedURL.scheme === 'http' || parsedURL.scheme === 'https';
|
|
459
|
+
if (eligibleForLoadFromTarget && initiator.target) {
|
|
364
460
|
// Default to enforcing CSP for http(s) resources (fail-closed). Only relax
|
|
365
461
|
// if the isolation-status query positively confirms no connect-src/default-src
|
|
366
462
|
// directive is present. A null/error response keeps the guard armed so that a
|
|
367
463
|
// detached-frame or protocol-error path cannot fall through to loadFromHostBindings.
|
|
368
464
|
let mustEnforceCSP = isHttp;
|
|
369
|
-
const {frameTarget, frameId} = this.#resolveFrameTarget(initiator);
|
|
465
|
+
const {frameTarget, frameId, initiatorFrameId, usedFallbackMainFrame} = this.#resolveFrameTarget(initiator);
|
|
466
|
+
// Do not execute network loads under mainFrame.id (which uses the main frame's origin,
|
|
467
|
+
// cookies, and CSP) if a detached frame's initiatorUrl is cross-origin with the main frame.
|
|
468
|
+
if (usedFallbackMainFrame && !this.#isInitiatorUrlSameOriginWithMainFrame(initiator, frameTarget)) {
|
|
469
|
+
Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LOAD_THROUGH_PAGE_FAILURE);
|
|
470
|
+
return {
|
|
471
|
+
success: false,
|
|
472
|
+
content: '',
|
|
473
|
+
errorDescription: {
|
|
474
|
+
statusCode: 0,
|
|
475
|
+
message: i18nString(UIStrings.crossOriginFrameLoadBlocked),
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
370
479
|
if (isHttp && frameTarget) {
|
|
371
480
|
const networkManager = frameTarget.model(NetworkManager);
|
|
372
481
|
if (networkManager) {
|
|
@@ -385,7 +494,7 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
385
494
|
} catch (e) {
|
|
386
495
|
if (e instanceof Error) {
|
|
387
496
|
Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LOAD_THROUGH_PAGE_FAILURE);
|
|
388
|
-
if (mustEnforceCSP || !this.#isSameOriginWithPrimaryPage(frameTarget,
|
|
497
|
+
if (mustEnforceCSP || !this.#isSameOriginWithPrimaryPage(hasExplicitTarget, frameTarget, initiatorFrameId) ||
|
|
389
498
|
e.message.includes('CSP violation')) {
|
|
390
499
|
return {
|
|
391
500
|
success: false,
|
|
@@ -400,10 +509,21 @@ export class PageResourceLoader extends Common.ObjectWrapper.ObjectWrapper<Event
|
|
|
400
509
|
}
|
|
401
510
|
Host.userMetrics.developerResourceLoaded(Host.UserMetrics.DeveloperResourceLoaded.LOAD_THROUGH_PAGE_FALLBACK);
|
|
402
511
|
} else {
|
|
403
|
-
const code =
|
|
404
|
-
|
|
405
|
-
Host.UserMetrics.DeveloperResourceLoaded.FALLBACK_PER_OVERRIDE;
|
|
512
|
+
const code = loadThroughTarget ? Host.UserMetrics.DeveloperResourceLoaded.FALLBACK_PER_PROTOCOL :
|
|
513
|
+
Host.UserMetrics.DeveloperResourceLoaded.FALLBACK_PER_OVERRIDE;
|
|
406
514
|
Host.userMetrics.developerResourceLoaded(code);
|
|
515
|
+
// Fail closed for HTTP(S) loads when target-based loading is enabled but no target is available,
|
|
516
|
+
// preventing unauthenticated fallback to privileged host bindings.
|
|
517
|
+
if (loadThroughTarget && isHttp && !initiator.target) {
|
|
518
|
+
return {
|
|
519
|
+
success: false,
|
|
520
|
+
content: '',
|
|
521
|
+
errorDescription: {
|
|
522
|
+
statusCode: 0,
|
|
523
|
+
message: i18nString(UIStrings.noTargetForHttpLoad),
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
}
|
|
407
527
|
}
|
|
408
528
|
|
|
409
529
|
const result = await this.loadFromHostBindings(url);
|
|
@@ -32,6 +32,7 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
|
|
|
32
32
|
#pendingReloadOptions: {
|
|
33
33
|
ignoreCache: (boolean|undefined),
|
|
34
34
|
scriptToEvaluateOnLoad: (string|undefined),
|
|
35
|
+
loaderId: Protocol.Network.LoaderId,
|
|
35
36
|
}|null = null;
|
|
36
37
|
#reloadSuspensionCount = 0;
|
|
37
38
|
isInterstitialShowing = false;
|
|
@@ -209,6 +210,11 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
|
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
frameNavigated(framePayload: Protocol.Page.Frame, type: Protocol.Page.NavigationType|undefined): void {
|
|
213
|
+
this.#frameNavigated(framePayload, type, /* isDocumentOpened */ false);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#frameNavigated(framePayload: Protocol.Page.Frame, type: Protocol.Page.NavigationType|undefined,
|
|
217
|
+
isDocumentOpened: boolean): void {
|
|
212
218
|
const sameTargetParentFrame =
|
|
213
219
|
framePayload.parentId ? (this.framesInternal.get(framePayload.parentId) || null) : null;
|
|
214
220
|
// Do nothing unless cached resource tree is processed - it will overwrite everything.
|
|
@@ -245,6 +251,23 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
|
|
|
245
251
|
this.dispatchEventToListeners(Events.ResourceAdded, resources[i]);
|
|
246
252
|
}
|
|
247
253
|
|
|
254
|
+
// The document resource is normally added once the Network domain reports
|
|
255
|
+
// the document request as finished. Clients that run without the Network
|
|
256
|
+
// domain never get it, so inline scripts and styles can't be mapped to the
|
|
257
|
+
// document. Unless the request is still in flight, add the document
|
|
258
|
+
// resource right away, like `addFramesRecursively()` does for loaded frames.
|
|
259
|
+
// Documents created by `document.open()` have no request of their own and
|
|
260
|
+
// their content is not available.
|
|
261
|
+
const networkManager = this.target().model(NetworkManager);
|
|
262
|
+
if (!frame.getResourcesMap().get(framePayload.url as Platform.DevToolsPath.UrlString) &&
|
|
263
|
+
(isDocumentOpened || !networkManager?.requestForLoaderId(framePayload.loaderId))) {
|
|
264
|
+
const frameResource = this.createResourceFromFramePayload(
|
|
265
|
+
framePayload, framePayload.url as Platform.DevToolsPath.UrlString, Common.ResourceType.resourceTypes.Document,
|
|
266
|
+
framePayload.mimeType, null, null);
|
|
267
|
+
frameResource.isGenerated = isDocumentOpened;
|
|
268
|
+
frame.addResource(frameResource);
|
|
269
|
+
}
|
|
270
|
+
|
|
248
271
|
this.updateSecurityOrigins();
|
|
249
272
|
void this.updateStorageKeys();
|
|
250
273
|
|
|
@@ -265,17 +288,10 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
|
|
|
265
288
|
}
|
|
266
289
|
|
|
267
290
|
documentOpened(framePayload: Protocol.Page.Frame): void {
|
|
268
|
-
this
|
|
291
|
+
this.#frameNavigated(framePayload, undefined, /* isDocumentOpened */ true);
|
|
269
292
|
const frame = this.framesInternal.get(framePayload.id);
|
|
270
293
|
if (frame) {
|
|
271
294
|
this.dispatchEventToListeners(Events.DocumentOpened, frame);
|
|
272
|
-
if (!frame.getResourcesMap().get(framePayload.url)) {
|
|
273
|
-
const frameResource = this.createResourceFromFramePayload(
|
|
274
|
-
framePayload, framePayload.url as Platform.DevToolsPath.UrlString,
|
|
275
|
-
Common.ResourceType.resourceTypes.Document, framePayload.mimeType, null, null);
|
|
276
|
-
frameResource.isGenerated = true;
|
|
277
|
-
frame.addResource(frameResource);
|
|
278
|
-
}
|
|
279
295
|
}
|
|
280
296
|
}
|
|
281
297
|
|
|
@@ -434,7 +450,15 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
|
|
|
434
450
|
this.#reloadSuspensionCount--;
|
|
435
451
|
console.assert(this.#reloadSuspensionCount >= 0, 'Unbalanced call to ResourceTreeModel.resumeReload()');
|
|
436
452
|
if (!this.#reloadSuspensionCount && this.#pendingReloadOptions) {
|
|
437
|
-
const {ignoreCache, scriptToEvaluateOnLoad} = this.#pendingReloadOptions;
|
|
453
|
+
const {ignoreCache, scriptToEvaluateOnLoad, loaderId} = this.#pendingReloadOptions;
|
|
454
|
+
// A deferred reload (and its scriptToEvaluateOnLoad) only applies to the
|
|
455
|
+
// document it was requested against. If the main frame has since
|
|
456
|
+
// navigated to another document, drop the stale request instead of
|
|
457
|
+
// replaying it against the new document.
|
|
458
|
+
if (loaderId !== this.mainFrame?.loaderId) {
|
|
459
|
+
this.#pendingReloadOptions = null;
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
438
462
|
this.reloadPage(ignoreCache, scriptToEvaluateOnLoad);
|
|
439
463
|
}
|
|
440
464
|
}
|
|
@@ -449,7 +473,7 @@ export class ResourceTreeModel extends SDKModel<EventTypes> {
|
|
|
449
473
|
this.dispatchEventToListeners(Events.PageReloadRequested, this);
|
|
450
474
|
}
|
|
451
475
|
if (this.#reloadSuspensionCount) {
|
|
452
|
-
this.#pendingReloadOptions = {ignoreCache, scriptToEvaluateOnLoad};
|
|
476
|
+
this.#pendingReloadOptions = {ignoreCache, scriptToEvaluateOnLoad, loaderId};
|
|
453
477
|
return;
|
|
454
478
|
}
|
|
455
479
|
this.#pendingReloadOptions = null;
|
|
@@ -1180,6 +1180,7 @@ inspectorBackend.registerCommand("Page.waitForDebugger", [], [], "Pauses page ex
|
|
|
1180
1180
|
inspectorBackend.registerCommand("Page.setInterceptFileChooserDialog", [{"name": "enabled", "type": "boolean", "optional": false, "description": "", "typeRef": null}, {"name": "cancel", "type": "boolean", "optional": true, "description": "If true, cancels the dialog by emitting relevant events (if any) in addition to not showing it if the interception is enabled (default: false).", "typeRef": null}], [], "Intercept file chooser requests and transfer control to protocol clients. When file chooser interception is enabled, native file chooser dialog is not shown. Instead, a protocol event `Page.fileChooserOpened` is emitted.");
|
|
1181
1181
|
inspectorBackend.registerCommand("Page.setPrerenderingAllowed", [{"name": "isAllowed", "type": "boolean", "optional": false, "description": "", "typeRef": null}], [], "Enable/disable prerendering manually. This command is a short-term solution for https://crbug.com/1440085. See https://docs.google.com/document/d/12HVmFxYj5Jc-eJr5OmWsa2bqTJsbgGLKI6ZIyx0_wpA for more details. TODO(https://crbug.com/1440085): Remove this once Puppeteer supports tab targets.");
|
|
1182
1182
|
inspectorBackend.registerCommand("Page.getAnnotatedPageContent", [{"name": "includeActionableInformation", "type": "boolean", "optional": true, "description": "Whether to include actionable information. Defaults to true.", "typeRef": null}], ["content"], "Get the annotated page content for the main frame. This is an experimental command that is subject to change.");
|
|
1183
|
+
inspectorBackend.registerCommand("Page.setInputVisualization", [{"name": "clicks", "type": "boolean", "optional": true, "description": "Whether to enable click visualization.", "typeRef": null}], [], "Configures input visualization on the page.");
|
|
1183
1184
|
inspectorBackend.registerType("Page.AdFrameStatus", [{"name": "adFrameType", "type": "string", "optional": false, "description": "", "typeRef": "Page.AdFrameType"}, {"name": "explanations", "type": "array", "optional": true, "description": "", "typeRef": "Page.AdFrameExplanation"}]);
|
|
1184
1185
|
inspectorBackend.registerType("Page.PermissionsPolicyBlockLocator", [{"name": "frameId", "type": "string", "optional": false, "description": "", "typeRef": "Page.FrameId"}, {"name": "blockReason", "type": "string", "optional": false, "description": "", "typeRef": "Page.PermissionsPolicyBlockReason"}]);
|
|
1185
1186
|
inspectorBackend.registerType("Page.PermissionsPolicyFeatureState", [{"name": "feature", "type": "string", "optional": false, "description": "", "typeRef": "Page.PermissionsPolicyFeature"}, {"name": "allowed", "type": "boolean", "optional": false, "description": "", "typeRef": null}, {"name": "locator", "type": "object", "optional": true, "description": "", "typeRef": "Page.PermissionsPolicyBlockLocator"}]);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* This file is auto-generated, do not edit manually. *
|
|
7
|
-
* Re-generate with:
|
|
7
|
+
* Re-generate with: scripts/tools/dt.py generate-protocol-resources.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
|
|
@@ -4582,6 +4582,13 @@ export namespace ProtocolMapping {
|
|
|
4582
4582
|
paramsType: [Protocol.Page.GetAnnotatedPageContentRequest?];
|
|
4583
4583
|
returnType: Protocol.Page.GetAnnotatedPageContentResponse;
|
|
4584
4584
|
};
|
|
4585
|
+
/**
|
|
4586
|
+
* Configures input visualization on the page.
|
|
4587
|
+
*/
|
|
4588
|
+
'Page.setInputVisualization': {
|
|
4589
|
+
paramsType: [Protocol.Page.SetInputVisualizationRequest?];
|
|
4590
|
+
returnType: void;
|
|
4591
|
+
};
|
|
4585
4592
|
/**
|
|
4586
4593
|
* Disable collecting and reporting metrics.
|
|
4587
4594
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* This file is auto-generated, do not edit manually. *
|
|
7
|
-
* Re-generate with:
|
|
7
|
+
* Re-generate with: scripts/tools/dt.py generate-protocol-resources.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
|
|
@@ -2258,6 +2258,7 @@ declare namespace ProtocolProxyApi {
|
|
|
2258
2258
|
|
|
2259
2259
|
/**
|
|
2260
2260
|
* Return UUID of Blob object specified by a remote object id.
|
|
2261
|
+
* @deprecated
|
|
2261
2262
|
*/
|
|
2262
2263
|
invoke_resolveBlob(params: Protocol.IO.ResolveBlobRequest): Promise<Protocol.IO.ResolveBlobResponse>;
|
|
2263
2264
|
|
|
@@ -3688,6 +3689,11 @@ declare namespace ProtocolProxyApi {
|
|
|
3688
3689
|
*/
|
|
3689
3690
|
invoke_getAnnotatedPageContent(params: Protocol.Page.GetAnnotatedPageContentRequest): Promise<Protocol.Page.GetAnnotatedPageContentResponse>;
|
|
3690
3691
|
|
|
3692
|
+
/**
|
|
3693
|
+
* Configures input visualization on the page.
|
|
3694
|
+
*/
|
|
3695
|
+
invoke_setInputVisualization(params: Protocol.Page.SetInputVisualizationRequest): Promise<Protocol.ProtocolResponseWithError>;
|
|
3696
|
+
|
|
3691
3697
|
}
|
|
3692
3698
|
export interface PageDispatcher {
|
|
3693
3699
|
domContentEventFired(params: Protocol.Page.DomContentEventFiredEvent): void;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* This file is auto-generated, do not edit manually. *
|
|
7
|
-
* Re-generate with:
|
|
7
|
+
* Re-generate with: scripts/tools/dt.py generate-protocol-resources.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
|
|
@@ -16683,6 +16683,13 @@ export namespace Page {
|
|
|
16683
16683
|
content: binary;
|
|
16684
16684
|
}
|
|
16685
16685
|
|
|
16686
|
+
export interface SetInputVisualizationRequest {
|
|
16687
|
+
/**
|
|
16688
|
+
* Whether to enable click visualization.
|
|
16689
|
+
*/
|
|
16690
|
+
clicks?: boolean;
|
|
16691
|
+
}
|
|
16692
|
+
|
|
16686
16693
|
export interface DomContentEventFiredEvent {
|
|
16687
16694
|
timestamp: Network.MonotonicTime;
|
|
16688
16695
|
}
|
|
@@ -120,10 +120,10 @@ Once it's working, it's a good idea to manually experiment with the new Insight
|
|
|
120
120
|
|
|
121
121
|
## Debugging & Local Development
|
|
122
122
|
|
|
123
|
-
To test the AI code, you must be running DevTools in a branded Chrome (e.g. official Chrome Canary) and be signed in. You can use the
|
|
123
|
+
To test the AI code, you must be running DevTools in a branded Chrome (e.g. official Chrome Canary) and be signed in. You can use the `./dt start` command with a canary flag in this repository to run against Chromium, or by running Chrome Canary and pointing it to your local DevTools:
|
|
124
124
|
|
|
125
125
|
```
|
|
126
|
-
|
|
126
|
+
./dt start --browser=canary
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
or
|
|
@@ -10,7 +10,7 @@ Content:
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"title": "Response",
|
|
13
|
-
"text": "Response headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status:
|
|
13
|
+
"text": "Response headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status: finished\n"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"title": "Timing",
|
|
@@ -59,7 +59,7 @@ Content:
|
|
|
59
59
|
{
|
|
60
60
|
"parts": [
|
|
61
61
|
{
|
|
62
|
-
"text": "# Selected network request\nRequest: https://www.example.com\nRequest headers:\ncontent-type: bar1\n\nResponse headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status:
|
|
62
|
+
"text": "# Selected network request\nRequest: https://www.example.com\nRequest headers:\ncontent-type: bar1\n\nResponse headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status: finished\n\nRequest timing:\nQueued at (timestamp): 0 s\nStarted at (timestamp): 501 s\nQueueing (duration): 501 s\nConnection start (stalled) (duration): 800 ms\nRequest sent (duration): 100 ms\nDuration (duration): 501 s\n\nRequest initiator chain:\n- URL: <redacted cross-origin initiator URL>\n\t- URL: https://www.example.com\n\t\t- URL: https://www.example.com/1\n\t\t- URL: https://www.example.com/2\n\n# User request\n\ntest"
|
|
63
63
|
}
|
|
64
64
|
],
|
|
65
65
|
"role": 1
|
|
@@ -78,7 +78,7 @@ Content:
|
|
|
78
78
|
"properties": {
|
|
79
79
|
"code": {
|
|
80
80
|
"type": 1,
|
|
81
|
-
"description": "JavaScript code snippet to run on the inspected page. Make sure the code is formatted for readability.\n\n# Instructions\n\n* To return data, define a top-level `data` variable and populate it with data you want to get. Only JSON-serializable objects can be assigned to `data`.\n* $0 refers to the currently selected DOM node (or document body if none selected).\n* **CRITICAL** Never assume a selector for the elements unless you verified your knowledge.\n* **CRITICAL** Variables from previous function calls are not available in subsequent calls.\n* **CRITICAL** Keep code concise (max 40 lines and 2,500 characters). Split complex logic into multiple steps.\n* **CRITICAL** Network requests (e.g., fetch, XMLHttpRequest) are disabled and cannot be made.\n\nFor example, to query DOM elements:\n\n```\nconst data = {\n title: document.title,\n elementText: $0?.textContent?.trim(),\n childCount: $0?.children.length,\n};\n```\n"
|
|
81
|
+
"description": "JavaScript code snippet to run on the inspected page. Make sure the code is formatted for readability.\n\n# Instructions\n\n* To return data, define a top-level `data` variable and populate it with data you want to get. Only JSON-serializable objects can be assigned to `data`.\n* $0 refers to the currently selected DOM node (or document body if none selected).\n* **CRITICAL** Never assume a selector for the elements unless you verified your knowledge.\n* **CRITICAL** Variables from previous function calls are not available in subsequent calls.\n* **CRITICAL** Keep code concise (max 40 lines, 120 characters per line, and 2,500 characters). Split complex logic into multiple steps.\n* **CRITICAL** Network requests (e.g., fetch, XMLHttpRequest) are disabled and cannot be made.\n\nFor example, to query DOM elements:\n\n```\nconst data = {\n title: document.title,\n elementText: $0?.textContent?.trim(),\n childCount: $0?.children.length,\n};\n```\n"
|
|
82
82
|
},
|
|
83
83
|
"explanation": {
|
|
84
84
|
"type": 1,
|
|
@@ -12,7 +12,7 @@ Response body:
|
|
|
12
12
|
{}
|
|
13
13
|
|
|
14
14
|
Response status: 200
|
|
15
|
-
Network request status:
|
|
15
|
+
Network request status: finished
|
|
16
16
|
|
|
17
17
|
Request timing:
|
|
18
18
|
Queued at (timestamp): 0 s
|
|
@@ -33,7 +33,7 @@ Content:
|
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"title": "Response",
|
|
36
|
-
"text": "Response headers:\nContent-Type: application/json\n\nResponse body:\n{}\n\nResponse status: 200\nNetwork request status:
|
|
36
|
+
"text": "Response headers:\nContent-Type: application/json\n\nResponse body:\n{}\n\nResponse status: 200\nNetwork request status: finished\n"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"title": "Timing",
|
|
@@ -98,6 +98,13 @@ export class NetworkRequestFormatter {
|
|
|
98
98
|
|
|
99
99
|
static async formatBody(title: string, request: SDK.NetworkRequest.NetworkRequest, maxBodySize: number):
|
|
100
100
|
Promise<string> {
|
|
101
|
+
// requestContentData() waits until the request finishes. Some requests
|
|
102
|
+
// never finish: Chrome does not send loadingFinished for a
|
|
103
|
+
// `Cache-Control: no-store` fetch() whose body the page never reads.
|
|
104
|
+
if (!request.finished) {
|
|
105
|
+
return `${title}\n<not available: the request has not finished loading>`;
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
const data = await request.requestContentData();
|
|
102
109
|
|
|
103
110
|
if (TextUtils.ContentData.ContentData.isError(data)) {
|
|
@@ -472,8 +472,9 @@ export class PerformanceTraceFormatter {
|
|
|
472
472
|
description: this.#getSerializeBottomUpRootNodeFormat(limit),
|
|
473
473
|
empty: 'no activity',
|
|
474
474
|
cb: async insightSet => {
|
|
475
|
+
const navigationId = Trace.Helpers.Trace.navigationIDForEvent(insightSet.navigation);
|
|
475
476
|
const rootNode = AIQueries.mainThreadActivityBottomUpSingleNavigation(
|
|
476
|
-
|
|
477
|
+
navigationId,
|
|
477
478
|
insightSet.bounds,
|
|
478
479
|
parsedTrace,
|
|
479
480
|
);
|
|
@@ -519,8 +520,8 @@ export class PerformanceTraceFormatter {
|
|
|
519
520
|
title: 'Longest tasks',
|
|
520
521
|
empty: 'none',
|
|
521
522
|
cb: async insightSet => {
|
|
522
|
-
const
|
|
523
|
-
|
|
523
|
+
const navigationId = Trace.Helpers.Trace.navigationIDForEvent(insightSet.navigation);
|
|
524
|
+
const longestTaskTrees = AIQueries.longestTasks(navigationId, insightSet.bounds, parsedTrace, 3);
|
|
524
525
|
if (!longestTaskTrees?.length) {
|
|
525
526
|
return null;
|
|
526
527
|
}
|
|
@@ -585,7 +586,7 @@ export class PerformanceTraceFormatter {
|
|
|
585
586
|
if (Trace.Types.Events.isSyntheticNetworkRequest(event)) {
|
|
586
587
|
const safeUrl = filteredUrl(event, this.#parsedTrace);
|
|
587
588
|
if (safeUrl !== event.args.data.url) {
|
|
588
|
-
|
|
589
|
+
const syntheticEvent: Trace.Types.Events.SyntheticNetworkRequest = {
|
|
589
590
|
...event,
|
|
590
591
|
args: {
|
|
591
592
|
...event.args,
|
|
@@ -594,7 +595,8 @@ export class PerformanceTraceFormatter {
|
|
|
594
595
|
url: safeUrl,
|
|
595
596
|
},
|
|
596
597
|
},
|
|
597
|
-
}
|
|
598
|
+
};
|
|
599
|
+
return Trace.Name.forEntry(syntheticEvent);
|
|
598
600
|
}
|
|
599
601
|
}
|
|
600
602
|
return Trace.Name.forEntry(event);
|
|
@@ -609,8 +611,9 @@ export class PerformanceTraceFormatter {
|
|
|
609
611
|
|
|
610
612
|
const insightSet = this.#parsedTrace.insights?.values().find(
|
|
611
613
|
insightSet => Trace.Helpers.Timing.boundsIncludeTimeRange({bounds, timeRange: insightSet.bounds}));
|
|
614
|
+
const navigationId = Trace.Helpers.Trace.navigationIDForEvent(insightSet?.navigation);
|
|
612
615
|
const topDownTree = AIQueries.mainThreadActivityTopDown(
|
|
613
|
-
|
|
616
|
+
navigationId,
|
|
614
617
|
bounds,
|
|
615
618
|
this.#parsedTrace,
|
|
616
619
|
);
|
|
@@ -89,7 +89,8 @@ export class AIQueries {
|
|
|
89
89
|
const threads: Trace.Handlers.Threads.ThreadData[] = [];
|
|
90
90
|
if (parsedTrace.insights) {
|
|
91
91
|
for (const insightSet of parsedTrace.insights?.values()) {
|
|
92
|
-
const
|
|
92
|
+
const navigationId = Trace.Helpers.Trace.navigationIDForEvent(insightSet.navigation);
|
|
93
|
+
const thread = this.findMainThread(navigationId, parsedTrace);
|
|
93
94
|
if (thread) {
|
|
94
95
|
threads.push(thread);
|
|
95
96
|
}
|