chrome-devtools-frontend 1.0.1516909 → 1.0.1519267
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/config/owner/COMMON_OWNERS +2 -2
- package/docs/checklist/README.md +2 -2
- package/docs/checklist/javascript.md +1 -1
- package/docs/contributing/README.md +1 -1
- package/docs/contributing/settings-experiments-features.md +9 -8
- package/docs/cookbook/devtools_on_devtools.md +2 -2
- package/docs/cookbook/localization.md +10 -10
- package/docs/devtools-protocol.md +9 -8
- package/docs/ecosystem/automatic_workspace_folders.md +3 -3
- package/docs/get_the_code.md +0 -2
- package/docs/styleguide/ux/components.md +166 -85
- package/docs/styleguide/ux/numbers.md +3 -4
- package/eslint.config.mjs +1 -0
- package/front_end/core/common/README.md +13 -12
- package/front_end/core/host/GdpClient.ts +16 -1
- package/front_end/core/host/UserMetrics.ts +4 -2
- package/front_end/core/root/Runtime.ts +13 -0
- package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
- package/front_end/core/sdk/EnhancedTracesParser.ts +5 -5
- package/front_end/core/sdk/RehydratingConnection.snapshot.txt +211 -0
- package/front_end/core/sdk/TargetManager.ts +4 -0
- package/front_end/entrypoints/main/MainImpl.ts +6 -3
- package/front_end/generated/InspectorBackendCommands.js +10 -7
- package/front_end/generated/SupportedCSSProperties.js +40 -11
- package/front_end/generated/protocol-mapping.d.ts +16 -1
- package/front_end/generated/protocol-proxy-api.d.ts +13 -1
- package/front_end/generated/protocol.ts +95 -0
- package/front_end/models/ai_assistance/agents/AiAgent.ts +57 -10
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +119 -51
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -31
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +19 -315
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
- package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
- package/front_end/models/ai_code_completion/AiCodeCompletion.ts +22 -11
- package/front_end/models/badges/AiExplorerBadge.ts +19 -3
- package/front_end/models/badges/Badge.ts +10 -3
- package/front_end/models/badges/CodeWhispererBadge.ts +3 -4
- package/front_end/models/badges/DOMDetectiveBadge.ts +1 -0
- package/front_end/models/badges/SpeedsterBadge.ts +1 -0
- package/front_end/models/badges/StarterBadge.ts +3 -2
- package/front_end/models/badges/UserBadges.ts +21 -3
- package/front_end/models/badges/badges.ts +1 -0
- package/front_end/models/javascript_metadata/NativeFunctions.js +2 -2
- package/front_end/models/trace/EventsSerializer.ts +4 -3
- package/front_end/models/trace/README.md +28 -1
- package/front_end/models/trace/handlers/UserInteractionsHandler.ts +101 -73
- package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
- package/front_end/models/trace/helpers/Timing.ts +1 -1
- package/front_end/models/trace/helpers/Trace.ts +99 -43
- package/front_end/models/trace/types/TraceEvents.ts +9 -0
- package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
- package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
- package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
- package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +20 -3
- package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
- package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +44 -0
- package/front_end/panels/application/components/BounceTrackingMitigationsView.ts +2 -2
- package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
- package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
- package/front_end/panels/common/BadgeNotification.ts +21 -5
- package/front_end/panels/common/GdpSignUpDialog.ts +20 -12
- package/front_end/panels/console/ConsolePrompt.ts +1 -1
- package/front_end/panels/console/ConsoleView.ts +6 -2
- package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +5 -5
- package/front_end/panels/elements/ElementsPanel.ts +4 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
- package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
- package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
- package/front_end/panels/media/TickingFlameChart.ts +1 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
- package/front_end/panels/recorder/components/RecordingView.ts +2 -2
- package/front_end/panels/search/SearchResultsPane.ts +167 -152
- package/front_end/panels/search/SearchView.ts +36 -26
- package/front_end/panels/search/searchResultsPane.css +9 -0
- package/front_end/panels/security/CookieControlsView.ts +2 -1
- package/front_end/panels/settings/AISettingsTab.ts +6 -3
- package/front_end/panels/settings/components/SyncSection.ts +39 -17
- package/front_end/panels/settings/emulation/components/UserAgentClientHintsForm.ts +1 -1
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +9 -1
- package/front_end/panels/sources/SourcesPanel.ts +4 -1
- package/front_end/panels/sources/sourcesView.css +6 -1
- package/front_end/panels/timeline/AppenderUtils.ts +2 -2
- package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
- package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
- package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
- package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
- package/front_end/panels/timeline/ThreadAppender.ts +12 -3
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
- package/front_end/panels/timeline/TimelinePanel.ts +3 -2
- package/front_end/panels/timeline/TimelineUIUtils.ts +5 -4
- package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
- package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
- package/front_end/panels/timeline/components/LayoutShiftDetails.ts +1 -1
- package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
- package/front_end/panels/timeline/components/NetworkRequestDetails.ts +1 -1
- package/front_end/panels/timeline/components/RelatedInsightChips.ts +1 -1
- package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
- package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
- package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
- package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
- package/front_end/third_party/codemirror.next/package.json +2 -1
- package/front_end/third_party/diff/README.chromium +1 -0
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js +16 -25
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +19 -28
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js +16 -25
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js.map +1 -1
- package/front_end/third_party/puppeteer/package/package.json +10 -3
- package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/Function.ts +22 -30
- package/front_end/ui/components/dialogs/Dialog.ts +1 -1
- package/front_end/ui/components/markdown_view/MarkdownImage.ts +4 -5
- package/front_end/ui/components/switch/SwitchImpl.ts +12 -1
- package/front_end/ui/components/text_editor/config.ts +22 -9
- package/front_end/ui/components/tooltips/Tooltip.ts +70 -31
- package/front_end/ui/legacy/README.md +33 -24
- package/front_end/ui/legacy/SearchableView.ts +19 -26
- package/front_end/ui/legacy/TextPrompt.ts +166 -1
- package/front_end/ui/legacy/Treeoutline.ts +19 -3
- package/front_end/ui/legacy/UIUtils.ts +15 -2
- package/front_end/ui/legacy/XElement.ts +0 -43
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
- package/front_end/ui/legacy/components/source_frame/XMLView.ts +12 -11
- package/front_end/ui/lit/i18n-template.ts +5 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +23 -6
- package/front_end/ui/visual_logging/README.md +43 -27
- package/package.json +1 -1
@@ -4,9 +4,10 @@
|
|
4
4
|
|
5
5
|
import * as Common from '../../core/common/common.js';
|
6
6
|
import * as Host from '../../core/host/host.js';
|
7
|
+
import * as Root from '../../core/root/root.js';
|
7
8
|
|
8
9
|
import {AiExplorerBadge} from './AiExplorerBadge.js';
|
9
|
-
import type {Badge, BadgeAction, BadgeActionEvents, BadgeContext} from './Badge.js';
|
10
|
+
import type {Badge, BadgeAction, BadgeActionEvents, BadgeContext, TriggerOptions} from './Badge.js';
|
10
11
|
import {CodeWhispererBadge} from './CodeWhispererBadge.js';
|
11
12
|
import {DOMDetectiveBadge} from './DOMDetectiveBadge.js';
|
12
13
|
import {SpeedsterBadge} from './SpeedsterBadge.js';
|
@@ -24,6 +25,7 @@ export interface EventTypes {
|
|
24
25
|
|
25
26
|
const SNOOZE_TIME_MS = 24 * 60 * 60 * 1000; // 24 hours
|
26
27
|
const MAX_SNOOZE_COUNT = 3;
|
28
|
+
const DELAY_BEFORE_TRIGGER = 1500;
|
27
29
|
|
28
30
|
let userBadgesInstance: UserBadges|undefined = undefined;
|
29
31
|
export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
@@ -48,6 +50,10 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
48
50
|
super();
|
49
51
|
|
50
52
|
this.#receiveBadgesSetting = Common.Settings.Settings.instance().moduleSetting('receive-gdp-badges');
|
53
|
+
if (Host.GdpClient.getGdpProfilesEnterprisePolicy() ===
|
54
|
+
Root.Runtime.GdpProfilesEnterprisePolicyValue.ENABLED_WITHOUT_BADGES) {
|
55
|
+
this.#receiveBadgesSetting.set(false);
|
56
|
+
}
|
51
57
|
this.#receiveBadgesSetting.addChangeListener(this.#reconcileBadges, this);
|
52
58
|
|
53
59
|
this.#starterBadgeSnoozeCount = Common.Settings.Settings.instance().createSetting(
|
@@ -93,7 +99,8 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
93
99
|
this.#badgeActionEventTarget.dispatchEventToListeners(action);
|
94
100
|
}
|
95
101
|
|
96
|
-
async #onTriggerBadge(badge: Badge): Promise<void> {
|
102
|
+
async #onTriggerBadge(badge: Badge, opts?: TriggerOptions): Promise<void> {
|
103
|
+
const triggerTime = Date.now();
|
97
104
|
let shouldAwardBadge = false;
|
98
105
|
// By default, we award non-starter badges directly when they are triggered.
|
99
106
|
if (!badge.isStarterBadge) {
|
@@ -116,7 +123,12 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
116
123
|
}
|
117
124
|
}
|
118
125
|
|
119
|
-
|
126
|
+
const timeElapsedAfterTriggerCall = Date.now() - triggerTime;
|
127
|
+
// We want to add exactly 1.5 second delay between the trigger action & the notification.
|
128
|
+
const delay = opts?.immediate ? 0 : Math.max(DELAY_BEFORE_TRIGGER - timeElapsedAfterTriggerCall, 0);
|
129
|
+
setTimeout(() => {
|
130
|
+
this.dispatchEventToListeners(Events.BADGE_TRIGGERED, badge);
|
131
|
+
}, delay);
|
120
132
|
}
|
121
133
|
|
122
134
|
#deactivateAllBadges(): void {
|
@@ -145,6 +157,12 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
145
157
|
return;
|
146
158
|
}
|
147
159
|
|
160
|
+
if (!Host.GdpClient.isGdpProfilesAvailable() ||
|
161
|
+
Host.GdpClient.getGdpProfilesEnterprisePolicy() !== Root.Runtime.GdpProfilesEnterprisePolicyValue.ENABLED) {
|
162
|
+
this.#deactivateAllBadges();
|
163
|
+
return;
|
164
|
+
}
|
165
|
+
|
148
166
|
const [gdpProfile, isEligibleToCreateProfile] = await Promise.all([
|
149
167
|
Host.GdpClient.GdpClient.instance().getProfile(),
|
150
168
|
Host.GdpClient.GdpClient.instance().isEligibleToCreateProfile(),
|
@@ -7978,7 +7978,7 @@ export const NativeFunctions = [
|
|
7978
7978
|
},
|
7979
7979
|
{
|
7980
7980
|
name: "constant",
|
7981
|
-
signatures: [["tensor"],["desc","buffer"]]
|
7981
|
+
signatures: [["tensor"],["desc","buffer"],["type","value"]]
|
7982
7982
|
},
|
7983
7983
|
{
|
7984
7984
|
name: "argMin",
|
@@ -8470,7 +8470,7 @@ export const NativeFunctions = [
|
|
8470
8470
|
},
|
8471
8471
|
{
|
8472
8472
|
name: "submitPrintJob",
|
8473
|
-
signatures: [["job_name","
|
8473
|
+
signatures: [["job_name","document_data","attributes"]]
|
8474
8474
|
},
|
8475
8475
|
{
|
8476
8476
|
name: "PushEvent",
|
@@ -39,7 +39,7 @@ export class EventsSerializer {
|
|
39
39
|
if (EventsSerializer.isLegacyTimelineFrameKey(eventValues)) {
|
40
40
|
const event = parsedTrace.data.Frames.frames.at(eventValues.rawIndex);
|
41
41
|
if (!event) {
|
42
|
-
throw new Error(`Could not find frame with index ${eventValues.rawIndex}`);
|
42
|
+
throw new Error(`Unknown trace event. Could not find frame with index ${eventValues.rawIndex}`);
|
43
43
|
}
|
44
44
|
return event;
|
45
45
|
}
|
@@ -48,7 +48,8 @@ export class EventsSerializer {
|
|
48
48
|
const syntheticEvents = Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager().getSyntheticTraces();
|
49
49
|
const syntheticEvent = syntheticEvents.at(eventValues.rawIndex);
|
50
50
|
if (!syntheticEvent) {
|
51
|
-
throw new Error(`Attempted to get a synthetic event from an unknown raw event index: ${
|
51
|
+
throw new Error(`Unknown trace event. Attempted to get a synthetic event from an unknown raw event index: ${
|
52
|
+
eventValues.rawIndex}`);
|
52
53
|
}
|
53
54
|
return syntheticEvent;
|
54
55
|
}
|
@@ -57,7 +58,7 @@ export class EventsSerializer {
|
|
57
58
|
const rawEvents = Helpers.SyntheticEvents.SyntheticEventsManager.getActiveManager().getRawTraceEvents();
|
58
59
|
return rawEvents[eventValues.rawIndex];
|
59
60
|
}
|
60
|
-
throw new Error(`Unknown trace event
|
61
|
+
throw new Error(`Unknown trace event. Serializable key values: ${(eventValues as unknown[]).join('-')}`);
|
61
62
|
}
|
62
63
|
|
63
64
|
static isProfileCallKey(key: Types.File.SerializableKeyValues): key is Types.File.ProfileCallKeyValues {
|
@@ -31,7 +31,7 @@ This folder contains the new trace engine that was first implemented for the Per
|
|
31
31
|
│
|
32
32
|
│
|
33
33
|
┌──────────────────▼─────────────────┐
|
34
|
-
│const data = model.parsedTrace()│
|
34
|
+
│const data = model.parsedTrace() │
|
35
35
|
└────────────────────────────────────┘
|
36
36
|
```
|
37
37
|
|
@@ -98,3 +98,30 @@ The object returned from `parsedTrace()` is an object of key-value pairs where e
|
|
98
98
|
// and so on for each enabled Handler
|
99
99
|
}
|
100
100
|
```
|
101
|
+
|
102
|
+
## Pairing begin & end events
|
103
|
+
|
104
|
+
Note: this detail is not useful if you are using the Trace Engine, but it is if you are working on it.
|
105
|
+
|
106
|
+
Trace events are often emitted as `begin` & `end` events to represent the lifetime of the event. These have the `b` and `e` phase.
|
107
|
+
|
108
|
+
When we find these events, we often try to pair them into what we call a "Synthetic" event. This is a trace event that doesn't exist in the raw trace, but one that we create to make it easier to deal with. This means we can represent a `b` & `e` pair as a single event rather than pass two events around.
|
109
|
+
|
110
|
+
When we pair these events, we look for an ID. Some events have a top level `id` field, others have a nested `id2.local` field (this is for various historical reasons). `getSyntheticId` in `Trace.Helpers` takes care of this, and tries to account for potential collisions by appending a few other pieces of metadata onto the ID.
|
111
|
+
|
112
|
+
This approach worked well until July 2025 when an upstream change in Perfetto [https://chromium.googlesource.com/external/github.com/google/perfetto.git/+/aef636b27ffbf379fd722e7798030da2c5c4d699] meant that Perfetto will try to minimise the amount of unique IDs it uses. A consequence of this change is that IDs can be reused by consecutive, non-overlapping events.
|
113
|
+
|
114
|
+
For example, take the following set of events:
|
115
|
+
|
116
|
+
```
|
117
|
+
=== E1 === === E2 ===
|
118
|
+
```
|
119
|
+
|
120
|
+
These could have the same ID, because they do not overlap.
|
121
|
+
|
122
|
+
Whereas these events will have different IDs because otherwise you cannot reliably pair them up:
|
123
|
+
|
124
|
+
```
|
125
|
+
=== E1 ===
|
126
|
+
=== E2 ===
|
127
|
+
```
|
@@ -2,6 +2,7 @@
|
|
2
2
|
// Use of this source code is governed by a BSD-style license that can be
|
3
3
|
// found in the LICENSE file.
|
4
4
|
|
5
|
+
import * as Platform from '../../../core/platform/platform.js';
|
5
6
|
import * as Helpers from '../helpers/helpers.js';
|
6
7
|
import * as Types from '../types/types.js';
|
7
8
|
|
@@ -56,15 +57,15 @@ let longestInteractionEvent: Types.Events.SyntheticInteractionPair|null = null;
|
|
56
57
|
|
57
58
|
let interactionEvents: Types.Events.SyntheticInteractionPair[] = [];
|
58
59
|
let interactionEventsWithNoNesting: Types.Events.SyntheticInteractionPair[] = [];
|
59
|
-
let eventTimingEndEventsById = new Map<string, Types.Events.EventTimingEnd>();
|
60
60
|
let eventTimingStartEventsForInteractions: Types.Events.EventTimingBegin[] = [];
|
61
|
+
let eventTimingEndEventsForInteractions: Types.Events.EventTimingEnd[] = [];
|
61
62
|
|
62
63
|
export function reset(): void {
|
63
64
|
beginCommitCompositorFrameEvents = [];
|
64
65
|
parseMetaViewportEvents = [];
|
65
66
|
interactionEvents = [];
|
66
67
|
eventTimingStartEventsForInteractions = [];
|
67
|
-
|
68
|
+
eventTimingEndEventsForInteractions = [];
|
68
69
|
interactionEventsWithNoNesting = [];
|
69
70
|
longestInteractionEvent = null;
|
70
71
|
}
|
@@ -86,7 +87,7 @@ export function handleEvent(event: Types.Events.Event): void {
|
|
86
87
|
|
87
88
|
if (Types.Events.isEventTimingEnd(event)) {
|
88
89
|
// Store the end event; for each start event that is an interaction, we need the matching end event to calculate the duration correctly.
|
89
|
-
|
90
|
+
eventTimingEndEventsForInteractions.push(event);
|
90
91
|
}
|
91
92
|
|
92
93
|
// From this point on we want to find events that represent interactions.
|
@@ -167,9 +168,18 @@ export function categoryOfInteraction(interaction: Types.Events.SyntheticInterac
|
|
167
168
|
* =======B=[keyup]=====
|
168
169
|
* ====C=[pointerdown]=
|
169
170
|
* =D=[pointerup]=
|
171
|
+
*
|
172
|
+
* Additionally, this method will also maximise the processing duration of the
|
173
|
+
* events that we keep as non-nested. We want to make sure we give an accurate
|
174
|
+
* representation of main thread activity, so if we keep an event + hide its
|
175
|
+
* nested children, we set the top level event's processing start &
|
176
|
+
* processing end to be the earliest processing start & the latest processing
|
177
|
+
* end of its children. This ensures we report a more accurate main thread
|
178
|
+
* activity time which is important as we want developers to focus on fixing
|
179
|
+
* this.
|
170
180
|
**/
|
171
|
-
export function
|
172
|
-
readonly Types.Events.SyntheticInteractionPair[] {
|
181
|
+
export function removeNestedInteractionsAndSetProcessingTime(
|
182
|
+
interactions: readonly Types.Events.SyntheticInteractionPair[]): readonly Types.Events.SyntheticInteractionPair[] {
|
173
183
|
/**
|
174
184
|
* Because we nest events only that are in the same category, we store the
|
175
185
|
* longest event for a given end time by category.
|
@@ -257,77 +267,95 @@ function writeSyntheticTimespans(event: Types.Events.SyntheticInteractionPair):
|
|
257
267
|
export async function finalize(): Promise<void> {
|
258
268
|
const {navigationsByFrameId} = metaHandlerData();
|
259
269
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
270
|
+
const beginAndEndEvents = Platform.ArrayUtilities.mergeOrdered(
|
271
|
+
eventTimingStartEventsForInteractions, eventTimingEndEventsForInteractions,
|
272
|
+
Helpers.Trace.eventTimeComparator) as Types.Events.EventTimingBeginOrEnd[];
|
273
|
+
|
274
|
+
// Pair up the begin & end events and create synthetic user timing events.
|
275
|
+
const beginEventById = new Map<string, Types.Events.EventTimingBegin[]>();
|
276
|
+
for (const event of beginAndEndEvents) {
|
277
|
+
if (Types.Events.isEventTimingStart(event)) {
|
278
|
+
const forId = beginEventById.get(event.id) ?? [];
|
279
|
+
forId.push(event);
|
280
|
+
beginEventById.set(event.id, forId);
|
281
|
+
} else if (Types.Events.isEventTimingEnd(event)) {
|
282
|
+
const beginEvents = beginEventById.get(event.id) ?? [];
|
283
|
+
const beginEvent = beginEvents.pop();
|
284
|
+
if (!beginEvent) {
|
285
|
+
continue;
|
286
|
+
}
|
287
|
+
const {type, interactionId, timeStamp, processingStart, processingEnd} = beginEvent.args.data;
|
288
|
+
if (!type || !interactionId || !timeStamp || !processingStart || !processingEnd) {
|
289
|
+
// A valid interaction event that we care about has to have a type (e.g. pointerdown, keyup).
|
290
|
+
// We also need to ensure it has an interactionId and various timings. There are edge cases where these aren't included in the trace event.
|
291
|
+
continue;
|
292
|
+
}
|
293
|
+
// In the future we will add microsecond timestamps to the trace events…
|
294
|
+
// (See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/timing/window_performance.cc;l=900-901;drc=b503c262e425eae59ced4a80d59d176ed07152c7 )
|
295
|
+
// …but until then we can use the millisecond precision values that are in
|
296
|
+
// the trace event. To adjust them to be relative to the event.ts and the
|
297
|
+
// trace timestamps, for both processingStart and processingEnd we subtract
|
298
|
+
// the event timestamp (NOT event.ts, but the timeStamp millisecond value
|
299
|
+
// emitted in args.data), and then add that value to the event.ts. This
|
300
|
+
// will give us a processingStart and processingEnd time in microseconds
|
301
|
+
// that is relative to event.ts, and can be used when drawing boxes.
|
302
|
+
// There is some inaccuracy here as we are converting milliseconds to
|
303
|
+
// microseconds, but it is good enough until the backend emits more
|
304
|
+
// accurate numbers.
|
305
|
+
const processingStartRelativeToTraceTime = Types.Timing.Micro(
|
306
|
+
Helpers.Timing.milliToMicro(processingStart) - Helpers.Timing.milliToMicro(timeStamp) + beginEvent.ts,
|
307
|
+
);
|
308
|
+
|
309
|
+
const processingEndRelativeToTraceTime = Types.Timing.Micro(
|
310
|
+
(Helpers.Timing.milliToMicro(processingEnd) - Helpers.Timing.milliToMicro(timeStamp)) + beginEvent.ts);
|
311
|
+
|
312
|
+
// Ultimate frameId fallback only needed for TSC, see comments in the type.
|
313
|
+
const frameId = beginEvent.args.frame ?? beginEvent.args.data.frame ?? '';
|
314
|
+
const navigation = Helpers.Trace.getNavigationForTraceEvent(beginEvent, frameId, navigationsByFrameId);
|
315
|
+
const navigationId = navigation?.args.data?.navigationId;
|
316
|
+
const interactionEvent =
|
317
|
+
Helpers.SyntheticEvents.SyntheticEventsManager.registerSyntheticEvent<Types.Events.SyntheticInteractionPair>({
|
318
|
+
// Use the start event to define the common fields.
|
319
|
+
rawSourceEvent: beginEvent,
|
320
|
+
cat: beginEvent.cat,
|
321
|
+
name: beginEvent.name,
|
322
|
+
pid: beginEvent.pid,
|
323
|
+
tid: beginEvent.tid,
|
324
|
+
ph: beginEvent.ph,
|
325
|
+
processingStart: processingStartRelativeToTraceTime,
|
326
|
+
processingEnd: processingEndRelativeToTraceTime,
|
327
|
+
// These will be set in writeSyntheticTimespans()
|
328
|
+
inputDelay: Types.Timing.Micro(-1),
|
329
|
+
mainThreadHandling: Types.Timing.Micro(-1),
|
330
|
+
presentationDelay: Types.Timing.Micro(-1),
|
331
|
+
args: {
|
332
|
+
data: {
|
333
|
+
beginEvent,
|
334
|
+
endEvent: event,
|
335
|
+
frame: frameId,
|
336
|
+
navigationId,
|
337
|
+
},
|
318
338
|
},
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
interactionEvents.push(interactionEvent);
|
339
|
+
ts: beginEvent.ts,
|
340
|
+
dur: Types.Timing.Micro(event.ts - beginEvent.ts),
|
341
|
+
type: beginEvent.args.data.type,
|
342
|
+
interactionId: beginEvent.args.data.interactionId,
|
343
|
+
});
|
344
|
+
writeSyntheticTimespans(interactionEvent);
|
345
|
+
interactionEvents.push(interactionEvent);
|
346
|
+
}
|
328
347
|
}
|
329
348
|
|
330
|
-
|
349
|
+
// Once we gather up all the interactions, we want to remove nested
|
350
|
+
// interactions. Interactions can be nested because one user action (e.g. a
|
351
|
+
// click) will cause a pointerdown, pointerup and click. But we don't want to
|
352
|
+
// fill the interactions track with lots of noise. To fix this, we go through
|
353
|
+
// all the events and remove any nested ones so on the timeline we focus the
|
354
|
+
// user on the most important events, which we define as the longest one. But
|
355
|
+
// this algorithm assumes the events are in ASC order, so we first sort the
|
356
|
+
// set of interactions.
|
357
|
+
Helpers.Trace.sortTraceEventsInPlace(interactionEvents);
|
358
|
+
interactionEventsWithNoNesting.push(...removeNestedInteractionsAndSetProcessingTime(interactionEvents));
|
331
359
|
|
332
360
|
// Pick the longest interactions from the set that were not nested, as we
|
333
361
|
// know those are the set of the largest interactions.
|
@@ -223,9 +223,9 @@ export async function finalize(): Promise<void> {
|
|
223
223
|
|
224
224
|
export function data(): UserTimingsData {
|
225
225
|
return {
|
226
|
+
consoleTimings: syntheticEvents.filter(e => e.cat === 'blink.console') as Types.Events.SyntheticConsoleTimingPair[],
|
226
227
|
performanceMeasures: syntheticEvents.filter(e => e.cat === 'blink.user_timing') as
|
227
228
|
Types.Events.SyntheticUserTimingPair[],
|
228
|
-
consoleTimings: syntheticEvents.filter(e => e.cat === 'blink.console') as Types.Events.SyntheticConsoleTimingPair[],
|
229
229
|
performanceMarks: performanceMarkEvents,
|
230
230
|
timestampEvents,
|
231
231
|
measureTraceByTraceId,
|
@@ -221,7 +221,7 @@ export function boundsIncludeTimeRange(data: BoundsIncludeTimeRange): boolean {
|
|
221
221
|
/** Checks to see if the event is within or overlaps the bounds */
|
222
222
|
export function eventIsInBounds(event: Types.Events.Event, bounds: Types.Timing.TraceWindowMicro): boolean {
|
223
223
|
const startTime = event.ts;
|
224
|
-
return startTime <= bounds.max && bounds.min
|
224
|
+
return startTime <= bounds.max && bounds.min < (startTime + (event.dur ?? 0));
|
225
225
|
}
|
226
226
|
|
227
227
|
export function timestampIsInBounds(bounds: Types.Timing.TraceWindowMicro, timestamp: Types.Timing.Micro): boolean {
|
@@ -12,6 +12,7 @@ import {SyntheticEventsManager} from './SyntheticEvents.js';
|
|
12
12
|
import {eventTimingsMicroSeconds} from './Timing.js';
|
13
13
|
|
14
14
|
interface MatchingPairableAsyncEvents {
|
15
|
+
syntheticId: string;
|
15
16
|
begin: Types.Events.PairableAsyncBegin|null;
|
16
17
|
end: Types.Events.PairableAsyncEnd|null;
|
17
18
|
instant?: Types.Events.PairableAsyncInstant[];
|
@@ -290,62 +291,113 @@ export function makeProfileCall(
|
|
290
291
|
}
|
291
292
|
|
292
293
|
/**
|
293
|
-
* Matches beginning events with PairableAsyncEnd and PairableAsyncInstant
|
294
|
-
* if provided
|
295
|
-
* account for that.
|
294
|
+
* Matches beginning events with PairableAsyncEnd and PairableAsyncInstant
|
295
|
+
* if provided. Traces may contain multiple instant events so we need to
|
296
|
+
* account for that. Additionally we have seen cases where we might only have a
|
297
|
+
* begin event & instant event(s), with no end event. So we account for that
|
298
|
+
* situation also.
|
296
299
|
*
|
297
|
-
*
|
300
|
+
* You might also like to read the models/trace/README.md which has some
|
301
|
+
* documentation on trace IDs. This is important as Perfetto will reuse trace
|
302
|
+
* IDs when emitting events (if they do not overlap). This means it's not as
|
303
|
+
* simple as grouping events by IDs. Instead, we group begin & instant events
|
304
|
+
* by ID as we find them. When we find end events, we then pop any matching
|
305
|
+
* begin/instant events off the stack and group those. That way, if we meet the
|
306
|
+
* same ID later on it doesn't cause us collisions.
|
307
|
+
*
|
308
|
+
* @returns An array of all the matched event groups, along with their ID. Note
|
309
|
+
* that two event groups can have the same ID if they were non-overlapping
|
310
|
+
* events. You cannot rely on ID being unique across a trace. The returned set
|
311
|
+
* of groups are NOT SORTED in any order.
|
298
312
|
*/
|
299
|
-
|
313
|
+
function matchEvents(unpairedEvents: Types.Events.PairableAsync[]): MatchingPairableAsyncEvents[] {
|
314
|
+
sortTraceEventsInPlace(unpairedEvents);
|
300
315
|
// map to store begin and end of the event
|
301
|
-
const
|
316
|
+
const matches: MatchingPairableAsyncEvents[] = [];
|
302
317
|
|
303
|
-
|
318
|
+
const beginEventsById = new Map<string, Types.Events.PairableAsyncBegin[]>();
|
319
|
+
const instantEventsById = new Map<string, Types.Events.PairableAsyncInstant[]>();
|
304
320
|
for (const event of unpairedEvents) {
|
305
|
-
const
|
306
|
-
if (
|
321
|
+
const id = getSyntheticId(event);
|
322
|
+
if (id === undefined) {
|
307
323
|
continue;
|
308
324
|
}
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
otherEventsWithID.end = event as Types.Events.PairableAsyncEnd;
|
324
|
-
} else if (isInstantEvent) {
|
325
|
-
if (!otherEventsWithID.instant) {
|
326
|
-
otherEventsWithID.instant = [];
|
325
|
+
if (Types.Events.isPairableAsyncBegin(event)) {
|
326
|
+
const existingEvents = beginEventsById.get(id) ?? [];
|
327
|
+
existingEvents.push(event);
|
328
|
+
beginEventsById.set(id, existingEvents);
|
329
|
+
} else if (Types.Events.isPairableAsyncInstant(event)) {
|
330
|
+
const existingEvents = instantEventsById.get(id) ?? [];
|
331
|
+
existingEvents.push(event);
|
332
|
+
instantEventsById.set(id, existingEvents);
|
333
|
+
} else if (Types.Events.isPairableAsyncEnd(event)) {
|
334
|
+
// Find matching begin event by ID
|
335
|
+
const beginEventsWithMatchingId = beginEventsById.get(id) ?? [];
|
336
|
+
const beginEvent = beginEventsWithMatchingId.pop();
|
337
|
+
if (!beginEvent) {
|
338
|
+
continue;
|
327
339
|
}
|
328
|
-
|
340
|
+
const instantEventsWithMatchingId = instantEventsById.get(id) ?? [];
|
341
|
+
// Find all instant events after the begin event ts.
|
342
|
+
const instantEventsForThisGroup: Types.Events.PairableAsyncInstant[] = [];
|
343
|
+
while (instantEventsWithMatchingId.length > 0) {
|
344
|
+
if (instantEventsWithMatchingId[0].ts >= beginEvent.ts) {
|
345
|
+
const event = instantEventsWithMatchingId.pop();
|
346
|
+
if (event) {
|
347
|
+
instantEventsForThisGroup.push(event);
|
348
|
+
}
|
349
|
+
} else {
|
350
|
+
break;
|
351
|
+
}
|
352
|
+
}
|
353
|
+
const matchingGroup: MatchingPairableAsyncEvents = {
|
354
|
+
begin: beginEvent,
|
355
|
+
end: event,
|
356
|
+
instant: instantEventsForThisGroup,
|
357
|
+
syntheticId: id,
|
358
|
+
};
|
359
|
+
matches.push(matchingGroup);
|
329
360
|
}
|
330
361
|
}
|
331
|
-
|
362
|
+
|
363
|
+
// At this point we know we have paired up all the Begin & End & Instant
|
364
|
+
// events. But it is possible to see only begin & instant events with the
|
365
|
+
// same ID, and no end event. So now we do a second pass through our begin
|
366
|
+
// events to find any that did not have an end event. If we find some
|
367
|
+
// instant events for the begin event, we create a new group.
|
368
|
+
// Also, because there were no end events, we know that the IDs will be
|
369
|
+
// unique now; e.g. each key in the map should have no more than one item in
|
370
|
+
// it.
|
371
|
+
for (const [id, beginEvents] of beginEventsById) {
|
372
|
+
const beginEvent = beginEvents.pop();
|
373
|
+
if (!beginEvent) {
|
374
|
+
continue;
|
375
|
+
}
|
376
|
+
const matchingInstantEvents = instantEventsById.get(id);
|
377
|
+
if (matchingInstantEvents?.length) {
|
378
|
+
matches.push({
|
379
|
+
syntheticId: id,
|
380
|
+
begin: beginEvent,
|
381
|
+
end: null,
|
382
|
+
instant: matchingInstantEvents,
|
383
|
+
});
|
384
|
+
}
|
385
|
+
}
|
386
|
+
|
387
|
+
return matches;
|
332
388
|
}
|
333
389
|
|
334
|
-
function getSyntheticId(event: Types.Events.PairableAsync): string|undefined {
|
390
|
+
export function getSyntheticId(event: Types.Events.PairableAsync): string|undefined {
|
335
391
|
const id = extractId(event);
|
336
392
|
return id && `${event.cat}:${id}:${event.name}`;
|
337
393
|
}
|
338
394
|
|
339
|
-
|
340
|
-
matchedPairs:
|
341
|
-
begin: Types.Events.PairableAsyncBegin | null,
|
342
|
-
end: Types.Events.PairableAsyncEnd | null,
|
343
|
-
instant?: Types.Events.PairableAsyncInstant[],
|
344
|
-
}>,
|
345
|
-
syntheticEventCallback?: (syntheticEvent: Types.Events.SyntheticEventPair<T>) => void,
|
395
|
+
function createSortedSyntheticEvents<T extends Types.Events.PairableAsync>(
|
396
|
+
matchedPairs: MatchingPairableAsyncEvents[],
|
346
397
|
): Array<Types.Events.SyntheticEventPair<T>> {
|
347
398
|
const syntheticEvents: Array<Types.Events.SyntheticEventPair<T>> = [];
|
348
|
-
for (const
|
399
|
+
for (const eventsTriplet of matchedPairs) {
|
400
|
+
const id = eventsTriplet.syntheticId;
|
349
401
|
const beginEvent = eventsTriplet.begin;
|
350
402
|
const endEvent = eventsTriplet.end;
|
351
403
|
const instantEvents = eventsTriplet.instant;
|
@@ -399,17 +451,21 @@ export function createSortedSyntheticEvents<T extends Types.Events.PairableAsync
|
|
399
451
|
// crbug.com/1472375
|
400
452
|
continue;
|
401
453
|
}
|
402
|
-
syntheticEventCallback?.(event);
|
403
454
|
syntheticEvents.push(event);
|
404
455
|
}
|
405
|
-
|
456
|
+
sortTraceEventsInPlace(syntheticEvents);
|
457
|
+
return syntheticEvents;
|
406
458
|
}
|
407
459
|
|
408
|
-
|
409
|
-
|
460
|
+
/**
|
461
|
+
* Groups up sets of async events into synthetic events.
|
462
|
+
* @param unpairedAsyncEvents the raw array of begin, end and async instant
|
463
|
+
* events. These MUST be sorted in timestamp ASC order.
|
464
|
+
*/
|
465
|
+
export function createMatchedSortedSyntheticEvents<T extends Types.Events.PairableAsync>(unpairedAsyncEvents: T[]):
|
410
466
|
Array<Types.Events.SyntheticEventPair<T>> {
|
411
467
|
const matchedPairs = matchEvents(unpairedAsyncEvents);
|
412
|
-
const syntheticEvents = createSortedSyntheticEvents<T>(matchedPairs
|
468
|
+
const syntheticEvents = createSortedSyntheticEvents<T>(matchedPairs);
|
413
469
|
return syntheticEvents;
|
414
470
|
}
|
415
471
|
|
@@ -1358,6 +1358,15 @@ export interface PairableAsyncInstant extends PairableAsync {
|
|
1358
1358
|
export interface PairableAsyncEnd extends PairableAsync {
|
1359
1359
|
ph: Phase.ASYNC_NESTABLE_END;
|
1360
1360
|
}
|
1361
|
+
export function isPairableAsyncBegin(e: Event): e is PairableAsyncBegin {
|
1362
|
+
return e.ph === Phase.ASYNC_NESTABLE_START;
|
1363
|
+
}
|
1364
|
+
export function isPairableAsyncEnd(e: Event): e is PairableAsyncEnd {
|
1365
|
+
return e.ph === Phase.ASYNC_NESTABLE_END;
|
1366
|
+
}
|
1367
|
+
export function isPairableAsyncInstant(e: Event): e is PairableAsyncInstant {
|
1368
|
+
return e.ph === Phase.ASYNC_NESTABLE_INSTANT;
|
1369
|
+
}
|
1361
1370
|
|
1362
1371
|
export interface AnimationFrame extends PairableAsync {
|
1363
1372
|
name: Name.ANIMATION_FRAME;
|