chrome-devtools-frontend 1.0.1596260 → 1.0.1596535

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 (94) hide show
  1. package/front_end/core/common/Console.ts +6 -6
  2. package/front_end/core/common/Settings.ts +12 -8
  3. package/front_end/core/root/DevToolsContext.ts +13 -7
  4. package/front_end/core/sdk/CPUThrottlingManager.ts +12 -9
  5. package/front_end/core/sdk/FrameManager.ts +7 -8
  6. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +1 -1
  7. package/front_end/entrypoints/main/MainImpl.ts +1 -2
  8. package/front_end/foundation/Universe.ts +22 -11
  9. package/front_end/generated/InspectorBackendCommands.ts +7 -0
  10. package/front_end/generated/protocol-mapping.d.ts +16 -0
  11. package/front_end/generated/protocol-proxy-api.d.ts +25 -0
  12. package/front_end/generated/protocol.ts +71 -0
  13. package/front_end/models/ai_assistance/AiConversation.ts +6 -95
  14. package/front_end/models/ai_assistance/agents/AiAgent.ts +11 -1
  15. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +14 -0
  16. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +1 -0
  17. package/front_end/panels/ai_assistance/components/ChatMessage.ts +25 -4
  18. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +2 -2
  19. package/front_end/panels/elements/StandaloneStylesContainer.ts +48 -12
  20. package/front_end/panels/elements/StylePropertiesSection.ts +97 -1
  21. package/front_end/panels/elements/StylePropertyTreeElement.ts +16 -0
  22. package/front_end/panels/elements/StylesContainer.ts +1 -0
  23. package/front_end/panels/elements/StylesSidebarPane.ts +48 -32
  24. package/front_end/panels/elements/stylePropertiesTreeOutline.css +1 -1
  25. package/front_end/panels/lighthouse/LighthouseController.ts +13 -5
  26. package/front_end/panels/lighthouse/LighthousePanel.ts +22 -5
  27. package/front_end/panels/recorder/components/StepView.ts +0 -2
  28. package/front_end/panels/recorder/components/stepView.css +13 -13
  29. package/front_end/panels/recorder/components/timelineSection.css +6 -7
  30. package/front_end/panels/timeline/TimelinePanel.ts +18 -0
  31. package/front_end/panels/timeline/components/CWVMetrics.ts +339 -0
  32. package/front_end/panels/timeline/components/Sidebar.ts +17 -0
  33. package/front_end/panels/timeline/components/SidebarInsightsTab.ts +10 -0
  34. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +25 -267
  35. package/front_end/panels/timeline/components/components.ts +2 -0
  36. package/front_end/panels/timeline/components/cwvMetrics.css +107 -0
  37. package/front_end/panels/timeline/components/sidebarSingleInsightSet.css +0 -102
  38. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  39. package/front_end/panels/timeline/utils/Helpers.ts +5 -1
  40. package/front_end/third_party/chromium/README.chromium +1 -1
  41. package/front_end/third_party/puppeteer/README.chromium +2 -2
  42. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +7 -0
  43. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
  44. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
  45. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts +1 -0
  46. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.d.ts.map +1 -1
  47. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js +3 -0
  48. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/Page.js.map +1 -1
  49. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts +1 -0
  50. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
  51. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +6 -0
  52. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
  53. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts +1 -0
  54. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.d.ts.map +1 -1
  55. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +5 -0
  56. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
  57. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  58. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
  59. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
  60. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  61. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  62. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  63. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +7 -0
  64. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +14 -3
  65. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +7 -0
  66. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
  67. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
  68. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts +1 -0
  69. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.d.ts.map +1 -1
  70. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js +3 -0
  71. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/Page.js.map +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts +1 -0
  73. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +6 -0
  75. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
  76. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts +1 -0
  77. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.d.ts.map +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +5 -0
  79. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
  81. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
  82. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  83. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  84. package/front_end/third_party/puppeteer/package/lib/types.d.ts +7 -0
  85. package/front_end/third_party/puppeteer/package/package.json +1 -1
  86. package/front_end/third_party/puppeteer/package/src/api/Page.ts +8 -0
  87. package/front_end/third_party/puppeteer/package/src/bidi/Page.ts +4 -0
  88. package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +7 -0
  89. package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +6 -0
  90. package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
  91. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  92. package/front_end/ui/components/text_editor/AutocompleteHistory.ts +89 -5
  93. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  94. package/package.json +1 -1
