chrome-devtools-frontend 1.0.1515988 → 1.0.1516909

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.
Files changed (43) hide show
  1. package/front_end/core/host/GdpClient.ts +1 -1
  2. package/front_end/core/host/UserMetrics.ts +5 -1
  3. package/front_end/entrypoints/main/MainImpl.ts +1 -1
  4. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +4 -5
  5. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +24 -8
  6. package/front_end/models/badges/UserBadges.ts +38 -3
  7. package/front_end/models/formatter/FormatterWorkerPool.ts +3 -3
  8. package/front_end/panels/common/BadgeNotification.ts +46 -10
  9. package/front_end/panels/elements/ComputedStyleWidget.ts +1 -2
  10. package/front_end/panels/elements/LayoutPane.ts +1 -1
  11. package/front_end/panels/network/NetworkLogView.ts +5 -1
  12. package/front_end/panels/search/SearchResultsPane.ts +32 -47
  13. package/front_end/panels/search/SearchView.ts +25 -48
  14. package/front_end/panels/settings/components/SyncSection.ts +1 -1
  15. package/front_end/panels/sources/OutlineQuickOpen.ts +3 -1
  16. package/front_end/panels/timeline/TimelineUIUtils.ts +13 -8
  17. package/front_end/third_party/puppeteer/README.chromium +2 -2
  18. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  19. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Accessibility.js +0 -20
  20. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Accessibility.js.map +1 -1
  21. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  22. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
  23. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  24. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +1 -1
  25. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +1 -1
  26. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  27. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  28. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +2 -23
  29. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js +0 -20
  30. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js.map +1 -1
  31. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  32. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  33. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +1 -1
  34. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +1 -1
  35. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  36. package/front_end/third_party/puppeteer/package/package.json +1 -1
  37. package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +1 -21
  38. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  39. package/front_end/third_party/puppeteer/package/src/revisions.ts +1 -1
  40. package/front_end/ui/components/text_editor/config.ts +30 -1
  41. package/front_end/ui/components/tooltips/Tooltip.ts +1 -3
  42. package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
  43. package/package.json +1 -1
