chrome-devtools-frontend 1.0.1611825 → 1.0.1613465

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 (39) hide show
  1. package/front_end/Images/src/flowsheet.svg +1 -0
  2. package/front_end/core/common/Color.ts +3 -2
  3. package/front_end/core/platform/StringUtilities.ts +5 -0
  4. package/front_end/core/sdk/NetworkManager.ts +7 -0
  5. package/front_end/generated/InspectorBackendCommands.ts +2 -2
  6. package/front_end/generated/protocol.ts +12 -2
  7. package/front_end/models/greendev/Prototypes.ts +7 -4
  8. package/front_end/models/har/HARFormat.ts +30 -0
  9. package/front_end/models/har/Importer.ts +12 -1
  10. package/front_end/models/har/Log.ts +28 -0
  11. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +62 -22
  12. package/front_end/panels/ai_assistance/PatchWidget.ts +7 -23
  13. package/front_end/panels/ai_assistance/SelectWorkspaceDialog.ts +3 -3
  14. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +7 -0
  15. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  16. package/front_end/panels/ai_assistance/components/ChatView.ts +4 -0
  17. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +12 -6
  18. package/front_end/panels/ai_assistance/components/OptInChangeDialog.ts +179 -0
  19. package/front_end/panels/ai_assistance/components/optInChangeDialog.css +95 -0
  20. package/front_end/panels/ai_assistance/components/walkthroughView.css +1 -1
  21. package/front_end/panels/application/CookieItemsView.ts +3 -2
  22. package/front_end/panels/changes/ChangesView.ts +0 -12
  23. package/front_end/panels/common/common.ts +0 -1
  24. package/front_end/panels/console/ConsolePrompt.ts +2 -2
  25. package/front_end/panels/elements/StylePropertyTreeElement.ts +2 -1
  26. package/front_end/panels/elements/StylesSidebarPane.ts +3 -1
  27. package/front_end/panels/elements/elements-meta.ts +14 -0
  28. package/front_end/panels/network/NetworkLogView.ts +58 -28
  29. package/front_end/panels/recorder/RecorderController.ts +4 -4
  30. package/front_end/panels/settings/AISettingsTab.ts +39 -3
  31. package/front_end/panels/settings/SettingsScreen.ts +2 -2
  32. package/front_end/third_party/chromium/README.chromium +1 -1
  33. package/front_end/ui/legacy/SplitWidget.ts +9 -6
  34. package/front_end/ui/legacy/components/settings_ui/SettingsUI.ts +3 -3
  35. package/front_end/ui/legacy/components/source_frame/FontView.ts +101 -88
  36. package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +4 -4
  37. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -0
  38. package/package.json +1 -1
  39. package/front_end/panels/common/CopyChangesToPrompt.ts +0 -233
@@ -422,9 +422,9 @@ export class SplitWidget extends Common.ObjectWrapper.eventMixin<EventTypes, typ
422
422
  */
423
423
  #totalSizeDIP(): number {
424
424
  if (!this.#totalSizeCSS) {
425
- this.#totalSizeCSS = this.#isVertical ? this.contentElement.offsetWidth : this.contentElement.offsetHeight;
426
- this.#totalSizeOtherDimensionCSS =
427
- this.#isVertical ? this.contentElement.offsetHeight : this.contentElement.offsetWidth;
425
+ const {width, height} = this.contentElement.getBoundingClientRect();
426
+ this.#totalSizeCSS = this.#isVertical ? width : height;
427
+ this.#totalSizeOtherDimensionCSS = this.#isVertical ? height : width;
428
428
  }
429
429
  return ZoomManager.instance().cssToDIP(this.#totalSizeCSS);
430
430
  }
@@ -457,9 +457,12 @@ export class SplitWidget extends Common.ObjectWrapper.eventMixin<EventTypes, typ
457
457
  this.#removeAllLayoutProperties();
458
458
 
459
459
  // this.#totalSizeDIP is available below since we successfully applied constraints.