@@ -4,10 +4,10 @@
4
4
 
5
5
  import * as Host from '../../core/host/host.js';
6
6
  import * as Root from '../../core/root/root.js';
7
- import * as SDK from '../../core/sdk/sdk.js';
8
- import * as Trace from '../../models/trace/trace.js';
7
+ import type * as SDK from '../../core/sdk/sdk.js';
8
+ import type * as Trace from '../../models/trace/trace.js';
9
9
  import * as Greendev from '../greendev/greendev.js';
10
- import * as NetworkTimeCalculator from '../network_time_calculator/network_time_calculator.js';
10
+ import type * as NetworkTimeCalculator from '../network_time_calculator/network_time_calculator.js';
11
11
 
12
12
  import {
13
13
  type AiAgent,
@@ -26,10 +26,6 @@ import {PerformanceAgent, PerformanceTraceContext} from './agents/PerformanceAge
26
26
  import {NodeContext, StylingAgent} from './agents/StylingAgent.js';
27
27
  import {AiHistoryStorage, ConversationType, type SerializedConversation} from './AiHistoryStorage.js';
28
28
  import type {ChangeManager} from './ChangeManager.js';
29
- import {NetworkRequestFormatter} from './data_formatters/NetworkRequestFormatter.js';
30
- import {PerformanceInsightFormatter} from './data_formatters/PerformanceInsightFormatter.js';
31
- import {micros} from './data_formatters/UnitFormatters.js';
32
- import {AgentFocus} from './performance/AIContext.js';
33
29
 
34
30
  export const NOT_FOUND_IMAGE_DATA = '';
35
31
  const MAX_TITLE_LENGTH = 80;
@@ -274,6 +270,9 @@ export class AiConversation {
274
270
  if (item.type === ResponseType.SIDE_EFFECT) {
275
271
  return {...item, confirm: undefined};
276
272
  }
273
+ if (item.type === ResponseType.CONTEXT && item.widgets) {
274
+ return {...item, widgets: undefined};
275
+ }
277
276
  return item;
278
277
  })
279
278
  .filter(history => !!history),
@@ -342,88 +341,11 @@ export class AiConversation {
342
341
  }
343
342
  }
344
343
 
345
- #factsCache = new Map<ExtraContext, Host.AidaClient.RequestFact>();
346
-
347
- async #createFactsForExtraContext(contexts: ExtraContext[]): Promise<void> {
348
- for (const context of contexts) {
349
- const cached = this.#factsCache.get(context);
350
- if (cached) {
351
- this.#agent.addFact(cached);
352
- continue;
353
- }
354
-
355
- if (context instanceof SDK.DOMModel.DOMNode) {
356
- const desc = await StylingAgent.describeElement(context);
357
-
358
- const fact = {
359
- text: `Relevant HTML element:\n${desc}`,
360
- metadata: {
361
- source: 'devtools-floaty',
362
- score: 1,
363
- }
364
- };
365
- this.#factsCache.set(context, fact);
366
- this.#agent.addFact(fact);
367
- } else if (context instanceof SDK.NetworkRequest.NetworkRequest) {
368
- const calculator = new NetworkTimeCalculator.NetworkTransferTimeCalculator();
369
- calculator.updateBoundaries(context);
370
- const formatter = new NetworkRequestFormatter(context, calculator);
371
- const desc = await formatter.formatNetworkRequest();
372
-
373
- const fact = {
374
- text: `Relevant network request:\n${desc}`,
375
- metadata: {
376
- source: 'devtools-floaty',
377
- score: 1,
378
- }
379
- };
380
- this.#factsCache.set(context, fact);
381
- this.#agent.addFact(fact);
382
- } else if ('insight' in context) {
383
- const focus = AgentFocus.fromInsight(context.trace, context.insight);
384
- const formatter = new PerformanceInsightFormatter(
385
- focus,
386
- context.insight,
387
- );
388
-
389
- const text = `Relevant Performance Insight:\n${formatter.formatInsight()}`;
390
- const fact = {
391
- text,
392
- metadata: {
393
- source: 'devtools-floaty',
394
- score: 1,
395
- }
396
- };
397
- this.#factsCache.set(context, fact);
398
- this.#agent.addFact(fact);
399
- } else {
400
- // Must be a trace event
401
- const time = Trace.Types.Timing.Micro(
402
- context.event.ts - context.traceStartTime,
403
- );
404
-
405
- const desc = `Trace event: ${context.event.name}
406
- Time: ${micros(time)}`;
407
-
408
- const fact = {
409
- text: `Relevant trace event:\n${desc}`,
410
- metadata: {
411
- source: 'devtools-floaty',
412
- score: 1,
413
- }
414
- };
415
- this.#factsCache.set(context, fact);
416
- this.#agent.addFact(fact);
417
- }
418
- }
419
- }
420
-
421
344
  async *