@@ -80,7 +80,7 @@ function normalizeBadgeName(name: string): string {
80
80
  export const GOOGLE_DEVELOPER_PROGRAM_PROFILE_LINK = 'https://developers.google.com/profile/u/me';
81
81
 
82
82
  async function makeHttpRequest<R extends object>(request: DispatchHttpRequestRequest): Promise<R|null> {
83
- if (!Root.Runtime.hostConfig.devToolsGdpProfiles?.enabled) {
83
+ if (!Root.Runtime.hostConfig.devToolsGdpProfiles?.enabled || Root.Runtime.hostConfig.isOffTheRecord) {
84
84
  return null;
85
85
  }
86
86
 
@@ -507,7 +507,11 @@ export enum Action {
507
507
  AiAssistanceOpenedFromPerformanceInsight = 182,
508
508
  AiAssistanceOpenedFromPerformanceFullButton = 183,
509
509
  AiCodeCompletionResponseServedFromCache = 184,
510
- MAX_VALUE = 185,
510
+ AiCodeCompletionRequestTriggered = 185,
511
+ AiCodeCompletionSuggestionDisplayed = 186,
512
+ AiCodeCompletionSuggestionAccepted = 187,
513
+ AiCodeCompletionError = 188,
514
+ MAX_VALUE = 189,
511
515
  /* eslint-enable @typescript-eslint/naming-convention */
512
516
  }
513
517
 
@@ -516,7 +516,7 @@ export class MainImpl {
516
516
  this.#registerMessageSinkListener();
517
517
 
518
518
  // Initialize `GDPClient` and `UserBadges` for Google Developer Program integration
519
- if (Root.Runtime.hostConfig.devToolsGdpProfiles?.enabled) {
519
+ if (Root.Runtime.hostConfig.devToolsGdpProfiles?.enabled && !Root.Runtime.hostConfig.isOffTheRecord) {
520
520
  void Host.GdpClient.GdpClient.instance().initialize();
521
521
  void Badges.UserBadges.instance().initialize();
522
522
  Badges.UserBadges.instance().addEventListener(Badges.Events.BADGE_TRIGGERED, async ev => {
@@ -264,7 +264,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
264
264
  #formatter: PerformanceTraceFormatter|null = null;
265
265
  #lastInsightForEnhancedQuery: Trace.Insights.Types.InsightModel|undefined;
266
266
  #eventsSerializer = new Trace.EventsSerializer.EventsSerializer();
267
- #lastFocusHandledForContextDetails: AgentFocus|null = null;
267
+ #hasShownAnalyzeTraceContext = false;
268
268
 
269
269
  /**
270
270
  * Cache of all function calls made by the agent. This allows us to include (as a
@@ -336,13 +336,10 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
336
336
  return;
337
337
  }
338
338
 
339
- const focus = context.getItem();
340
- if (this.#lastFocusHandledForContextDetails === focus) {
339
+ if (this.#hasShownAnalyzeTraceContext) {
341
340
  return;
342
341
  }
343
342
 
344
- this.#lastFocusHandledForContextDetails = focus;
345
-
346
343
  yield {
347
344
  type: ResponseType.CONTEXT,
348
345
  title: lockedString(UIStringsNotTranslated.analyzingTrace),
@@ -353,6 +350,8 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
353
350
  },
354
351
  ],
355
352
  };
353
+
354
+ this.#hasShownAnalyzeTraceContext = true;
356
355
  }
357
356
 
358
357
  #callTreeContextSet = new WeakSet();
@@ -159,8 +159,10 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
159
159
  }
160
160
 
161
161
  #debouncedRequestAidaSuggestion = Common.Debouncer.debounce(
162
- (prefix: string, suffix: string, cursor: number, inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage) => {
163
- void this.#requestAidaSuggestion(this.#buildRequest(prefix, suffix, inferenceLanguage), cursor);
162
+ (prefix: string, suffix: string, cursorPositionAtRequest: number,
163
+ inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage) => {
164
+ void this.#requestAidaSuggestion(
165
+ this.#buildRequest(prefix, suffix, inferenceLanguage), cursorPositionAtRequest);
164
166
  },
165
167
  AIDA_REQUEST_DEBOUNCE_TIMEOUT_MS);
166
168
 
@@ -288,12 +290,15 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
288
290
  };
289
291
  }
290
292
 
291
- async #requestAidaSuggestion(request: Host.AidaClient.CompletionRequest, cursor: number): Promise<void> {
293
+ async #requestAidaSuggestion(request: Host.AidaClient.CompletionRequest, cursorPositionAtRequest: number):
294
+ Promise<void> {
292
295
  const startTime = performance.now();
293
296
  this.dispatchEventToListeners(Events.REQUEST_TRIGGERED, {});
297
+ // Registering AiCodeCompletionRequestTriggered metric even if the request is served from cache
298
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeCompletionRequestTriggered);
294
299
 
295
300
  try {
296
- const sampleResponse = await this.#generateSampleForRequest(request, cursor);
301
+ const sampleResponse = await this.#generateSampleForRequest(request, cursorPositionAtRequest);
297
302
  if (!sampleResponse) {
298
303
  this.dispatchEventToListeners(Events.RESPONSE_RECEIVED, {});
299
304
  return;
@@ -308,12 +313,19 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
308
313
  } = sampleResponse;
309
314
  const remainingDelay = Math.max(DELAY_BEFORE_SHOWING_RESPONSE_MS - (performance.now() - startTime), 0);
310
315
  this.#renderingTimeout = window.setTimeout(() => {
316
+ const currentCursorPosition = this.#editor.editor.state.selection.main.head;
317
+ if (currentCursorPosition !== cursorPositionAtRequest) {
318
+ this.dispatchEventToListeners(Events.RESPONSE_RECEIVED, {});
319
+ return;
320
+ }
311
321
  this.#editor.dispatch({
312
322
  effects: TextEditor.Config.setAiAutoCompleteSuggestion.of({
313
323
  text: suggestionText,
314
- from: cursor,
324
+ from: cursorPositionAtRequest,
315
325
  rpcGlobalId,
316
326
  sampleId,
327
+ startTime,
328
+ onImpression: this.#registerUserImpression.bind(this),
317
329
  })
318
330
  });
319
331
 
@@ -326,12 +338,13 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
326
338
  this.#registerUserImpression(rpcGlobalId, sampleId, latency);
327
339
  }
328
340
 
329
- debugLog('Suggestion dispatched to the editor', suggestionText, 'at cursor position', cursor);
341
+ debugLog('Suggestion dispatched to the editor', suggestionText, 'at cursor position', cursorPositionAtRequest);
330
342
  this.dispatchEventToListeners(Events.RESPONSE_RECEIVED, {citations});
331
343
  }, remainingDelay);
332
344
  } catch (e) {
333
345
  debugLog('Error while fetching code completion suggestions from AIDA', e);
334
346
  this.dispatchEventToListeners(Events.RESPONSE_RECEIVED, {});
347
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeCompletionError);
335
348
  }
336
349
  }
337
350
 
@@ -418,6 +431,7 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
418
431
  },
419
432
  });
420
433
  debugLog('Registered user impression with latency {seconds:', seconds, ', nanos:', nanos, '}');
434
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeCompletionSuggestionDisplayed);
421
435
  }
422
436
 
423
437
  registerUserAcceptance(rpcGlobalId: Host.AidaClient.RpcGlobalId, sampleId: number): void {
@@ -433,11 +447,13 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
433
447
  },
434
448
  });
435
449
  debugLog('Registered user acceptance');
450
+ Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeCompletionSuggestionAccepted);
436
451
  }
437
452
 
438
453
  onTextChanged(
439
- prefix: string, suffix: string, cursor: number, inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage): void {
440
- this.#debouncedRequestAidaSuggestion(prefix, suffix, cursor, inferenceLanguage);
454
+ prefix: string, suffix: string, cursorPositionAtRequest: number,
455
+ inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage): void {
456
+ this.#debouncedRequestAidaSuggestion(prefix, suffix, cursorPositionAtRequest, inferenceLanguage);
441
457
  }
442
458
 
443
459
  remove(): void {
@@ -22,6 +22,9 @@ export interface EventTypes {
22
22
  [Events.BADGE_TRIGGERED]: Badge;
23
23
  }
24
24
 
25
+ const SNOOZE_TIME_MS = 24 * 60 * 60 * 1000; // 24 hours
26
+ const MAX_SNOOZE_COUNT = 3;
27
+
25
28
  let userBadgesInstance: UserBadges|undefined = undefined;
26
29
  export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
27
30
  readonly #badgeActionEventTarget = new Common.ObjectWrapper.ObjectWrapper<BadgeActionEvents>();
@@ -29,6 +32,10 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
29
32
  #receiveBadgesSetting: Common.Settings.Setting<Boolean>;
30
33
  #allBadges: Badge[];
31
34
 
35
+ #starterBadgeSnoozeCount: Common.Settings.Setting<number>;
36
+ #starterBadgeLastSnoozedTimestamp: Common.Settings.Setting<number>;
37
+ #starterBadgeDismissed: Common.Settings.Setting<boolean>;
38
+
32
39
  static readonly BADGE_REGISTRY: BadgeClass[] = [
33
40
  StarterBadge,
34
41
  SpeedsterBadge,
@@ -43,6 +50,13 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
43
50
  this.#receiveBadgesSetting = Common.Settings.Settings.instance().moduleSetting('receive-gdp-badges');
44
51
  this.#receiveBadgesSetting.addChangeListener(this.#reconcileBadges, this);
45
52
 
53
+ this.#starterBadgeSnoozeCount = Common.Settings.Settings.instance().createSetting(
54
+ 'starter-badge-snooze-count', 0, Common.Settings.SettingStorageType.SYNCED);
55
+ this.#starterBadgeLastSnoozedTimestamp = Common.Settings.Settings.instance().createSetting(
56
+ 'starter-badge-last-snoozed-timestamp', 0, Common.Settings.SettingStorageType.SYNCED);
57
+ this.#starterBadgeDismissed = Common.Settings.Settings.instance().createSetting(
58
+ 'starter-badge-dismissed', false, Common.Settings.SettingStorageType.SYNCED);
59
+
46
60
  this.#allBadges = UserBadges.BADGE_REGISTRY.map(badgeCtor => new badgeCtor({
47
61
  onTriggerBadge: this.#onTriggerBadge.bind(this),
48
62
  badgeActionEventTarget: this.#badgeActionEventTarget,
@@ -60,6 +74,15 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
60
74
  return await this.#reconcileBadges();
61
75
  }
62
76
 
77
+ snoozeStarterBadge(): void {
78
+ this.#starterBadgeSnoozeCount.set(this.#starterBadgeSnoozeCount.get() + 1);
79
+ this.#starterBadgeLastSnoozedTimestamp.set(Date.now());
80
+ }
81
+
82
+ dismissStarterBadge(): void {
83
+ this.#starterBadgeDismissed.set(true);
84
+ }
85
+
63
86
  recordAction(action: BadgeAction): void {
64
87
  // `Common.ObjectWrapper.ObjectWrapper` does not allow passing unions to
65
88
  // the `dispatchEventToListeners` and `action` in this case is a union.
@@ -79,7 +102,8 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
79
102
  const gdpProfile = await Host.GdpClient.GdpClient.instance().getProfile();
80
103
  const receiveBadgesSettingEnabled = Boolean(this.#receiveBadgesSetting.get());
81
104
  // If there is a GDP profile and the user has enabled receiving badges, we award the starter badge as well.
82
- if (gdpProfile && receiveBadgesSettingEnabled) {
105
+ if (gdpProfile && receiveBadgesSettingEnabled && !this.#isStarterBadgeDismissed() &&
106
+ !this.#isStarterBadgeSnoozed()) {
83
107
  shouldAwardBadge = true;
84
108
  }
85
109
  }
@@ -101,7 +125,17 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
101
125
  });
102
126
  }
103
127
 
104
- // TODO(ergunsh): Implement starter badge dismissal, snooze count & timestamp checks.
128
+ #isStarterBadgeDismissed(): boolean {
129
+ return this.#starterBadgeDismissed.get();
130
+ }
131
+
132
+ #isStarterBadgeSnoozed(): boolean {
133
+ const snoozeCount = this.#starterBadgeSnoozeCount.get();
134
+ const lastSnoozed = this.#starterBadgeLastSnoozedTimestamp.get();
135
+ const snoozedRecently = (Date.now() - lastSnoozed) < SNOOZE_TIME_MS;
136
+ return snoozeCount >= MAX_SNOOZE_COUNT || snoozedRecently;
137
+ }
138
+
105
139
  async #reconcileBadges(): Promise<void> {
106
140
  const syncInfo = await new Promise<Host.InspectorFrontendHostAPI.SyncInformation>(
107
141
  resolve => Host.InspectorFrontendHost.InspectorFrontendHostInstance.getSyncInformation(resolve));
@@ -148,7 +182,8 @@ export class UserBadges extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
148
182
  continue;
149
183
  }
150
184
 
151
- const shouldActivateStarterBadge = badge.isStarterBadge && isEligibleToCreateProfile;
185
+ const shouldActivateStarterBadge = badge.isStarterBadge && isEligibleToCreateProfile &&
186
+ !this.#isStarterBadgeDismissed() && !this.#isStarterBadgeSnoozed();
152
187
  const shouldActivateActivityBasedBadge =
153
188
  !badge.isStarterBadge && Boolean(gdpProfile) && receiveBadgesSettingEnabled;
154
189
  if (shouldActivateStarterBadge || shouldActivateActivityBasedBadge) {
@@ -7,8 +7,6 @@ import * as FormatterActions from '../../entrypoints/formatter_worker/FormatterA
7
7
 
8
8
  export {DefinitionKind, type ScopeTreeNode} from '../../entrypoints/formatter_worker/FormatterActions.js';
9
9
 
10
- const MAX_WORKERS = Math.max(2, navigator.hardwareConcurrency - 1);
11
-
12
10
  let formatterWorkerPoolInstance: FormatterWorkerPool;
13
11
 
14
12
  export class FormatterWorkerPool {
@@ -37,12 +35,14 @@ export class FormatterWorkerPool {
37
35
  }
38
36
 
39
37
  private processNextTask(): void {
38
+ const maxWorkers = Math.max(2, navigator.hardwareConcurrency - 1);
39
+
40
40
  if (!this.taskQueue.length) {
41
41
  return;
42
42
  }
43
43
 
44
44
  let freeWorker = [...this.workerTasks.keys()].find(worker => !this.workerTasks.get(worker));
45
- if (!freeWorker && this.workerTasks.size < MAX_WORKERS) {
45
+ if (!freeWorker && this.workerTasks.size < maxWorkers) {
46
46
  freeWorker = this.createWorker();
47
47
  }
48
48
  if (!freeWorker) {
@@ -69,6 +69,8 @@ const lockedString = i18n.i18n.lockedString;
69
69
 
70
70
  const LEFT_OFFSET = 5;
71
71
  const BOTTOM_OFFSET = 5;
72
+ const AUTO_CLOSE_TIME_IN_MS = 30000;
73
+
72
74
  export interface BadgeNotificationAction {
73
75
  label: string;
74
76
  jslogContext?: string;
@@ -80,10 +82,11 @@ export interface BadgeNotificationProperties {
80
82
  message: HTMLElement|string;
81
83
  imageUri: string;
82
84
  actions: BadgeNotificationAction[];
85
+ isStarterBadge: boolean;
83
86
  }
84
87
 
85
88
  export interface ViewInput extends BadgeNotificationProperties {
86
- onCloseClick: () => void;
89
+ onDismissClick: () => void;
87
90
  }
88
91
 
89
92
  // clang-format off
@@ -101,7 +104,7 @@ const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement)
101
104
 
102
105
  const crossButton = html`<devtools-button
103
106
  class="dismiss notification-button"
104
- @click=${input.onCloseClick}
107
+ @click=${input.onDismissClick}
105
108
  jslog=${VisualLogging.action('badge-notification.dismiss').track({click: true})}
106
109
  aria-label=${i18nString(UIStrings.close)}
107
110
  .iconName=${'cross'}
@@ -138,9 +141,10 @@ export class BadgeNotification extends UI.Widget.Widget {
138
141
  message: HTMLElement|string = '';
139
142
  imageUri = '';
140
143
  actions: BadgeNotificationAction[] = [];
144
+ isStarterBadge = false;
141
145
 
146
+ #autoCloseTimeout?: number;
142
147
  #view: View;
143
-
144
148
  constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
145
149
  super(element);
146
150
  this.#view = view;
@@ -170,12 +174,18 @@ export class BadgeNotification extends UI.Widget.Widget {
170
174
  this.message = properties.message;
171
175
  this.imageUri = properties.imageUri;
172
176
  this.actions = properties.actions;
177
+ this.isStarterBadge = properties.isStarterBadge;
173
178
  this.requestUpdate();
174
179
  this.show(document.body);
175
180
 
176
181
  void this.updateComplete.then(() => {
177
182
  this.#positionNotification();
178
183
  });
184
+
185
+ if (this.#autoCloseTimeout) {
186
+ window.clearTimeout(this.#autoCloseTimeout);
187
+ }
188
+ this.#autoCloseTimeout = window.setTimeout(this.#onAutoClose, AUTO_CLOSE_TIME_IN_MS);
179
189
  }
180
190
 
181
191
  async #presentStarterBadge(badge: Badges.Badge): Promise<void> {
@@ -201,17 +211,21 @@ export class BadgeNotification extends UI.Widget.Widget {
201
211
  actions: [
202
212
  {
203
213
  label: i18nString(UIStrings.remindMeLater),
204
- onClick: () => {/* To implement */},
214
+ onClick: () => {
215
+ this.detach();
216
+ Badges.UserBadges.instance().snoozeStarterBadge();
217
+ },
205
218
  },
206
219
  {
207
220
  label: i18nString(UIStrings.receiveBadges),
208
221
  onClick: () => {
209
- this.#close();
222
+ this.detach();
210
223
  revealBadgeSettings();
211
224
  }
212
225
  }
213
226
  ],
214
227
  imageUri: badge.imageUri,
228
+ isStarterBadge: true,
215
229
  });
216
230
  return;
217
231
  }
@@ -223,17 +237,21 @@ export class BadgeNotification extends UI.Widget.Widget {
223
237
  actions: [
224
238
  {
225
239
  label: i18nString(UIStrings.remindMeLater),
226
- onClick: () => {/* TODO(ergunsh): Implement */},
240
+ onClick: () => {
241
+ this.detach();
242
+ Badges.UserBadges.instance().snoozeStarterBadge();
243
+ },
227
244
  },
228
245
  {
229
246
  label: i18nString(UIStrings.createProfile),
230
247
  onClick: () => {
231
- this.#close();
248
+ this.detach();
232
249
  GdpSignUpDialog.GdpSignUpDialog.show();
233
250
  }
234
251
  }
235
252
  ],
236
253
  imageUri: badge.imageUri,
254
+ isStarterBadge: true,
237
255
  });
238
256
  }