460
- const roundSizeCSS = Math.round(ZoomManager.instance().dipToCSS(sizeDIP));
461
- const sidebarSizeValue = roundSizeCSS + 'px';
462
- const mainSizeValue = (this.#totalSizeCSS - roundSizeCSS) + 'px';
460
+ const sizeCSS = ZoomManager.instance().dipToCSS(sizeDIP);
461
+ const sidebarSizeValue = sizeCSS + 'px';
462
+ const mainSizeValue = (this.#totalSizeCSS - sizeCSS) + 'px';
463
+ // With `box-sizing: border-box` on the sidebar (set in splitWidget.css),
464
+ // flex-basis, width, and height all include the border, so the sidebar's
465
+ // border does not steal space from the main pane.
463
466
  this.#sidebarElement.style.flexBasis = sidebarSizeValue;
464
467
 
465
468
  // Make both sides relayout boundaries.
@@ -105,7 +105,7 @@ export function renderSettingSelect(setting: Common.Settings.Setting<unknown>, s
105
105
  }
106
106
 
107
107
  export const renderControlForSetting = function(
108
- setting: Common.Settings.Setting<unknown>, subtitle?: string): TemplateResult|null {
108
+ setting: Common.Settings.Setting<unknown>, subtitle?: string): TemplateResult|typeof nothing {
109
109
  switch (setting.type()) {
110
110
  case Common.Settings.SettingType.BOOLEAN: {
111
111
  const onchange = (): void => {
@@ -123,14 +123,14 @@ export const renderControlForSetting = function(
123
123
  }
124
124
  default:
125
125
  console.error('Invalid setting type: ' + setting.type());
126
- return null;
126
+ return nothing;
127
127
  }
128
128
  };
129
129
 
130
130
  export const createControlForSetting = function(
131
131
  setting: Common.Settings.Setting<unknown>, subtitle?: string): HTMLElement|null {
132
132
  const template = renderControlForSetting(setting, subtitle);
133
- if (template === null) {
133
+ if (template === nothing) {
134
134
  return null;
135
135
  }
136
136
  const fragment = document.createDocumentFragment();
@@ -1,7 +1,6 @@
1
1
  // Copyright 2021 The Chromium Authors
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
- /* eslint-disable @devtools/no-imperative-dom-api */
5
4
 
6
5
  /*
7
6
  * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
@@ -34,6 +33,7 @@
34
33
  import * as i18n from '../../../../core/i18n/i18n.js';
35
34
  import * as Platform from '../../../../core/platform/platform.js';
36
35
  import * as TextUtils from '../../../../models/text_utils/text_utils.js';
36
+ import {Directives, html, render} from '../../../lit/lit.js';
37
37
  import * as VisualLogging from '../../../visual_logging/visual_logging.js';
38
38
  import * as UI from '../../legacy.js';
39
39
 
@@ -52,24 +52,70 @@ const UIStrings = {
52
52
  } as const;
53
53
  const str_ = i18n.i18n.registerUIStrings('ui/legacy/components/source_frame/FontView.ts', UIStrings);
54
54
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
55
+
56
+ const FONT_PREVIEW_LINES = ['ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ', 'abcdefghijklm', 'nopqrstuvwxyz', '1234567890'];
57
+ const MEASURE_FONT_SIZE = 50;
58
+
59
+ export interface ViewInput {
60
+ url: Platform.DevToolsPath.UrlString;
61
+ fontFaceRule: string;
62
+ fontFamily: string;
63
+ previewFontSize: string;
64
+ previewVisible: boolean;
65
+ }
66
+
67
+ export interface ViewOutput {
68
+ measureDimensions?: () => {
69
+ width: number, height: number,
70
+ };
71
+ }
72
+
73
+ export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
74
+
75
+ // clang-format off
76
+ export const DEFAULT_VIEW: View = (input, output, target) => {
77
+ let dummyEl: HTMLElement|undefined;
78
+ render(html`
79
+ <style>${fontViewStyles}</style>
80
+ <style>${input.fontFaceRule}</style>
81
+ <div class="font-view"
82
+ aria-label=${i18nString(UIStrings.previewOfFontFromS, {PH1: input.url})}
83
+ style="font-family: ${input.fontFamily}; font-size: ${input.previewFontSize}"
84
+ aria-hidden="true"
85
+ ?hidden=${!input.previewVisible}
86
+ >${FONT_PREVIEW_LINES.map((line, i) => html`${i > 0 ? html`<br>` : ''}${line}`)}</div>
87
+ <div ${Directives.ref(el => { dummyEl = el as HTMLElement; })}
88
+ style="visibility: hidden; z-index: -1; display: inline; position: absolute; font-family: ${input.fontFamily}; font-size: ${MEASURE_FONT_SIZE}px"
89
+ >${FONT_PREVIEW_LINES.map((line, i) => html`${i > 0 ? html`<br>` : ''}${line}`)}</div>
90
+ `, target);
91
+ output.measureDimensions = () => {
92
+ if (!dummyEl) {
93
+ return {width: 0, height: 0};
94
+ }
95
+ return {width: dummyEl.offsetWidth, height: dummyEl.offsetHeight};
96
+ };
97
+ };
98
+ // clang-format on
99
+
55
100
  export class FontView extends UI.View.SimpleView {
56
101
  private readonly url: Platform.DevToolsPath.UrlString;
57
102
  private readonly contentProvider: TextUtils.ContentProvider.ContentProvider;
58
103
  private readonly mimeTypeLabel: UI.Toolbar.ToolbarText;
59
- fontPreviewElement!: HTMLElement|null;
60
- private dummyElement!: HTMLElement|null;
61
- fontStyleElement!: HTMLStyleElement|null;
62
- private inResize!: boolean|null;
63
- constructor(mimeType: string, contentProvider: TextUtils.ContentProvider.ContentProvider) {
104
+ readonly #view: View;
105
+ #fontFaceRule = '';
106
+ #fontFamily = '';
107
+ #previewFontSize = '';
108
+ #previewVisible = false;
109
+ #contentLoaded = false;
110
+
111
+ constructor(mimeType: string, contentProvider: TextUtils.ContentProvider.ContentProvider, view: View = DEFAULT_VIEW) {
64
112
  super({
65
113
  title: i18nString(UIStrings.font),
66
114
  viewId: 'font',
67
115
  jslog: `${VisualLogging.pane('font-view')}`,
68
116
  });
69
- this.registerRequiredCSS(fontViewStyles);
70
- this.element.classList.add('font-view');
117
+ this.#view = view;
71
118
  this.url = contentProvider.contentURL();
72
- UI.ARIAUtils.setLabel(this.element, i18nString(UIStrings.previewOfFontFromS, {PH1: this.url}));
73
119
  this.contentProvider = contentProvider;
74
120
  this.mimeTypeLabel = new UI.Toolbar.ToolbarText(mimeType);
75
121
  }
@@ -78,96 +124,39 @@ export class FontView extends UI.View.SimpleView {
78
124
  return [this.mimeTypeLabel];
79
125
  }
80
126
 
81
- private onFontContentLoaded(uniqueFontName: string, contentData: TextUtils.ContentData.ContentDataOrError): void {
82
- const url = TextUtils.ContentData.ContentData.isError(contentData) ? this.url : contentData.asDataUrl();
83
- if (!this.fontStyleElement) {
84
- return;
85
- }
86
- this.fontStyleElement.textContent =
87
- Platform.StringUtilities.sprintf('@font-face { font-family: "%s"; src: url(%s); }', uniqueFontName, url);
88
- this.updateFontPreviewSize();
89
- }
90
-
91
- private createContentIfNeeded(): void {
92
- if (this.fontPreviewElement) {
127
+ #loadContentIfNeeded(): void {
128
+ if (this.#contentLoaded) {
93
129
  return;
94
130
  }
131
+ this.#contentLoaded = true;
95
132
 
96
- const uniqueFontName = `WebInspectorFontPreview${++fontId}`;
97
- this.fontStyleElement = document.createElement('style');
133
+ this.#fontFamily = `WebInspectorFontPreview${++fontId}`;
98
134
  void this.contentProvider.requestContentData().then(contentData => {
99
- this.onFontContentLoaded(uniqueFontName, contentData);
135
+ const url = TextUtils.ContentData.ContentData.isError(contentData) ? this.url : contentData.asDataUrl();
136
+ this.#fontFaceRule =
137
+ Platform.StringUtilities.sprintf('@font-face { font-family: "%s"; src: url(%s); }', this.#fontFamily, url);
138
+ this.#previewVisible = true;
139
+ this.requestUpdate();
100
140
  });
101
- this.element.appendChild(this.fontStyleElement);
102
-
103
- const fontPreview = document.createElement('div');
104
- for (let i = 0; i < FONT_PREVIEW_LINES.length; ++i) {
105
- if (i > 0) {
106
- fontPreview.createChild('br');
107
- }
108
- UI.UIUtils.createTextChild(fontPreview, FONT_PREVIEW_LINES[i]);
109
- }
110
- this.fontPreviewElement = (fontPreview.cloneNode(true) as HTMLDivElement);
111
- if (!this.fontPreviewElement) {
112
- return;
113
- }
114
- UI.ARIAUtils.setHidden(this.fontPreviewElement, true);
115
- this.fontPreviewElement.style.overflow = 'hidden';
116
- this.fontPreviewElement.style.setProperty('font-family', uniqueFontName);
117
- this.fontPreviewElement.style.setProperty('visibility', 'hidden');
118
-
119
- this.dummyElement = fontPreview;
120
- this.dummyElement.style.visibility = 'hidden';
121
- this.dummyElement.style.zIndex = '-1';
122
- this.dummyElement.style.display = 'inline';
123
- this.dummyElement.style.position = 'absolute';
124
- this.dummyElement.style.setProperty('font-family', uniqueFontName);
125
- this.dummyElement.style.setProperty('font-size', MEASUURE_FONT_SIZE + 'px');
126
-
127
- this.element.appendChild(this.fontPreviewElement);
128
141
  }
129
142
 
130
143
  override wasShown(): void {
131
144
  super.wasShown();
132
- this.createContentIfNeeded();
133
-
134
- this.updateFontPreviewSize();
145
+ this.#loadContentIfNeeded();
146
+ this.requestUpdate();
135
147
  }
136
148
 
137
149
  override onResize(): void {
138
- if (this.inResize) {
139
- return;
140
- }
141
-
142
- this.inResize = true;
143
- try {
144
- this.updateFontPreviewSize();
145
- } finally {
146
- this.inResize = null;
147
- }
150
+ this.requestUpdate();
148
151
  }
149
152
 
150
- private measureElement(): {
153
+ #calculateFontPreviewSize(dimension: {
151
154
  width: number,
152
155
  height: number,
153
- } {
154
- if (!this.dummyElement) {
155
- throw new Error('No font preview loaded');
156
+ }): string {
157
+ if (!this.#previewVisible || !this.isShowing()) {
158
+ return '';
156
159
  }
157
- this.element.appendChild(this.dummyElement);
158
- const result = {width: this.dummyElement.offsetWidth, height: this.dummyElement.offsetHeight};
159
- this.element.removeChild(this.dummyElement);
160
-
161
- return result;
162
- }
163
-
164
- updateFontPreviewSize(): void {
165
- if (!this.fontPreviewElement || !this.isShowing()) {
166
- return;
167
- }
168
-
169
- this.fontPreviewElement.style.removeProperty('visibility');
170
- const dimension = this.measureElement();
171
160
 
172
161
  const height = dimension.height;
173
162
  const width = dimension.width;
@@ -177,18 +166,42 @@ export class FontView extends UI.View.SimpleView {
177
166
  const containerHeight = this.element.offsetHeight - 30;
178
167
 
179
168
  if (!height || !width || !containerWidth || !containerHeight) {
180
- this.fontPreviewElement.style.removeProperty('font-size');
181
- return;
169
+ return '';
182
170
  }
183
171
 
184
172
  const widthRatio = containerWidth / width;
185
173
  const heightRatio = containerHeight / height;
186
- const finalFontSize = Math.floor(MEASUURE_FONT_SIZE * Math.min(widthRatio, heightRatio)) - 2;
174
+ const finalFontSize = Math.floor(MEASURE_FONT_SIZE * Math.min(widthRatio, heightRatio)) - 2;
187
175
 
188
- this.fontPreviewElement.style.setProperty('font-size', finalFontSize + 'px', undefined);
176
+ return `${finalFontSize}px`;
177
+ }
178
+
179
+ override performUpdate(): void {
180
+ const output: ViewOutput = {};
181
+ this.#view(
182
+ {
183
+ url: this.url,
184
+ fontFaceRule: this.#fontFaceRule,
185
+ fontFamily: this.#fontFamily,
186
+ previewFontSize: this.#previewFontSize,
187
+ previewVisible: this.#previewVisible,
188
+ },
189
+ output,
190
+ this.contentElement,
191
+ );
192
+
193
+ if (!output.measureDimensions) {
194
+ return;
195
+ }
196
+
197
+ const requestedFontSize = this.#calculateFontPreviewSize(output.measureDimensions());
198
+ if (requestedFontSize === this.#previewFontSize) {
199
+ return;
200
+ }
201
+
202
+ this.#previewFontSize = requestedFontSize;
203
+ this.requestUpdate();
189
204
  }
190
205
  }
191
206
 
192
207
  let fontId = 0;
193
- const FONT_PREVIEW_LINES = ['ABCDEFGHIJKLM', 'NOPQRSTUVWXYZ', 'abcdefghijklm', 'nopqrstuvwxyz', '1234567890'];
194
- const MEASUURE_FONT_SIZE = 50;
@@ -65,20 +65,20 @@ const UIStrings = {
65
65
  */
66
66
  doYouTrustThisCode: 'Do you trust this code?',
67
67
  /**
68
- * @description Warning shown to users when pasting text/code into DevTools.
68
+ * @description Warning shown to users when pasting text/code into DevTools. IMPORTANT: keep double quotes around PH1 and do not use single quotes.
69
69
  * @example {allow pasting} PH1
70
70
  */
71
71
  doNotPaste:
72
- 'Don\'t paste code you do not understand or have not reviewed yourself into DevTools. This could allow attackers to steal your identity or take control of your computer. Please type \'\'{PH1}\'\' below to allow pasting.',
72
+ 'Don\'t paste code you do not understand or have not reviewed yourself into DevTools. This could allow attackers to steal your identity or take control of your computer. Please type {PH1} below to allow pasting.',
73
73
  /**
74
74
  * @description Text a user needs to type in order to confirm that they are aware of the danger of pasting code into the DevTools console.
75
75
  */
76
76
  allowPasting: 'allow pasting',
77
77
  /**
78
- * @description Input box placeholder which instructs the user to type 'allow pasting' into the input box.
78
+ * @description Input box placeholder which instructs the user to type 'allow pasting' into the input box. IMPORTANT: keep double quotes around PH1 and do not use single quotes.
79
79
  * @example {allow pasting} PH1
80
80
  */
81
- typeAllowPasting: 'Type \'\'{PH1}\'\'',
81
+ typeAllowPasting: 'Type {PH1}',
82
82
  /**
83
83
  * @description Error message shown when the user tries to open a file that contains non-readable data. "Editor" refers to
84
84
  * a text editor.
@@ -345,6 +345,9 @@ export const knownContextValues = new Set([
345
345
  'ai-assistance-history-images',
346
346
  'ai-assistance-patching-fre-completed',
347
347
  'ai-assistance-patching-selected-project-id',
348
+ 'ai-assistance-v2-opt-in-change-dialog-seen',
349
+ 'ai-assistance-v2-opt-in.got-it',
350
+ 'ai-assistance-v2-opt-in.manage-settings',
348
351
  'ai-code-completion-citations',
349
352
  'ai-code-completion-citations.citation-link',
350
353
  'ai-code-completion-disclaimer',
@@ -366,6 +369,7 @@ export const knownContextValues = new Set([
366
369
  'ai-export-for-agents.save-as-markdown',
367
370
  'ai-hide-walkthrough-sidebar',
368
371
  'ai-show-walkthrough-sidebar',
372
+ 'ai-v2-opt-in-change-dialog',
369
373
  'ai_assistance',
370
374
  'align-content',
371
375
  'align-content-center',
@@ -1104,6 +1108,7 @@ export const knownContextValues = new Set([
1104
1108
  'css',
1105
1109
  'css-angle',
1106
1110
  'css-animation-name',
1111
+ 'css-animations-only-when-animations-tab-open',
1107
1112
  'css-font-palette',
1108
1113
  'css-function',
1109
1114
  'css-layers',
@@ -1809,6 +1814,7 @@ export const knownContextValues = new Set([
1809
1814
  'greendev',
1810
1815
  'greendev-ai-annotations-enabled',
1811
1816
  'greendev-artifact-viewer-enabled',
1817
+ 'greendev-beyond-styling-enabled',
1812
1818
  'greendev-breakpoint-debugger-agent-enabled',
1813
1819
  'greendev-copy-to-gemini-enabled',
1814
1820
  'greendev-emulation-capabilities-enabled',
package/package.json CHANGED
@@ -104,5 +104,5 @@
104
104
  "flat-cache": "6.1.12"
105
105
  }
106
106
  },
107
- "version": "1.0.1611825"
107
+ "version": "1.0.1613465"
108
108
  }
@@ -1,233 +0,0 @@
1
- // Copyright 2025 The Chromium Authors
2
- // Use of this source code is governed by a BSD-style license that can be
3
- // found in the LICENSE file.
4
-
5
- import * as Host from '../../core/host/host.js';
6
- import * as i18n from '../../core/i18n/i18n.js';
7
- import * as GreenDev from '../../models/greendev/greendev.js';
8
- import type * as Workspace from '../../models/workspace/workspace.js';
9
- import * as WorkspaceDiff from '../../models/workspace_diff/workspace_diff.js';
10
- import * as Diff from '../../third_party/diff/diff.js';
11
- import * as Buttons from '../../ui/components/buttons/buttons.js';
12
- import * as Snackbars from '../../ui/components/snackbars/snackbars.js';
13
- import * as UI from '../../ui/legacy/legacy.js';
14
- import * as Lit from '../../ui/lit/lit.js';
15
-
16
- const {render, html} = Lit;
17
-
18
- const UIStrings = {
19
- /**
20
- * @description The message shown in a toast when the response is copied to the clipboard.
21
- */
22
- responseCopiedToClipboard: 'Response copied to clipboard',
23
- } as const;
24
-
25
- const str_ = i18n.i18n.registerUIStrings('panels/common/CopyChangesToPrompt.ts', UIStrings);
26
- const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
27
-
28
- interface CopyChangesDiff {
29
- diff: Diff.Diff.DiffArray|undefined;
30
- uiSourceCode: Workspace.UISourceCode.UISourceCode;
31
- }
32
-
33
- interface GeminiChangesViewInput {
34
- /**
35
- * These are the set of diffs tracked by the Changes Panel.
36
- */
37
- diffs: CopyChangesDiff[];
38
- /**
39
- * Supplied by the Patch Agent if it has made any changes on behalf of the
40
- * user.
41
- */
42
- patchAgentCSSChange: string|null;
43
- onCopyToClipboard: (text: string) => void;
44
- }
45
-
46
- type GeminiChangesView = (input: GeminiChangesViewInput, output: object, target: HTMLElement) => void;
47
-
48
- export class CopyChangesToPrompt extends UI.Widget.Widget {
49
- #workspaceDiff: WorkspaceDiff.WorkspaceDiff.WorkspaceDiffImpl;
50
- readonly #view: GeminiChangesView;
51
- #patchAgentCSSChange: string|null = null;
52
-
53
- constructor(target?: HTMLElement, view = GEMINI_CHANGES_VIEW) {
54
- super(target);
55
- this.#view = view;
56
- this.#workspaceDiff = WorkspaceDiff.WorkspaceDiff.workspaceDiff();
57
- }
58
-
59
- get patchAgentCSSChange(): string|null {
60
- return this.#patchAgentCSSChange;
61
- }
62
- set patchAgentCSSChange(code: string) {
63
- this.#patchAgentCSSChange = code;
64
- this.requestUpdate();
65
- }
66
-
67
- override wasShown(): void {
68
- super.wasShown();
69
- this.#workspaceDiff.addEventListener(
70
- WorkspaceDiff.WorkspaceDiff.Events.MODIFIED_STATUS_CHANGED, this.#onDiffChange, this);
71
- }
72
-
73
- override willHide(): void {
74
- super.willHide();
75
- this.#workspaceDiff.removeEventListener(
76
- WorkspaceDiff.WorkspaceDiff.Events.MODIFIED_STATUS_CHANGED, this.#onDiffChange, this);
77
- }
78
-
79
- #getModifiledFiles(): Workspace.UISourceCode.UISourceCode[] {
80
- return this.#workspaceDiff.modifiedUISourceCodes().filter(modified => {
81
- return !modified.url().startsWith('inspector://');
82
- });
83
- }
84
-
85
- #onDiffChange(): void {
86
- // TODO: track this and unsubscribe to files?
87
- for (const file of this.#getModifiledFiles()) {
88
- this.#workspaceDiff.subscribeToDiffChange(file, this.requestUpdate, this);
89
- }
90
- this.requestUpdate();
91
- }
92
-
93
- set workspaceDiff(diff: WorkspaceDiff.WorkspaceDiff.WorkspaceDiffImpl) {
94
- this.#workspaceDiff = diff;
95
- this.requestUpdate();
96
- }
97
-
98
- override async performUpdate(): Promise<void> {
99
- if (!GreenDev.Prototypes.instance().isEnabled('copyToGemini')) {
100
- // We expect code that renders this component to only do so based on this flag, but this is a double-check.
101
- return;
102
- }
103
-
104
- const diffs = await Promise.all(this.#getModifiledFiles().map(async modifiedUISourceCode => {
105
- const diffResponse = await this.#workspaceDiff?.requestDiff(modifiedUISourceCode);
106
- return {diff: diffResponse?.diff ?? [], uiSourceCode: modifiedUISourceCode};
107
- }));
108
- this.#view(
109
- {
110
- diffs,
111
- patchAgentCSSChange: this.#patchAgentCSSChange,
112
- onCopyToClipboard: (text: string) => {
113
- Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(text);
114
- Snackbars.Snackbar.Snackbar.show({
115
- message: i18nString(UIStrings.responseCopiedToClipboard),
116
- });
117
- }
118
- },
119
- {}, this.contentElement);
120
- }
121
- }
122
- const GEMINI_CHANGES_VIEW: GeminiChangesView = (input, _output, target) => {
123
- const hasDiffs = input.diffs.some(d => {
124
- return d.diff !== undefined && d.diff.length > 0;
125
- });
126
-
127
- if (!hasDiffs && !input.patchAgentCSSChange) {
128
- render(Lit.nothing, target);
129
- return;
130
- }
131
-
132
- const promptForChangesPanel = hasDiffs ? buildGeminiCommand(input.diffs) : '';
133
- const promptForPatchAgentCSS = input.patchAgentCSSChange ? buildPatchAgentCSSPrompt(input.patchAgentCSSChange) : '';
134
-
135
- const finalPrompt = [PREAMBLE, promptForChangesPanel, promptForPatchAgentCSS].filter(x => x.length > 0).join(`\n`);
136
-
137
- function copyClick(): void {
138
- input.onCopyToClipboard(finalPrompt);
139
- }
140
- // clang-format off
141
- render(html`<devtools-button
142
- .iconName=${'copy'}
143
- .variant=${Buttons.Button.Variant.OUTLINED}
144
- @click=${copyClick}>Copy prompt to clipboard</devtools-button>
145
- `, target);
146
- // clang-format on
147
- };
148
-
149
- function buildPatchAgentCSSPrompt(code: string): string {
150
- // clang-format off
151
- return `The DevTools CSS Patching Agent has also made some CSS changes on behalf of the user. These changes are listed below. Think carefully about how best to apply these changes.
152
-
153
- **DevTools Patch Agent changes**
154
- \`\`\`
155
- ${code}
156
- \`\`\``;
157
- // clang-format on
158
- }
159
-
160
- function buildGeminiCommand(diffs: readonly CopyChangesDiff[]): string {
161
- // clang-format off
162
- const output = `Below this line are a list of files and the diff for each of them. Consider this diff and apply it to the codebase but remembering that the changes in DevTools may not be the most accurate fixes and you should not necessarily apply them directly as DevTools works with the deployed code and not the source code.
163
-
164
- Within the diffs you will often see lines of code commented out. If the diff contains a new line that was some code being wrapped in comments, treat that as if the intent is to delete the code.
165
-
166
- How to read a diff:
167
- * If a line starts with \`-\` , DevTools removed it.
168
- * If a line starts with \`+\` , DevTools added it.
169
- * If a line starts with neither a \`+\` or \`-\` , DevTools did not change the line and you can safely ignore it.
170
-
171
- ${diffs.map(diff => {
172
- if (!diff.diff || diff.diff.length === 0) {
173
- return '';
174
- }
175
- return `Filename: ${diff.uiSourceCode.fullDisplayName()}
176
-
177
- Diff:
178
- ${formatDiffForLLM(diff.diff)}`;
179
- }).filter(x => x.length).join('\n')}`;
180
- // clang-format on
181
- return output;
182
- }
183
-
184
- function formatDiffForLLM(diffArray: Diff.Diff.DiffArray): string {
185
- let formattedDiff = '';
186
- for (const diffItem of diffArray) {
187
- const operation = diffItem[0];
188
- const lines = diffItem[1];
189
-
190
- for (const line of lines) {
191
- if (operation === Diff.Diff.Operation.Equal) {
192
- formattedDiff += ' ' + line + '\n';
193
- } else if (operation === Diff.Diff.Operation.Insert) {
194
- formattedDiff += '+ ' + line + '\n';
195
- } else if (operation === Diff.Diff.Operation.Delete) {
196
- formattedDiff += '- ' + line + '\n';
197
- }
198
- }
199
- }
200
- return formattedDiff;
201
- }
202
-
203
- const PREAMBLE =
204
- `You are receiving a set of runtime changes (CSS, HTML, and JS) captured via Browser DevTools. Your goal is to persist these changes into the local source code by identifying the original source-of-truth.
205
-
206
- Because DevTools reflects the "Flattened Result" of complex build logic, you must follow this "Source-Aware" strategy:
207
-
208
- 1. **Structural Mapping (HTML/DOM):**
209
- - If a DOM element was added/removed, identify the source template (JSX, Vue, Svelte, HTML) responsible.
210
- - **Logic Check:** Determine if the change should be a static element or if it requires a new conditional (\`if/else\`) or loop (\`map\`) based on existing component patterns.
211
-
212
- 2. **Style Mapping (CSS/Attributes):**
213
- - Map raw styles to the project's specific styling architecture (Tailwind, SCSS, Styled-Components).
214
- - Replace hard-coded values with existing Design Tokens or Theme Variables (\`var(--color-primary)\`, etc.) found in the codebase.
215
- - CSS changes may not be applied to CSS files directly. Consider that CSS could be applied via JavaScript, especially if the codebase is using a component based frontend framework or web components.
216
-
217
-
218
- 3. **Behavioral Mapping (JS/Event Listeners):**
219
- - If logic or event handlers were modified, locate the corresponding functions or hooks in the source.
220
- - Ensure new logic follows the project's state management patterns (e.g., React \`useState\`, Redux, or standard ES6+ modules).
221
-
222
- 4. **Safety & Ambiguity Protocol:**
223
- - **Third-Party Code:** If a change targets a DOM element or style generated by an external library (e.g., a UI kit's internal wrapper), do not modify the library's source. Instead, find the appropriate override mechanism in the codebase.
224
- - **Uncertainty:** If a DevTools change cannot be mapped to the source with 100% confidence (e.g., minified selectors or ambiguous origin), stop and report the conflict rather than guessing.
225
-
226
- 5. **Agentic Execution Workflow:**
227
- - **Discovery:** Use your tools (\`grep\`, \`find\`, etc.) to locate unique strings or class names from the DevTools log.
228
- - **Analysis:** Determine if the target is a reusable component or a specific page instance.
229
- - **Implementation:** Execute file edits using the project's idiomatic syntax and formatting standards.
230
-
231
- **INSTRUCTION:**
232
- Begin by searching for the relevant source files. Explain your mapping logic before performing the edits.
233
- `;