422
345
  run(
423
346
  initialQuery: string,
424
347
  options: {
425
348
  signal?: AbortSignal,
426
- extraContext?: ExtraContext[],
427
349
  multimodalInput?: MultimodalInput,
428
350
  } = {},
429
351
  ): AsyncGenerator<ResponseData, void, void> {
@@ -442,9 +364,6 @@ Time: ${micros(time)}`;
442
364
  void this.addHistoryItem(userQuery);
443
365
  yield userQuery;
444
366
 
445
- if (options.extraContext) {
446
- await this.#createFactsForExtraContext(options.extraContext);
447
- }
448
367
  this.#setOriginIfEmpty(this.selectedContext?.getOrigin());
449
368
 
450
369
  if (this.isBlockedByOrigin) {
@@ -463,7 +382,6 @@ Time: ${micros(time)}`;
463
382
  initialQuery: string,
464
383
  options: {
465
384
  signal?: AbortSignal,
466
- extraContext?: ExtraContext[],
467
385
  multimodalInput?: MultimodalInput,
468
386
  } = {},
469
387
  ): AsyncGenerator<ResponseData, void, void> {
@@ -528,13 +446,6 @@ function isAiAssistanceServerSideLoggingEnabled(): boolean {
528
446
  return !Root.Runtime.hostConfig.aidaAvailability?.disallowLogging;
529
447
  }
530
448
 
531
- // TODO: this is the same as the type in UI.Floaty but we cannot use UI
532
- // here. This is fine for prototyping but if we take this further we can
533
- // rearchitect.
534
- type ExtraContext = SDK.DOMModel.DOMNode|SDK.NetworkRequest.NetworkRequest|
535
- {event: Trace.Types.Events.Event, traceStartTime: Trace.Types.Timing.Micro}|
536
- {insight: Trace.Insights.Types.InsightModel, trace: Trace.TraceModel.ParsedTrace};
537
-
538
449
  function isAiAssistanceContextSelectionAgentEnabled(): boolean {
539
450
  return Boolean(Root.Runtime.hostConfig.devToolsAiAssistanceContextSelectionAgent?.enabled);
540
451
  }
@@ -7,6 +7,7 @@ import * as Root from '../../../core/root/root.js';
7
7
  import type * as SDK from '../../../core/sdk/sdk.js';
8
8
  import type * as Protocol from '../../../generated/protocol.js';
9
9
  import * as Greendev from '../../greendev/greendev.js';
10
+ import type * as Trace from '../../trace/trace.js';
10
11
  import {debugLog, isStructuredLogEnabled} from '../debug.js';
11
12
 
12
13
  export const enum ResponseType {
@@ -69,6 +70,7 @@ export interface ContextResponse {
69
70
  type: ResponseType.CONTEXT;
70
71
  title: string;
71
72
  details: [ContextDetail, ...ContextDetail[]];
73
+ widgets?: AiWidget[];
72
74
  }
73
75
 
74
76
  export interface TitleResponse {
@@ -228,8 +230,16 @@ export interface ComputedStyleAiWidget {
228
230
  properties: string[],
229
231
  };
230
232
  }
233
+ export interface CoreVitalsAiWidget {
234
+ name: 'CORE_VITALS';
235
+ data: {
236
+ insightSetKey: string,
237
+ parsedTrace: Trace.TraceModel.ParsedTrace,
238
+ };
239
+ }
240
+
231
241
  // This type will grow as we add more widgets.
232
- export type AiWidget = ComputedStyleAiWidget;
242
+ export type AiWidget = ComputedStyleAiWidget|CoreVitalsAiWidget;
233
243
 
234
244
  export type FunctionCallHandlerResult<Result> = {
235
245
  requiresApproval: true,
@@ -22,6 +22,7 @@ import {AgentFocus} from '../performance/AIContext.js';
22
22
 
23
23
  import {
24
24
  AiAgent,
25
+ type AiWidget,
25
26
  type ContextResponse,
26
27
  ConversationContext,
27
28
  type ConversationSuggestions,
@@ -381,6 +382,18 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
381
382
  return;
382
383
  }
383
384
 
385
+ const widgets: AiWidget[] = [];
386
+ const primaryInsightSet = context.getItem().primaryInsightSet;
387
+ if (primaryInsightSet) {
388
+ widgets.push({
389
+ name: 'CORE_VITALS',
390
+ data: {
391
+ parsedTrace: context.getItem().parsedTrace,
392
+ insightSetKey: primaryInsightSet.id,
393
+ },
394
+ });
395
+ }
396
+
384
397
  yield {
385
398
  type: ResponseType.CONTEXT,
386
399
  title: lockedString(UIStringsNotTranslated.analyzingTrace),
@@ -390,6 +403,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
390
403
  text: this.#formatter?.formatTraceSummary() ?? '',
391
404
  },
392
405
  ],
406
+ widgets,
393
407
  };
394
408
 
395
409
  this.#hasShownAnalyzeTraceContext = true;
@@ -1714,6 +1714,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1714
1714
  case AiAssistanceModel.AiAgent.ResponseType.CONTEXT: {
1715
1715
  step.title = data.title;
1716
1716
  step.contextDetails = data.details;
1717
+ step.widgets = data.widgets;
1717
1718
  step.isLoading = false;
1718
1719
  commitStep();
1719
1720
  break;
@@ -12,7 +12,8 @@ import type * as Platform from '../../../core/platform/platform.js';
12
12
  import * as Root from '../../../core/root/root.js';
13
13
  import * as SDK from '../../../core/sdk/sdk.js';
14
14
  import type * as Protocol from '../../../generated/protocol.js';
15
- import type {AiWidget, ComputedStyleAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
15
+ import type {
16
+ AiWidget, ComputedStyleAiWidget, CoreVitalsAiWidget} from '../../../models/ai_assistance/agents/AiAgent.js';
16
17
  import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
17
18
  import * as ComputedStyle from '../../../models/computed_style/computed_style.js';
18
19
  import * as Marked from '../../../third_party/marked/marked.js';
@@ -25,6 +26,8 @@ import * as UI from '../../../ui/legacy/legacy.js';
25
26
  import * as Lit from '../../../ui/lit/lit.js';
26
27
  import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
27
28
  import * as Elements from '../../elements/elements.js';
29
+ import * as TimelineComponents from '../../timeline/components/components.js';
30
+ import * as TimelineUtils from '../../timeline/utils/utils.js';
28
31
 
29
32
  import chatMessageStyles from './chatMessage.css.js';
30
33
  import {walkthroughTitle, WalkthroughView} from './WalkthroughView.js';
@@ -696,6 +699,19 @@ async function makeComputedStyleWidget(widgetData: ComputedStyleAiWidget): Promi
696
699
  return {renderedWidget: widget, revealable: new Elements.ElementsPanel.NodeComputedStyles(domNodeForId)};
697
700
  }
698
701
 
702
+ async function makeCoreVitalsWidget(widgetData: CoreVitalsAiWidget): Promise<WidgetMakerResponse|null> {
703
+ const widgetConfig = UI.Widget.widgetConfig(TimelineComponents.CWVMetrics.CWVMetrics, {data: widgetData.data});
704
+
705
+ // clang-format off
706
+ const widget = html`<devtools-widget class="core-vitals-widget" .widgetConfig=${widgetConfig}></devtools-widget>`;
707
+ // clang-format on
708
+
709
+ return {
710
+ renderedWidget: widget,
711
+ revealable: new TimelineUtils.Helpers.RevealableCoreVitals(widgetData.data.insightSetKey)
712
+ };
713
+ }
714
+
699
715
  function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTemplate {
700
716
  if (response === null) {
701
717
  return Lit.nothing;
@@ -732,10 +748,11 @@ function renderWidgetResponse(response: WidgetMakerResponse|null): Lit.LitTempla
732
748
  * iterates through the `step.widgets` array. For each widget, it determines
733
749
  * the appropriate rendering logic based on the `widgetData.name`.
734
750
  *
735
- * Currently, only 'COMPUTED_STYLES' widgets are supported. For these, the
736
- * `makeComputedStyleWidget` function is called to construct the necessary
751
+ * Currently, only 'COMPUTED_STYLES' and 'CORE_VITALS' widgets are supported. For these, the
752
+ * `makeComputedStyleWidget` and `makeCoreVitalsWidget` functions are called to construct the necessary
737
753
  * data and configuration for the `Elements.ComputedStyleWidget.ComputedStyleWidget`
738
- * component. The widget is then rendered using the `<devtools-widget>`
754
+ * and `TimelineComponents.CWVMetrics.CWVMetrics`
755
+ * components. The widget is then rendered using the `<devtools-widget>`
739
756
  * custom element, which dynamically instantiates and displays the specified
740
757
  * UI.Widget subclass with the provided configuration.
741
758
  *
@@ -752,6 +769,10 @@ async function renderStepWidgets(step: Step): Promise<Lit.LitTemplate> {
752
769
  const response = await makeComputedStyleWidget(widgetData);
753
770
  return renderWidgetResponse(response);
754
771
  }
772
+ if (widgetData.name === 'CORE_VITALS') {
773
+ const response = await makeCoreVitalsWidget(widgetData);
774
+ return renderWidgetResponse(response);
775
+ }
755
776
  return Lit.nothing;
756
777
  }));
757
778
  return html`${ui}`;
@@ -25,7 +25,7 @@ const UIStrings = {
25
25
  /**
26
26
  * @description Title for the walkthrough view.
27
27
  */
28
- title: 'Investigation steps',
28
+ title: 'Agent walkthrough',
29
29
  /**
30
30
  * @description Title for the button that shows the walkthrough when there are no widgets in the walkthrough.
31
31
  */
@@ -101,7 +101,7 @@ function renderSidebarWalkthrough(input: ViewInput, stepsOutput: Lit.LitTemplate
101
101
  <devtools-button
102
102
  .data=${{
103
103
  variant: Buttons.Button.Variant.TOOLBAR,
104
- iconName: 'right-panel-open',
104
+ iconName: 'cross',
105
105
  title: i18nString(UIStrings.close),
106
106
  jslogContext: 'close-walkthrough',
107
107
  } as Buttons.Button.ButtonData}
@@ -40,7 +40,16 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
40
40
  target);
41
41
  };
42
42
 
43
- export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesContainer {
43
+ export const enum Events {
44
+ STYLES_UPDATE_COMPLETED = 'StylesUpdateCompleted',
45
+ }
46
+
47
+ export interface EventTypes {
48
+ [Events.STYLES_UPDATE_COMPLETED]: void;
49
+ }
50
+
51
+ export class StandaloneStylesContainer extends Common.ObjectWrapper.eventMixin<EventTypes, typeof UI.Widget.VBox>(
52
+ UI.Widget.VBox) implements StylesContainer {
44
53
  activeCSSAngle: InlineEditor.CSSAngle.CSSAngle|null = null;
45
54
  isEditingStyle = false;
46
55
  readonly sectionByElement = new WeakMap<Node, StylePropertiesSection>();
@@ -49,7 +58,7 @@ export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesC
49
58
  new Components.Linkifier.Linkifier(23, /* useLinkDecorator */ true);
50
59
 
51
60
  #webCustomData: WebCustomData|undefined;
52
- #userOperation = false;
61
+ userOperation = false;
53
62
  #sections: StylePropertiesSection[] = [];
54
63
  #swatchPopoverHelper = new InlineEditor.SwatchPopoverHelper.SwatchPopoverHelper();
55
64
  #computedStyleModelInternal = new ComputedStyle.ComputedStyleModel.ComputedStyleModel();
@@ -58,10 +67,24 @@ export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesC
58
67
  constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
59
68
  super(element, {useShadowDom: true});
60
69
  this.#view = view;
70
+ this.#computedStyleModelInternal.addEventListener(
71
+ ComputedStyle.ComputedStyleModel.Events.CSS_MODEL_CHANGED, this.#onCSSModelChanged, this);
61
72
  }
62
73
 
63
- get userOperation(): boolean {
64
- return this.#userOperation;
74
+ async #onCSSModelChanged(
75
+ event: Common.EventTarget.EventTargetEvent<ComputedStyle.ComputedStyleModel.CSSModelChangedEvent>):
76
+ Promise<void> {
77
+ // We only recreate sections if this update is more than an "edit" operation.
78
+ // Sections will pull their own updates in the case of an "edit".
79
+ if (event?.data && 'edit' in event.data && event?.data.edit) {
80
+ return;
81
+ }
82
+
83
+ if (this.isEditingStyle || this.userOperation) {
84
+ return;
85
+ }
86
+
87
+ this.requestUpdate();
65
88
  }
66
89
 
67
90
  get webCustomData(): WebCustomData|undefined {
@@ -73,10 +96,12 @@ export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesC
73
96
  }
74
97
 
75
98
  async #updateSections(): Promise<void> {
99
+ for (const section of this.#sections) {
100
+ section.dispose();
101
+ }
76
102
  const node = this.node();
77
103
  if (!node) {
78
104
  this.#sections = [];
79
- this.requestUpdate();
80
105
  return;
81
106
  }
82
107
 
@@ -106,9 +131,8 @@ export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesC
106
131
  }
107
132
 
108
133
  override async performUpdate(): Promise<void> {
109
- if (this.isEditingStyle || this.#userOperation) {
110
- return;
111
- }
134
+ this.hideAllPopovers();
135
+ this.node()?.domModel().cssModel().discardCachedMatchedCascade();
112
136
 
113
137
  await this.#updateSections();
114
138
 
@@ -116,6 +140,11 @@ export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesC
116
140
  sections: this.#sections,
117
141
  };
118
142
  this.#view(viewInput, undefined, this.contentElement);
143
+ this.#onUpdateFinished();
144
+ }
145
+
146
+ #onUpdateFinished(): void {
147
+ this.dispatchEventToListeners(Events.STYLES_UPDATE_COMPLETED);
119
148
  }