239
257
 
@@ -244,7 +262,7 @@ export class BadgeNotification extends UI.Widget.Widget {
244
262
  {
245
263
  label: i18nString(UIStrings.manageSettings),
246
264
  onClick: () => {
247
- this.#close();
265
+ this.detach();
248
266
  revealBadgeSettings();
249
267
  },
250
268
  },
@@ -256,11 +274,28 @@ export class BadgeNotification extends UI.Widget.Widget {
256
274
  }
257
275
  ],
258
276
  imageUri: badge.imageUri,
277
+ isStarterBadge: badge.isStarterBadge,
259
278
  });
260
279
  }
261
280
 
262
- #close = (): void => {
281
+ override onDetach(): void {
282
+ window.clearTimeout(this.#autoCloseTimeout);
283
+ }
284
+
285
+ #onDismissClick = (): void => {
286
+ this.detach();
287
+
288
+ if (this.isStarterBadge) {
289
+ Badges.UserBadges.instance().dismissStarterBadge();
290
+ }
291
+ };
292
+
293
+ #onAutoClose = (): void => {
263
294
  this.detach();
295
+
296
+ if (this.isStarterBadge) {
297
+ Badges.UserBadges.instance().snoozeStarterBadge();
298
+ }
264
299
  };
265
300
 
