chrome-devtools-frontend 1.0.1518653 → 1.0.1520139
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 +1 -2
- package/config/typescript/tsconfig.eslint.json +12 -1
- package/docs/ui_engineering.md +1011 -0
- package/eslint.config.mjs +1 -0
- package/front_end/core/host/GdpClient.ts +12 -3
- package/front_end/core/sdk/EnhancedTracesParser.ts +5 -5
- package/front_end/core/sdk/NetworkManager.ts +1 -0
- package/front_end/core/sdk/NetworkRequest.ts +10 -0
- 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 -1
- package/front_end/entrypoints/main/main-meta.ts +3 -3
- package/front_end/generated/SupportedCSSProperties.js +19 -4
- package/front_end/models/ai_assistance/agents/AiAgent.ts +57 -10
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +64 -87
- package/front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.ts +4 -4
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -31
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +127 -29
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +106 -55
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +317 -640
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +23 -19
- package/front_end/models/ai_assistance/performance/AICallTree.snapshot.txt +75 -0
- package/front_end/models/ai_assistance/performance/AICallTree.ts +14 -6
- package/front_end/models/ai_assistance/performance/AIContext.ts +63 -8
- package/front_end/models/ai_code_completion/AiCodeCompletion.ts +5 -0
- package/front_end/models/badges/AiExplorerBadge.ts +19 -3
- package/front_end/models/badges/Badge.ts +8 -1
- package/front_end/models/badges/CodeWhispererBadge.ts +1 -0
- 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 +6 -0
- package/front_end/models/badges/badges.ts +1 -0
- package/front_end/models/javascript_metadata/NativeFunctions.js +4 -0
- package/front_end/models/trace/EventsSerializer.ts +4 -3
- package/front_end/models/trace/handlers/UserInteractionsHandler.ts +101 -73
- package/front_end/models/trace/helpers/Timing.ts +1 -1
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +18 -8
- package/front_end/panels/ai_assistance/PatchWidget.ts +17 -55
- package/front_end/panels/ai_assistance/components/ChatView.ts +44 -68
- package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +63 -15
- package/front_end/panels/ai_assistance/components/chatView.css +12 -0
- package/front_end/panels/animation/AnimationTimeline.ts +1 -1
- package/front_end/panels/animation/animationTimeline.css +4 -0
- package/front_end/panels/application/components/BounceTrackingMitigationsView.ts +2 -2
- package/front_end/panels/application/preloading/components/PreloadingString.ts +2 -5
- package/front_end/panels/common/AiCodeCompletionTeaser.ts +5 -0
- package/front_end/panels/common/BadgeNotification.ts +3 -3
- package/front_end/panels/common/GdpSignUpDialog.ts +3 -4
- package/front_end/panels/common/aiCodeCompletionTeaser.css +6 -1
- package/front_end/panels/console/ConsolePrompt.ts +6 -0
- package/front_end/panels/console/ConsoleView.ts +4 -2
- package/front_end/panels/coverage/CoverageListView.ts +133 -158
- package/front_end/panels/coverage/CoverageView.ts +39 -16
- package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +5 -5
- package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +2 -0
- package/front_end/panels/network/NetworkDataGridNode.ts +22 -0
- package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
- package/front_end/panels/recorder/components/CreateRecordingView.ts +2 -0
- package/front_end/panels/recorder/components/RecordingView.ts +2 -2
- package/front_end/panels/search/SearchResultsPane.ts +186 -134
- package/front_end/panels/search/SearchView.ts +42 -36
- package/front_end/panels/search/searchResultsPane.css +9 -0
- package/front_end/panels/search/searchView.css +0 -2
- 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 +26 -12
- package/front_end/panels/settings/emulation/components/UserAgentClientHintsForm.ts +1 -1
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +4 -4
- package/front_end/panels/sources/DebuggerPlugin.ts +4 -0
- package/front_end/panels/sources/SourcesPanel.ts +1 -1
- package/front_end/panels/sources/sourcesView.css +6 -1
- package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -1
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +0 -8
- package/front_end/panels/timeline/TimelineFlameChartView.ts +5 -5
- package/front_end/panels/timeline/TimelinePanel.ts +2 -0
- package/front_end/panels/timeline/TimelineTreeView.ts +1 -1
- package/front_end/panels/timeline/components/LayoutShiftDetails.ts +1 -1
- 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/third_party/chromium/README.chromium +1 -1
- 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/BrowserLauncher.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/BrowserLauncher.js.map +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/node/PipeTransport.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.js +15 -16
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/PipeTransport.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/BrowserLauncher.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/BrowserLauncher.js.map +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/node/PipeTransport.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.js +15 -16
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/PipeTransport.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 +12 -4
- 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/BrowserLauncher.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
- package/front_end/third_party/puppeteer/package/src/node/PipeTransport.ts +15 -17
- 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/tsconfig.json +12 -1
- 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 +16 -2
- package/front_end/ui/legacy/InspectorView.ts +86 -13
- package/front_end/ui/legacy/TabbedPane.ts +2 -1
- package/front_end/ui/legacy/Treeoutline.ts +3 -1
- 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 +15 -5
- package/front_end/ui/visual_logging/LoggingEvents.ts +1 -1
- package/package.json +1 -1
@@ -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.
|
@@ -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 * as Root from '../../core/root/root.js';
|
|
12
12
|
import * as SDK from '../../core/sdk/sdk.js';
|
13
13
|
import * as Protocol from '../../generated/protocol.js';
|
14
14
|
import * as AiAssistanceModel from '../../models/ai_assistance/ai_assistance.js';
|
15
|
+
import * as Badges from '../../models/badges/badges.js';
|
15
16
|
import * as TextUtils from '../../models/text_utils/text_utils.js';
|
16
17
|
import * as Workspace from '../../models/workspace/workspace.js';
|
17
18
|
import * as Buttons from '../../ui/components/buttons/buttons.js';
|
@@ -265,11 +266,18 @@ async function getEmptyStateSuggestions(
|
|
265
266
|
}
|
266
267
|
}
|
267
268
|
|
268
|
-
function getMarkdownRenderer(
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
269
|
+
function getMarkdownRenderer(
|
270
|
+
context: AiAssistanceModel.ConversationContext<unknown>|null,
|
271
|
+
conversation?: AiAssistanceModel.Conversation): MarkdownRendererWithCodeBlock {
|
272
|
+
if (context instanceof AiAssistanceModel.PerformanceTraceContext) {
|
273
|
+
if (!context.external) {
|
274
|
+
const focus = context.getItem();
|
275
|
+
return new PerformanceAgentMarkdownRenderer(
|
276
|
+
focus.parsedTrace.data.Meta.mainFrameId, focus.lookupEvent.bind(focus));
|
277
|
+
}
|
278
|
+
} else if (conversation?.type === AiAssistanceModel.ConversationType.PERFORMANCE) {
|
279
|
+
// Handle historical conversations (can't linkify anything).
|
280
|
+
return new PerformanceAgentMarkdownRenderer();
|
273
281
|
}
|
274
282
|
|
275
283
|
return new MarkdownRendererWithCodeBlock();
|
@@ -849,7 +857,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
849
857
|
|
850
858
|
override async performUpdate(): Promise<void> {
|
851
859
|
const emptyStateSuggestions = await getEmptyStateSuggestions(this.#selectedContext, this.#conversation);
|
852
|
-
const markdownRenderer = getMarkdownRenderer(this.#selectedContext);
|
860
|
+
const markdownRenderer = getMarkdownRenderer(this.#selectedContext, this.#conversation);
|
853
861
|
|
854
862
|
this.view(
|
855
863
|
{
|
@@ -1065,7 +1073,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
1065
1073
|
return Common.Revealer.reveal(context.getItem().uiLocation(0, 0));
|
1066
1074
|
}
|
1067
1075
|
if (context instanceof AiAssistanceModel.PerformanceTraceContext) {
|
1068
|
-
const focus = context.getItem()
|
1076
|
+
const focus = context.getItem();
|
1069
1077
|
if (focus.callTree) {
|
1070
1078
|
const event = focus.callTree.selectedNode?.event ?? focus.callTree.rootNode.event;
|
1071
1079
|
const revealable = new SDK.TraceObject.RevealableEvent(event);
|
@@ -1409,7 +1417,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
|
|
1409
1417
|
// invariants do not hold anymore.
|
1410
1418
|
throw new Error('cross-origin context data should not be included');
|
1411
1419
|
}
|
1412
|
-
|
1420
|
+
if (this.#conversation?.isEmpty) {
|
1421
|
+
Badges.UserBadges.instance().recordAction(Badges.BadgeAction.STARTED_AI_CONVERSATION);
|
1422
|
+
}
|
1413
1423
|
const image = isAiAssistanceMultimodalInputEnabled() ? imageInput : undefined;
|
1414
1424
|
const imageId = image ? crypto.randomUUID() : undefined;
|
1415
1425
|
const multimodalInput = image && imageId && multimodalInputType ? {
|
@@ -182,7 +182,6 @@ export interface ViewInput {
|
|
182
182
|
}
|
183
183
|
|
184
184
|
export interface ViewOutput {
|
185
|
-
tooltipRef?: Directives.Ref<HTMLElement>;
|
186
185
|
changeRef?: Directives.Ref<HTMLElement>;
|
187
186
|
summaryRef?: Directives.Ref<HTMLElement>;
|
188
187
|
}
|
@@ -211,7 +210,6 @@ export class PatchWidget extends UI.Widget.Widget {
|
|
211
210
|
#automaticFileSystem =
|
212
211
|
Persistence.AutomaticFileSystemManager.AutomaticFileSystemManager.instance().automaticFileSystem;
|
213
212
|
#applyToDisconnectedAutomaticWorkspace = false;
|
214
|
-
#popoverHelper: UI.PopoverHelper.PopoverHelper|null = null;
|
215
213
|
// `rpcId` from the `applyPatch` request
|
216
214
|
#rpcId: Host.AidaClient.RpcGlobalId|null = null;
|
217
215
|
|
@@ -228,7 +226,7 @@ export class PatchWidget extends UI.Widget.Widget {
|
|
228
226
|
if (!input.changeSummary && input.patchSuggestionState === PatchSuggestionState.INITIAL) {
|
229
227
|
return;
|
230
228
|
}
|
231
|
-
|
229
|
+
|
232
230
|
output.changeRef = output.changeRef ?? Directives.createRef<HTMLElement>();
|
233
231
|
output.summaryRef = output.summaryRef ?? Directives.createRef<HTMLElement>();
|
234
232
|
|
@@ -390,8 +388,23 @@ export class PatchWidget extends UI.Widget.Widget {
|
|
390
388
|
.jslogContext=${'patch-widget.info-tooltip-trigger'}
|
391
389
|
.iconName=${'info'}
|
392
390
|
.variant=${Buttons.Button.Variant.ICON}
|
393
|
-
.title=${input.applyToWorkspaceTooltipText}
|
394
391
|
></devtools-button>
|
392
|
+
<devtools-tooltip
|
393
|
+
id="info-tooltip"
|
394
|
+
variant=${'rich'}
|
395
|
+
>
|
396
|
+
<div class="info-tooltip-container">
|
397
|
+
${input.applyToWorkspaceTooltipText}
|
398
|
+
<button
|
399
|
+
class="link tooltip-link"
|
400
|
+
role="link"
|
401
|
+
jslog=${VisualLogging.link('open-ai-settings').track({
|
402
|
+
click: true,
|
403
|
+
})}
|
404
|
+
@click=${input.onLearnMoreTooltipClick}
|
405
|
+
>${lockedString(UIStringsNotTranslate.learnMore)}</button>
|
406
|
+
</div>
|
407
|
+
</devtools-tooltip>
|
395
408
|
</div>
|
396
409
|
</div>`;
|
397
410
|
}
|
@@ -417,62 +430,11 @@ export class PatchWidget extends UI.Widget.Widget {
|
|
417
430
|
|
418
431
|
render(template, target, {host: target});
|
419
432
|
});
|
420
|
-
// We're using PopoverHelper as a workaround instead of using <devtools-tooltip>. See the bug for more details.
|
421
|
-
// TODO: Update here when b/409965560 is fixed.
|
422
|
-
this.#popoverHelper = new UI.PopoverHelper.PopoverHelper(this.contentElement, event => {
|
423
|
-
// There are two ways this event is received for showing a popover case:
|
424
|
-
// * The latest element on the composed path is `<devtools-button>`
|
425
|
-
// * The 2nd element on the composed path is `<devtools-button>` (the last element is the `<button>` inside it.)
|
426
|
-
const hoveredNode = event.composedPath()[0];
|
427
|
-
const maybeDevToolsButton = event.composedPath()[2];
|
428
|
-
|
429
|
-
const popoverShownNode = hoveredNode instanceof HTMLElement && hoveredNode.getAttribute('aria-details') === 'info-tooltip' ? hoveredNode
|
430
|
-
: maybeDevToolsButton instanceof HTMLElement && maybeDevToolsButton.getAttribute('aria-details') === 'info-tooltip' ? maybeDevToolsButton
|
431
|
-
: null;
|
432
|
-
if (!popoverShownNode) {
|
433
|
-
return null;
|
434
|
-
}
|
435
|
-
return {
|
436
|
-
box: popoverShownNode.boxInWindow(),
|
437
|
-
show: async (popover: UI.GlassPane.GlassPane) => {
|
438
|
-
// clang-format off
|
439
|
-
render(html`
|
440
|
-
<style>
|
441
|
-
.info-tooltip-container {
|
442
|
-
max-width: var(--sys-size-28);
|
443
|
-
padding: var(--sys-size-4) var(--sys-size-5);
|
444
|
-
|
445
|
-
.tooltip-link {
|
446
|
-
display: block;
|
447
|
-
margin-top: var(--sys-size-4);
|
448
|
-
color: var(--sys-color-primary);
|
449
|
-
padding-left: 0;
|
450
|
-
}
|
451
|
-
}
|
452
|
-
</style>
|
453
|
-
<div class="info-tooltip-container">
|
454
|
-
${UIStringsNotTranslate.applyToWorkspaceTooltip}
|
455
|
-
<button
|
456
|
-
class="link tooltip-link"
|
457
|
-
role="link"
|
458
|
-
jslog=${VisualLogging.link('open-ai-settings').track({
|
459
|
-
click: true,
|
460
|
-
})}
|
461
|
-
@click=${this.#onLearnMoreTooltipClick}
|
462
|
-
>${lockedString(UIStringsNotTranslate.learnMore)}</button>
|
463
|
-
</div>`, popover.contentElement, {host: this});
|
464
|
-
// clang-forat on
|
465
|
-
return true;
|
466
|
-
},
|
467
|
-
};
|
468
|
-
}, 'patch-widget.info-tooltip');
|
469
|
-
this.#popoverHelper.setTimeout(0);
|
470
433
|
// clang-format on
|
471
434
|
this.requestUpdate();
|
472
435
|
}
|
473
436
|
|
474
437
|
#onLearnMoreTooltipClick(): void {
|
475
|
-
this.#viewOutput.tooltipRef?.value?.hidePopover();
|
476
438
|
void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
|
477
439
|
}
|
478
440
|
|
@@ -214,8 +214,8 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
214
214
|
const lockedString = i18n.i18n.lockedString;
|
215
215
|
|
216
216
|
const SCROLL_ROUNDING_OFFSET = 1;
|
217
|
-
const
|
218
|
-
const
|
217
|
+
const RELEVANT_DATA_LINK_FOOTER_ID = 'relevant-data-link-footer';
|
218
|
+
const RELEVANT_DATA_LINK_CHAT_ID = 'relevant-data-link-chat';
|
219
219
|
|
220
220
|
export interface Step {
|
221
221
|
isLoading: boolean;
|
@@ -313,7 +313,6 @@ export class ChatView extends HTMLElement {
|
|
313
313
|
#messagesContainerElement?: Element;
|
314
314
|
#mainElementRef?: Lit.Directives.Ref<Element> = Lit.Directives.createRef();
|
315
315
|
#messagesContainerResizeObserver = new ResizeObserver(() => this.#handleMessagesContainerResize());
|
316
|
-
#popoverHelper: UI.PopoverHelper.PopoverHelper|null = null;
|
317
316
|
/**
|
318
317
|
* Indicates whether the chat scroll position should be pinned to the bottom.
|
319
318
|
*
|
@@ -391,63 +390,6 @@ export class ChatView extends HTMLElement {
|
|
391
390
|
this.#setMainElementScrollTop(this.#mainElementRef.value.scrollHeight);
|
392
391
|
}
|
393
392
|
|
394
|
-
#handleChatUiRef(el: Element|undefined): void {
|
395
|
-
if (!el || this.#popoverHelper) {
|
396
|
-
return;
|
397
|
-
}
|
398
|
-
|
399
|
-
// TODO: Update here when b/409965560 is fixed.
|
400
|
-
this.#popoverHelper = new UI.PopoverHelper.PopoverHelper((el as HTMLElement), event => {
|
401
|
-
const popoverShownNode =
|
402
|
-
event.target instanceof HTMLElement && event.target.id === RELEVANT_DATA_LINK_ID ? event.target : null;
|
403
|
-
if (!popoverShownNode) {
|
404
|
-
return null;
|
405
|
-
}
|
406
|
-
|
407
|
-
// We move the glass pane to be a bit lower so
|
408
|
-
// that it does not disappear when moving the cursor
|
409
|
-
// over to link.
|
410
|
-
const nodeBox = popoverShownNode.boxInWindow();
|
411
|
-
nodeBox.y = nodeBox.y + TOOLTIP_POPOVER_OFFSET;
|
412
|
-
return {
|
413
|
-
box: nodeBox,
|
414
|
-
show: async (popover: UI.GlassPane.GlassPane) => {
|
415
|
-
// clang-format off
|
416
|
-
Lit.render(html`
|
417
|
-
<style>
|
418
|
-
.info-tooltip-container {
|
419
|
-
max-width: var(--sys-size-28);
|
420
|
-
padding: var(--sys-size-4) var(--sys-size-5);
|
421
|
-
|
422
|
-
.tooltip-link {
|
423
|
-
display: block;
|
424
|
-
margin-top: var(--sys-size-4);
|
425
|
-
color: var(--sys-color-primary);
|
426
|
-
padding-left: 0;
|
427
|
-
}
|
428
|
-
}
|
429
|
-
</style>
|
430
|
-
<div class="info-tooltip-container">
|
431
|
-
${this.#props.disclaimerText}
|
432
|
-
<button
|
433
|
-
class="link tooltip-link"
|
434
|
-
role="link"
|
435
|
-
jslog=${VisualLogging.link('open-ai-settings').track({
|
436
|
-
click: true,
|
437
|
-
})}
|
438
|
-
@click=${() => {
|
439
|
-
void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
|
440
|
-
}}
|
441
|
-
>${i18nString(UIStrings.learnAbout)}</button>
|
442
|
-
</div>`, popover.contentElement, {host: this});
|
443
|
-
// clang-format on
|
444
|
-
return true;
|
445
|
-
},
|
446
|
-
};
|
447
|
-
});
|
448
|
-
this.#popoverHelper.setTimeout(0);
|
449
|
-
}
|
450
|
-
|
451
393
|
#handleMessagesContainerResize(): void {
|
452
394
|
if (!this.#pinScrollToBottom) {
|
453
395
|
return;
|
@@ -583,9 +525,11 @@ export class ChatView extends HTMLElement {
|
|
583
525
|
// clang-format off
|
584
526
|
const footerContents = this.#props.conversationType
|
585
527
|
? renderRelevantDataDisclaimer({
|
586
|
-
|
587
|
-
|
588
|
-
|
528
|
+
isLoading: this.#props.isLoading,
|
529
|
+
blockedByCrossOrigin: this.#props.blockedByCrossOrigin,
|
530
|
+
tooltipId: RELEVANT_DATA_LINK_FOOTER_ID,
|
531
|
+
disclaimerText: this.#props.disclaimerText,
|
532
|
+
})
|
589
533
|
: html`<p>
|
590
534
|
${lockedString(UIStringsNotTranslate.inputDisclaimerForEmptyState)}
|
591
535
|
<button
|
@@ -611,7 +555,7 @@ export class ChatView extends HTMLElement {
|
|
611
555
|
// clang-format off
|
612
556
|
Lit.render(html`
|
613
557
|
<style>${chatViewStyles}</style>
|
614
|
-
<div class="chat-ui"
|
558
|
+
<div class="chat-ui">
|
615
559
|
<main @scroll=${this.#handleScroll} ${ref(this.#mainElementRef)}>
|
616
560
|
${renderMainContents({
|
617
561
|
state: this.#props.state,
|
@@ -643,6 +587,7 @@ export class ChatView extends HTMLElement {
|
|
643
587
|
isTextInputDisabled: this.#props.isTextInputDisabled,
|
644
588
|
inputPlaceholder: this.#props.inputPlaceholder,
|
645
589
|
state: this.#props.state,
|
590
|
+
disclaimerText: this.#props.disclaimerText,
|
646
591
|
selectedContext: this.#props.selectedContext,
|
647
592
|
inspectElementToggled: this.#props.inspectElementToggled,
|
648
593
|
multimodalInputEnabled: this.#props.multimodalInputEnabled,
|
@@ -1433,8 +1378,12 @@ function renderImageInput({
|
|
1433
1378
|
// clang-format on
|
1434
1379
|
}
|
1435
1380
|
|
1436
|
-
function renderRelevantDataDisclaimer(
|
1437
|
-
|
1381
|
+
function renderRelevantDataDisclaimer({isLoading, blockedByCrossOrigin, tooltipId, disclaimerText}: {
|
1382
|
+
isLoading: boolean,
|
1383
|
+
blockedByCrossOrigin: boolean,
|
1384
|
+
tooltipId: string,
|
1385
|
+
disclaimerText: string,
|
1386
|
+
}): Lit.LitTemplate {
|
1438
1387
|
const classes =
|
1439
1388
|
Lit.Directives.classMap({'chat-input-disclaimer': true, 'hide-divider': !isLoading && blockedByCrossOrigin});
|
1440
1389
|
// clang-format off
|
@@ -1443,7 +1392,7 @@ function renderRelevantDataDisclaimer(
|
|
1443
1392
|
<button
|
1444
1393
|
class="link"
|
1445
1394
|
role="link"
|
1446
|
-
|
1395
|
+
aria-details=${tooltipId}
|
1447
1396
|
jslog=${VisualLogging.link('open-ai-settings').track({
|
1448
1397
|
click: true,
|
1449
1398
|
})}
|
@@ -1451,6 +1400,7 @@ function renderRelevantDataDisclaimer(
|
|
1451
1400
|
void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
|
1452
1401
|
}}
|
1453
1402
|
>${lockedString('Relevant data')}</button> ${lockedString('is sent to Google')}
|
1403
|
+
${renderDisclamerTooltip(tooltipId, disclaimerText)}
|
1454
1404
|
</p>
|
1455
1405
|
`;
|
1456
1406
|
// clang-format on
|
@@ -1470,6 +1420,7 @@ function renderChatInput({
|
|
1470
1420
|
isTextInputEmpty,
|
1471
1421
|
uploadImageInputEnabled,
|
1472
1422
|
aidaAvailability,
|
1423
|
+
disclaimerText,
|
1473
1424
|
onContextClick,
|
1474
1425
|
onInspectElementClick,
|
1475
1426
|
onSubmit,
|
@@ -1490,6 +1441,7 @@ function renderChatInput({
|
|
1490
1441
|
inspectElementToggled: boolean,
|
1491
1442
|
isTextInputEmpty: boolean,
|
1492
1443
|
aidaAvailability: Host.AidaClient.AidaAccessPreconditions,
|
1444
|
+
disclaimerText: string,
|
1493
1445
|
onContextClick: () => void,
|
1494
1446
|
onInspectElementClick: () => void,
|
1495
1447
|
onSubmit: (ev: SubmitEvent) => void,
|
@@ -1547,7 +1499,7 @@ function renderChatInput({
|
|
1547
1499
|
</div>
|
1548
1500
|
<div class="chat-input-actions-right">
|
1549
1501
|
<div class="chat-input-disclaimer-container">
|
1550
|
-
${renderRelevantDataDisclaimer({isLoading, blockedByCrossOrigin})}
|
1502
|
+
${renderRelevantDataDisclaimer({ isLoading, blockedByCrossOrigin, tooltipId: RELEVANT_DATA_LINK_CHAT_ID, disclaimerText})}
|
1551
1503
|
</div>
|
1552
1504
|
${renderMultimodalInputButtons({
|
1553
1505
|
multimodalInputEnabled, blockedByCrossOrigin, isTextInputDisabled, imageInput, uploadImageInputEnabled, onTakeScreenshot, onImageUpload
|
@@ -1697,6 +1649,30 @@ function renderMainContents({
|
|
1697
1649
|
return renderEmptyState({isTextInputDisabled, suggestions, onSuggestionClick});
|
1698
1650
|
}
|
1699
1651
|
|
1652
|
+
function renderDisclamerTooltip(id: string, disclaimerText: string): Lit.TemplateResult {
|
1653
|
+
// clang-format off
|
1654
|
+
return html`
|
1655
|
+
<devtools-tooltip
|
1656
|
+
id=${id}
|
1657
|
+
variant=${'rich'}
|
1658
|
+
>
|
1659
|
+
<div class="info-tooltip-container">
|
1660
|
+
${disclaimerText}
|
1661
|
+
<button
|
1662
|
+
class="link tooltip-link"
|
1663
|
+
role="link"
|
1664
|
+
jslog=${VisualLogging.link('open-ai-settings').track({
|
1665
|
+
click: true,
|
1666
|
+
})}
|
1667
|
+
@click=${() => {
|
1668
|
+
void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
|
1669
|
+
}}>${i18nString(UIStrings.learnAbout)}
|
1670
|
+
</button>
|
1671
|
+
</div>
|
1672
|
+
</devtools-tooltip>`;
|
1673
|
+
// clang-format on
|
1674
|
+
}
|
1675
|
+
|
1700
1676
|
declare global {
|
1701
1677
|
interface HTMLElementTagNameMap {
|
1702
1678
|
'devtools-ai-chat-view': ChatView;
|