120
149
 
121
150
  swatchPopoverHelper(): InlineEditor.SwatchPopoverHelper.SwatchPopoverHelper {
@@ -154,6 +183,7 @@ export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesC
154
183
  }
155
184
 
156
185
  if (this.isEditingStyle) {
186
+ this.#onUpdateFinished();
157
187
  return;
158
188
  }
159
189
 
@@ -161,6 +191,7 @@ export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesC
161
191
  section.update(section === editedSection);
162
192
  }
163
193
  this.swatchPopoverHelper().reposition();
194
+ this.#onUpdateFinished();
164
195
  }
165
196
 
166
197
  filterRegex(): RegExp|null {
@@ -172,16 +203,19 @@ export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesC
172
203
  }
173
204
 
174
205
  setUserOperation(userOperation: boolean): void {
175
- this.#userOperation = userOperation;
206
+ this.userOperation = userOperation;
176
207
  }
177
208
 
178
209
  forceUpdate(): void {
179
- this.hideAllPopovers();
180
210
  this.requestUpdate();
181
211
  }
182
212
 
183
213
  hideAllPopovers(): void {
184
214
  this.#swatchPopoverHelper.hide();
215
+ if (this.activeCSSAngle) {
216
+ this.activeCSSAngle.minify();
217
+ this.activeCSSAngle = null;
218
+ }
185
219
  }