266
301
  override wasShown(): void {
@@ -273,7 +308,8 @@ export class BadgeNotification extends UI.Widget.Widget {
273
308
  message: this.message,
274
309
  imageUri: this.imageUri,
275
310
  actions: this.actions,
276
- onCloseClick: this.#close,
311
+ isStarterBadge: this.isStarterBadge,
312
+ onDismissClick: this.#onDismissClick,
277
313
  };
278
314
  this.#view(viewInput, undefined, this.contentElement);
279
315
  }
@@ -205,7 +205,6 @@ class ColorRenderer extends rendererBase(SDK.CSSPropertyParserMatchers.ColorMatc
205
205
  swatch.renderColor(color);
206
206
  const valueElement = document.createElement('span');
207
207
  valueElement.textContent = match.text;
208
- swatch.append(valueElement);
209
208
 
210
209
  swatch.addEventListener(
211
210
  InlineEditor.ColorSwatch.ColorChangedEvent.eventName, (event: InlineEditor.ColorSwatch.ColorChangedEvent) => {
@@ -214,7 +213,7 @@ class ColorRenderer extends rendererBase(SDK.CSSPropertyParserMatchers.ColorMatc
214
213
  });
215
214
 
216
215
  context.addControl('color', swatch);
217
- return [swatch];
216
+ return [swatch, valueElement];
218
217
  }
219
218
 
220
219
  matcher(): SDK.CSSPropertyParserMatchers.ColorMatcher {
@@ -284,7 +284,7 @@ const DEFAULT_VIEW: View = (input, output, target) => {
284
284
  render(html`
285
285
  <div style="min-width: min-content;" jslog=${VisualLogging.pane('layout').track({resize: true})}>
286
286
  <style>${layoutPaneStyles}</style>
287
- <style>${UI.inspectorCommonStyles}</style>
287
+ <style>@scope to (devtools-widget > *) { ${UI.inspectorCommonStyles} }</style>
288
288
  <details open>
289
289
  <summary class="header"
290
290
  @keydown=${input.onSummaryKeyDown}
@@ -2391,6 +2391,10 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2391
2391
  Lastly we replace new lines with ^ and TWO new lines because the first
2392
2392
  new line is there to enact the escape command the second is the character
2393
2393
  to escape (in this case new line).
2394
+
2395
+ All other whitespace characters are replaced with a single space, as there
2396
+ is no way to enter their literal values in a command line, and they do break
2397
+ the command allowing for injection.
2394
2398
  */
2395
2399
  const encapsChars = '^"';
2396
2400
  return encapsChars +
@@ -2398,7 +2402,7 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2398
2402
  .replace(/"/g, '\\"')
2399
2403
  .replace(/[^a-zA-Z0-9\s_\-:=+~'\/.',?;()*`]/g, '^$&')
2400
2404
  .replace(/%(?=[a-zA-Z0-9_])/g, '%^')
2401
- .replace(/[^\S \r\n]/g, '^$&')
2405
+ .replace(/[^\S \r\n]/g, ' ')
2402
2406
  .replace(/\r?\n|\r/g, '^\n\n') +
2403
2407
  encapsChars;
2404
2408
  }
@@ -2,14 +2,15 @@
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
  /* eslint-disable rulesdir/no-imperative-dom-api */
5
+ /* eslint-disable rulesdir/no-lit-render-outside-of-view */
5
6
 
6
7
  import * as Common from '../../core/common/common.js';
7
8
  import * as i18n from '../../core/i18n/i18n.js';
8
9
  import * as Platform from '../../core/platform/platform.js';
9
10
  import * as TextUtils from '../../models/text_utils/text_utils.js';
10
11
  import type * as Workspace from '../../models/workspace/workspace.js';
11
- import * as Components from '../../ui/legacy/components/utils/utils.js';
12
12
  import * as UI from '../../ui/legacy/legacy.js';
13
+ import {html, render} from '../../ui/lit/lit.js';
13
14
 
14
15
  import searchResultsPaneStyles from './searchResultsPane.css.js';
15
16
  import type {SearchResult} from './SearchScope.js';
@@ -137,31 +138,23 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
137
138
 
138
139
  private updateSearchMatches(): void {
139
140
  this.listItemElement.classList.add('search-result');
140
-
141
- const fileNameSpan = span(this.searchResult.label(), 'search-result-file-name');
142
- fileNameSpan.appendChild(span('\u2014', 'search-result-dash'));
143
- fileNameSpan.appendChild(span(this.searchResult.description(), 'search-result-qualifier'));
141
+ // clang-format off
142
+ render(html`
143
+ <span class="search-result-file-name">${this.searchResult.label()}
144
+ <span class="search-result-dash">${'\u2014'}</span>
145
+ <span class="search-result-qualifier">${this.searchResult.description()}</span>
146
+ </span>
147
+ <span class="search-result-matches-count"
148
+ aria-label=${i18nString(UIStrings.matchesCountS, {PH1: this.searchResult.matchesCount()})}>
149
+ ${this.searchResult.matchesCount()}
150
+ </span>`,
151
+ this.listItemElement);
152
+ // clang-format on
144
153
 
145
154
  this.tooltip = this.searchResult.description();
146
- this.listItemElement.appendChild(fileNameSpan);
147
- const matchesCountSpan = document.createElement('span');
148
- matchesCountSpan.className = 'search-result-matches-count';
149
-
150
- matchesCountSpan.textContent = `${this.searchResult.matchesCount()}`;
151
- UI.ARIAUtils.setLabel(
152
- matchesCountSpan, i18nString(UIStrings.matchesCountS, {PH1: this.searchResult.matchesCount()}));
153
-
154
- this.listItemElement.appendChild(matchesCountSpan);
155
155
  if (this.expanded) {
156
156
  this.updateMatchesUI();
157
157
  }
158
-
159
- function span(text: string, className: string): Element {
160
- const span = document.createElement('span');
161
- span.className = className;
162
- span.textContent = text;
163
- return span;
164
- }
165
158
  }
166
159
 
167
160
  private appendSearchMatches(fromIndex: number, toIndex: number): void {
@@ -195,28 +188,29 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
195
188
  ({lineSegment: lineContent, matchRanges} = lineSegmentForMultipleMatches(lineContent, matchRanges));
196
189
  }
197
190
 
198
- const anchor = Components.Linkifier.Linkifier.linkifyRevealable(
199
- searchResult.matchRevealable(i), '', undefined, undefined, undefined, 'search-match');
200
- anchor.classList.add('search-match-link');
201
- anchor.tabIndex = 0;
202
- const labelSpan = document.createElement('span');
203
- labelSpan.classList.add('search-match-line-number');
204
191
  const resultLabel = searchResult.matchLabel(i);
205
- labelSpan.textContent = resultLabel;
206
- if (typeof resultLabel === 'number' && !isNaN(resultLabel)) {
207
- UI.ARIAUtils.setLabel(labelSpan, i18nString(UIStrings.lineS, {PH1: resultLabel}));
208
- } else {
209
- UI.ARIAUtils.setLabel(labelSpan, resultLabel);
210
- }
211
- anchor.appendChild(labelSpan);
212
-
213
- const contentSpan = this.createContentSpan(lineContent, matchRanges);
214
- anchor.appendChild(contentSpan);
215
192
 
216
193
  const searchMatchElement = new UI.TreeOutline.TreeElement();
217
194
  this.appendChild(searchMatchElement);
195
+ // clang-format off
196
+ render(html`
197
+ <button class="devtools-link text-button link-style search-match-link"
198
+ jslog="Link; context: search-match; track: click" role="link" tabindex="0"
199
+ @click=${() => void Common.Revealer.reveal(searchResult.matchRevealable(i))}>
200
+ <span class="search-match-line-number"
201
+ aria-label=${typeof resultLabel === 'number' && !isNaN(resultLabel)
202
+ ? i18nString(UIStrings.lineS, {PH1: resultLabel}) : resultLabel}>
203
+ ${resultLabel}
204
+ </span>
205
+ <span class="search-match-content" aria-label="${lineContent} line">
206
+ ${lineContent}
207
+ </span>
208
+ </button>`,
209
+ searchMatchElement.listItemElement);
210
+ // clang-format on
211
+ const contentSpan = searchMatchElement.listItemElement.querySelector('.search-match-content') as HTMLElement;
212
+ UI.UIUtils.highlightRangesWithStyleClass(contentSpan, matchRanges, 'highlighted-search-result');
218
213
  searchMatchElement.listItemElement.className = 'search-match';
219
- searchMatchElement.listItemElement.appendChild(anchor);
220
214
  searchMatchElement.listItemElement.addEventListener('keydown', event => {
221
215
  if (event.key === 'Enter') {
222
216
  event.consume(true);
@@ -237,15 +231,6 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
237
231
  this.showMoreMatchesElementSelected.bind(this, showMoreMatchesTreeElement, startMatchIndex);
238
232
  }
239
233
 
240
- private createContentSpan(lineContent: string, matchRanges: TextUtils.TextRange.SourceRange[]): Element {
241
- const contentSpan = document.createElement('span');
242
- contentSpan.className = 'search-match-content';
243
- contentSpan.textContent = lineContent;
244
- UI.ARIAUtils.setLabel(contentSpan, `${lineContent} line`);
245
- UI.UIUtils.highlightRangesWithStyleClass(contentSpan, matchRanges, 'highlighted-search-result');
246
- return contentSpan;
247
- }
248
-
249
234
  private regexMatchRanges(lineContent: string, regex: RegExp): TextUtils.TextRange.SourceRange[] {
250
235
  regex.lastIndex = 0;
251
236
  let match;