186
220
 
187
221
  allSections(): StylePropertiesSection[] {
@@ -246,9 +280,11 @@ export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesC
246
280
  jumpToDeclaration(_valueSource: SDK.CSSMatchedStyles.CSSValueSource): void {
247
281
  }
248
282
 
249
- addStyleUpdateListener(_listener: () => void): void {
283
+ addStyleUpdateListener(listener: () => void): void {
284
+ this.addEventListener(Events.STYLES_UPDATE_COMPLETED, listener);
250
285
  }
251
286
 
252
- removeStyleUpdateListener(_listener: () => void): void {
287
+ removeStyleUpdateListener(listener: () => void): void {
288
+ this.removeEventListener(Events.STYLES_UPDATE_COMPLETED, listener);
253
289
  }
254
290
  }
@@ -43,6 +43,7 @@ import * as SDK from '../../core/sdk/sdk.js';
43
43
  import * as Protocol from '../../generated/protocol.js';
44
44
  import * as Badges from '../../models/badges/badges.js';
45
45
  import * as Bindings from '../../models/bindings/bindings.js';
46
+ import * as ComputedStyle from '../../models/computed_style/computed_style.js';
46
47
  import * as TextUtils from '../../models/text_utils/text_utils.js';
47
48
  import * as Buttons from '../../ui/components/buttons/buttons.js';
48
49
  import * as Tooltips from '../../ui/components/tooltips/tooltips.js';
@@ -55,7 +56,7 @@ import {FontEditorSectionManager} from './ColorSwatchPopoverIcon.js';
55
56
  import * as ElementsComponents from './components/components.js';
56
57
  import {ElementsPanel} from './ElementsPanel.js';
57
58
  import stylePropertiesTreeOutlineStyles from './stylePropertiesTreeOutline.css.js';
58
- import {type Context, StylePropertyTreeElement} from './StylePropertyTreeElement.js';
59
+ import {type Context, GhostStylePropertyTreeElement, StylePropertyTreeElement} from './StylePropertyTreeElement.js';
59
60
  import type {StylesContainer} from './StylesContainer.js';
60
61
 
61
62
  const UIStrings = {
@@ -166,6 +167,8 @@ export class StylePropertiesSection {
166
167
  static #nextSectionTooltipIdPrefix = 0;
167
168
  readonly sectionTooltipIdPrefix = StylePropertiesSection.#nextSectionTooltipIdPrefix++;
168
169
 
170
+ private ghostStyleTreeElements: GhostStylePropertyTreeElement[] = [];
171
+
169
172
  constructor(
170
173
  stylesContainer: StylesContainer, matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles,
171
174
  style: SDK.CSSStyleDeclaration.CSSStyleDeclaration, sectionIdx: number, computedStyles: Map<string, string>|null,
@@ -323,12 +326,55 @@ export class StylePropertiesSection {
323
326
  this.#isHidden = false;
324
327
  this.markSelectorMatches();
325
328
  this.onpopulate();
329
+
330
+ this.stylesContainer.computedStyleModel().addEventListener(
331
+ ComputedStyle.ComputedStyleModel.Events.CSS_MODEL_CHANGED, this.#onCSSModelChanged, this);
326
332
  }
327
333
 
328
334
  setComputedStyles(computedStyles: Map<string, string>|null): void {
329
335
  this.computedStyles = computedStyles;
330
336
  }
331
337
 
338
+ #onCSSModelChanged(event: Common.EventTarget.EventTargetEvent<ComputedStyle.ComputedStyleModel.CSSModelChangedEvent>):
339
+ void {
340
+ const edit = event?.data && 'edit' in event.data ? event.data.edit : null;
341
+ if (edit) {
342
+ this.styleSheetEdited(edit);
343
+ void this.refreshComputedValues();
344
+ return;
345
+ }
346
+
347
+ if (this.stylesContainer.isEditingStyle || this.stylesContainer.userOperation) {
348
+ void this.refreshComputedValues();
349
+ }
350
+ }
351
+
352
+ async refreshComputedValues(): Promise<void> {
353
+ const node = this.stylesContainer.node();
354
+ if (!node) {
355
+ return;
356
+ }
357
+
358
+ const cssModel = node.domModel().cssModel();
359
+ const computedStyleModel = this.stylesContainer.computedStyleModel();
360
+
361
+ const matchedStyles = await cssModel.cachedMatchedCascadeForNode(node);
362
+ const parentNodeId = matchedStyles?.getParentLayoutNodeId();
363
+
364
+ const [computedStyles, parentsComputedStyles] = await Promise.all([
365
+ computedStyleModel.fetchComputedStyle(),
366
+ parentNodeId ? cssModel.getComputedStyle(parentNodeId) : null,
367
+ ]);
368
+
369
+ if (computedStyles) {
370
+ this.setComputedStyles(computedStyles.computedStyle);
371
+ }
372
+ if (parentsComputedStyles) {
373
+ this.setParentsComputedStyles(parentsComputedStyles);
374
+ }
375
+ this.updateAuthoringHint();
376
+ }
377
+
332
378
  setParentsComputedStyles(parentsComputedStyles: Map<string, string>|null): void {
333
379
  this.parentsComputedStyles = parentsComputedStyles;
334
380
  }
@@ -349,6 +395,11 @@ export class StylePropertiesSection {
349
395
  }
350
396
  }
351
397
 
398
+ dispose(): void {
399
+ this.stylesContainer.computedStyleModel().removeEventListener(
400
+ ComputedStyle.ComputedStyleModel.Events.CSS_MODEL_CHANGED, this.#onCSSModelChanged, this);
401
+ }
402
+
352
403
  setSectionIdx(sectionIdx: number): void {
353
404
  this.sectionIdx = sectionIdx;
354
405
  this.onpopulate();
@@ -778,6 +829,51 @@ export class StylePropertiesSection {
778
829
  return;
779
830
  }
780
831
 
832
+ clearGhostStyleTreeElements(): void {
833
+ // Clear existing ghost elements
834
+ for (const ghost of this.ghostStyleTreeElements) {
835
+ this.propertiesTreeOutline.removeChild(ghost);
836
+ }
837
+ this.ghostStyleTreeElements = [];
838
+ }
839
+
840
+ renderGhostStyleTreeElements(suggestion: string): void {
841
+ this.clearGhostStyleTreeElements();
842
+ if (!suggestion) {
843
+ return;
844
+ }
845
+
846
+ const suggestionLines = suggestion.split(';').map(line => line.trim());
847
+ for (const line of suggestionLines) {
848
+ const colonIdx = line.indexOf(':');
849
+ if (colonIdx === -1) {
850
+ continue;
851
+ }
852
+
853
+ const name = line.substring(0, colonIdx).trim();
854
+ const value = line.substring(colonIdx + 1).trim();
855
+ if (!name || !value) {
856
+ continue;
857
+ }
858
+
859
+ // Create a fake property attached to this style
860
+ const fakeProperty = new SDK.CSSProperty.CSSProperty(
861
+ this.styleInternal,
862
+ this.styleInternal.allProperties().length,
863
+ name,
864
+ value,
865
+ false,
866
+ false,
867
+ true,
868
+ false,
869
+ );
870
+
871
+ const ghost = new GhostStylePropertyTreeElement(this.stylesContainer, this, this.matchedStyles, fakeProperty);
872
+ this.propertiesTreeOutline.appendChild(ghost);
873
+ this.ghostStyleTreeElements.push(ghost);
874
+ }
875
+ }
876
+
781
877
  private onNewRuleClick(event: Common.EventTarget.EventTargetEvent<Event>): void {
782
878
  event.data.consume();
783
879
  const rule = this.styleInternal.parentRule;
@@ -3548,6 +3548,22 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
3548
3548
  override isEventWithinDisclosureTriangle(event: Event): boolean {
3549
3549
  return event.target === this.expandElement;
3550
3550
  }
3551
+
3552
+ showGhostTextInValue(text: string): void {
3553
+ if (!this.valueElement) {
3554
+ return;
3555
+ }
3556
+ this.clearGhostTextInValue();
3557
+ this.valueElement.createChild('span', 'ghost-value-prediction').textContent = text;
3558
+ }
3559
+
3560
+ clearGhostTextInValue(): void {
3561
+ if (!this.valueElement) {
3562
+ return;
3563
+ }
3564
+ const ghostElement = this.valueElement.querySelector('.ghost-value-prediction');
3565
+ ghostElement?.remove();
3566
+ }
3551
3567
  }
3552
3568
 
3553
3569
  export class GhostStylePropertyTreeElement extends StylePropertyTreeElement {
@@ -17,6 +17,7 @@ export interface StylesContainer {
17
17
  activeCSSAngle: InlineEditor.CSSAngle.CSSAngle|null;
18
18
  readonly webCustomData: WebCustomData|undefined;
19
19
  isEditingStyle: boolean;
20
+ userOperation: boolean;
20
21
  readonly sectionByElement: WeakMap<Node, StylePropertiesSection>;
21
22
  readonly element: HTMLElement;
22
23
  readonly linkifier: Components.Linkifier.Linkifier;