chrome-devtools-frontend 1.0.1596535 → 1.0.1597624

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 (101) hide show
  1. package/agents/prompts/ui-widgets.md +7 -8
  2. package/docs/ui_engineering.md +10 -11
  3. package/front_end/core/host/AidaClient.ts +4 -0
  4. package/front_end/core/host/InspectorFrontendHostAPI.ts +1 -0
  5. package/front_end/core/host/UserMetrics.ts +12 -0
  6. package/front_end/core/root/Runtime.ts +5 -0
  7. package/front_end/core/sdk/CPUThrottlingManager.ts +14 -13
  8. package/front_end/core/sdk/CSSMatchedStyles.ts +2 -0
  9. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +28 -0
  10. package/front_end/core/sdk/PageResourceLoader.ts +22 -1
  11. package/front_end/devtools_compatibility.js +2 -1
  12. package/front_end/models/ai_assistance/AiConversation.ts +29 -8
  13. package/front_end/models/ai_assistance/ChangeManager.ts +16 -0
  14. package/front_end/models/ai_assistance/ExtensionScope.ts +11 -3
  15. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +127 -0
  16. package/front_end/models/ai_assistance/agents/AiAgent.ts +26 -3
  17. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +1 -1
  18. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +11 -8
  19. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +24 -0
  20. package/front_end/models/ai_assistance/agents/StylingAgent.ts +323 -16
  21. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  22. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +27 -0
  23. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +21 -0
  24. package/front_end/models/greendev/Prototypes.ts +7 -1
  25. package/front_end/models/trace/Processor.ts +1 -0
  26. package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +33 -0
  27. package/front_end/models/trace/insights/CharacterSet.ts +172 -0
  28. package/front_end/models/trace/insights/Models.ts +1 -0
  29. package/front_end/models/trace/insights/types.ts +1 -0
  30. package/front_end/models/trace/types/TraceEvents.ts +17 -0
  31. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +51 -36
  32. package/front_end/panels/ai_assistance/PatchWidget.ts +6 -6
  33. package/front_end/panels/ai_assistance/components/ChatMessage.ts +93 -74
  34. package/front_end/panels/ai_assistance/components/ChatView.ts +6 -11
  35. package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +18 -9
  36. package/front_end/panels/ai_assistance/components/StylingAgentMarkdownRenderer.ts +200 -0
  37. package/front_end/panels/ai_assistance/components/chatMessage.css +11 -8
  38. package/front_end/panels/application/AppManifestView.ts +3 -4
  39. package/front_end/panels/application/DeviceBoundSessionsView.ts +18 -22
  40. package/front_end/panels/application/FrameDetailsView.ts +9 -15
  41. package/front_end/panels/application/OriginTrialTreeView.ts +2 -3
  42. package/front_end/panels/application/ReportingApiView.ts +13 -17
  43. package/front_end/panels/application/ServiceWorkerCacheViews.ts +1 -1
  44. package/front_end/panels/application/components/BackForwardCacheView.ts +3 -3
  45. package/front_end/panels/application/preloading/components/UsedPreloadingView.ts +2 -3
  46. package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +3 -2
  47. package/front_end/panels/changes/ChangesView.ts +6 -4
  48. package/front_end/panels/common/ExtensionServer.ts +15 -0
  49. package/front_end/panels/console/ConsolePinPane.ts +3 -3
  50. package/front_end/panels/coverage/CoverageListView.ts +1 -1
  51. package/front_end/panels/css_overview/CSSOverviewPanel.ts +11 -15
  52. package/front_end/panels/developer_resources/DeveloperResourcesView.ts +3 -5
  53. package/front_end/panels/elements/ElementsTreeElement.ts +55 -47
  54. package/front_end/panels/elements/ElementsTreeOutline.ts +149 -28
  55. package/front_end/panels/elements/EventListenersWidget.ts +3 -2
  56. package/front_end/panels/elements/StandaloneStylesContainer.ts +21 -6
  57. package/front_end/panels/elements/StylePropertyTreeElement.ts +49 -4
  58. package/front_end/panels/layer_viewer/Layers3DView.ts +5 -4
  59. package/front_end/panels/lighthouse/LighthousePanel.ts +8 -0
  60. package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +5 -6
  61. package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +6 -11
  62. package/front_end/panels/network/RequestCookiesView.ts +3 -4
  63. package/front_end/panels/network/RequestInitiatorView.ts +7 -5
  64. package/front_end/panels/network/RequestResponseView.ts +10 -15
  65. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +3 -4
  66. package/front_end/panels/recorder/components/RecordingView.ts +31 -36
  67. package/front_end/panels/recorder/components/StepEditor.ts +6 -7
  68. package/front_end/panels/search/SearchView.ts +2 -3
  69. package/front_end/panels/settings/SettingsScreen.ts +3 -2
  70. package/front_end/panels/settings/WorkspaceSettingsTab.ts +2 -5
  71. package/front_end/panels/timeline/components/LiveMetricsView.ts +5 -8
  72. package/front_end/panels/timeline/components/insights/Cache.ts +8 -10
  73. package/front_end/panels/timeline/components/insights/CharacterSet.ts +38 -0
  74. package/front_end/panels/timeline/components/insights/DOMSize.ts +16 -20
  75. package/front_end/panels/timeline/components/insights/DocumentLatency.ts +2 -6
  76. package/front_end/panels/timeline/components/insights/DuplicatedJavaScript.ts +3 -4
  77. package/front_end/panels/timeline/components/insights/FontDisplay.ts +3 -4
  78. package/front_end/panels/timeline/components/insights/ForcedReflow.ts +5 -7
  79. package/front_end/panels/timeline/components/insights/INPBreakdown.ts +3 -4
  80. package/front_end/panels/timeline/components/insights/ImageDelivery.ts +3 -4
  81. package/front_end/panels/timeline/components/insights/ImageRef.ts +2 -4
  82. package/front_end/panels/timeline/components/insights/InsightRenderer.ts +2 -0
  83. package/front_end/panels/timeline/components/insights/LCPBreakdown.ts +5 -7
  84. package/front_end/panels/timeline/components/insights/LCPDiscovery.ts +2 -4
  85. package/front_end/panels/timeline/components/insights/LegacyJavaScript.ts +3 -4
  86. package/front_end/panels/timeline/components/insights/ModernHTTP.ts +3 -4
  87. package/front_end/panels/timeline/components/insights/NetworkDependencyTree.ts +7 -11
  88. package/front_end/panels/timeline/components/insights/NodeLink.ts +2 -4
  89. package/front_end/panels/timeline/components/insights/RenderBlocking.ts +3 -4
  90. package/front_end/panels/timeline/components/insights/SlowCSSSelector.ts +7 -10
  91. package/front_end/panels/timeline/components/insights/ThirdParties.ts +5 -7
  92. package/front_end/panels/timeline/components/insights/insights.ts +2 -0
  93. package/front_end/panels/web_audio/WebAudioView.ts +3 -4
  94. package/front_end/ui/components/settings/SettingCheckbox.ts +2 -0
  95. package/front_end/ui/legacy/UIUtils.ts +5 -5
  96. package/front_end/ui/legacy/Widget.ts +33 -2
  97. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
  98. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +8 -8
  99. package/front_end/ui/visual_logging/Debugging.ts +0 -32
  100. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  101. package/package.json +1 -1
@@ -7,23 +7,27 @@ import * as i18n from '../../../core/i18n/i18n.js';
7
7
  import * as Platform from '../../../core/platform/platform.js';
8
8
  import * as Root from '../../../core/root/root.js';
9
9
  import * as SDK from '../../../core/sdk/sdk.js';
10
- import type * as Protocol from '../../../generated/protocol.js';
10
+ import * as Protocol from '../../../generated/protocol.js';
11
+ import * as Greendev from '../../../models/greendev/greendev.js';
11
12
  import * as Annotations from '../../annotations/annotations.js';
13
+ import * as Emulation from '../../emulation/emulation.js';
12
14
  import {ChangeManager} from '../ChangeManager.js';
13
15
  import {debugLog} from '../debug.js';
14
16
  import {EvaluateAction, formatError, SideEffectError} from '../EvaluateAction.js';
15
17
  import {ExtensionScope} from '../ExtensionScope.js';
16
- import {FREESTYLER_WORLD_NAME} from '../injected.js';
18
+ import {AI_ASSISTANCE_CSS_CLASS_NAME, FREESTYLER_WORLD_NAME} from '../injected.js';
17
19
 
18
20
  import {
19
21
  type AgentOptions as BaseAgentOptions,
20
22
  AiAgent,
23
+ type AnswerResponse,
21
24
  type ComputedStyleAiWidget,
22
25
  type ContextResponse,
23
26
  ConversationContext,
24
27
  type ConversationSuggestions,
25
28
  type FunctionCallHandlerResult,
26
29
  type FunctionHandlerOptions,
30
+ type MultimodalInput,
27
31
  MultimodalInputType,
28
32
  type RequestOptions,
29
33
  ResponseType
@@ -45,13 +49,14 @@ const UIStringsNotTranslate = {
45
49
 
46
50
  const lockedString = i18n.i18n.lockedString;
47
51
 
48
- /**
49
- * WARNING: preamble defined in code is only used when userTier is
50
- * TESTERS. Otherwise, a server-side preamble is used (see
51
- * chrome_preambles.gcl). Sync local changes with the server-side.
52
- */
53
- /* clang-format off */
54
- const preamble = `You are the most advanced CSS/DOM/HTML debugging assistant integrated into Chrome DevTools.
52
+ function getPreamble(): string {
53
+ /**
54
+ * WARNING: preamble defined in code is only used when userTier is
55
+ * TESTERS. Otherwise, a server-side preamble is used (see
56
+ * chrome_preambles.gcl). Sync local changes with the server-side.
57
+ */
58
+ /* clang-format off */
59
+ let preamble = `You are the most advanced CSS/DOM/HTML debugging assistant integrated into Chrome DevTools.
55
60
  You always suggest considering the best web development practices and the newest platform features such as view transitions.
56
61
  The user selected a DOM element in the browser's DevTools and sends a query about the page or the selected DOM element.
57
62
  First, examine the provided context, then use the functions to gather additional context and resolve the user request.
@@ -73,6 +78,47 @@ First, examine the provided context, then use the functions to gather additional
73
78
  * **CRITICAL** NEVER output text before a function call. Always do a function call first.
74
79
  * **CRITICAL** When answering questions about positioning or layout, ALWAYS inspect \`position\`, \`display\` and ALL related properties.
75
80
  * **CRITICAL** You are a CSS/DOM/HTML debugging assistant. NEVER provide answers to questions of unrelated topics such as legal advice, financial advice, personal opinions, medical advice, religion, race, politics, sexuality, gender, or any other non web-development topics. Answer "Sorry, I can't answer that. I'm best at questions about debugging web pages." to such questions.`;
81
+
82
+ const greenDevEmulationEnabled = Greendev.Prototypes.instance().isEnabled('emulationCapabilities');
83
+ if (greenDevEmulationEnabled) {
84
+ preamble += `
85
+ # Emulation and Screenshots
86
+
87
+ * If asked to verify whether the page is visually broken or if there are display problems with specific devices, use the \`activateDeviceEmulation\` tool. This tool will activate emulation for a specified device and capture a screenshot.
88
+ * **DEVICE SELECTION**: You must choose the most closely related device match from the allowed list.
89
+ * If the user asks about a specific device (e.g., "iPhone 6"), choose the closest match (e.g., "iPhone 6/7/8").
90
+ * If the user specifies a generic category (e.g., "Android phone", "iPhone", "Samsung"), choose the device with the highest version number available in that category (e.g., "Pixel 7" or "Samsung Galaxy S20" for Android, "iPhone 14 Pro Max" for iPhone).
91
+ * **VISION DEFICIENCY**: If the user asks about checking for color blindness or vision issues, you can pass an optional \`visionDeficiency\` parameter to \`activateDeviceEmulation\`. Allowed values are: 'blurredVision', 'reducedContrast', 'achromatopsia', 'deuteranopia', 'protanopia', 'tritanopia'.
92
+ * **IMPORTANT**: This is a **TWO-STEP** process.
93
+ * **STEP 1**: Call \`activateDeviceEmulation\`. After calling this tool, YOU MUST STOP and tell the user that the screenshot has been captured and ask them whether they would like you to focus on specific sections of the screenshot or review it all for possible problems.
94
+ * **STEP 2**: The captured screenshot will be automatically attached to the user's **NEXT** query.
95
+ * **CRITICAL**: DO NOT try to investigate/analyze the page state or element visibility automatically. But, after the user has requested to analyze the page, you can prompt the user to select one of the problematic elements if they want to diagnose further.
96
+ * **CRITICAL**: The output of the analysis should only be in json form (no supplemental text) and the json should list the problems found on the device, with a short description of the problem. If identical problems are identified acress multiple devices, feel free to combine sections.
97
+ * **CRITICAL**: ALWAYS escape single and double quotes within the json output strings (\' and \").
98
+ *
99
+ * Example (with no duplication):
100
+
101
+ [
102
+ {
103
+ "Problem": "Element not resizing",
104
+ "Element": "Hero banner",
105
+ "NodeId": "23",
106
+ "Details": "The \"hero\" element is not resizing because... etc etc."
107
+ }
108
+ ]
109
+
110
+ # Additional notes:
111
+
112
+ When referring to an element for which you know the nodeId, annotate your output using markdown link syntax:
113
+ - For example, if nodeId is 23: ([link](#node-23))
114
+ - Always prefix the nodeId with the 'node-' prefix when using the markdown syntax.
115
+ - This link will reveal the element in the Elements panel
116
+ - Never mention node or nodeId when referring to the element, and especially not in the link text.`;
117
+ }
118
+
119
+ return preamble;
120
+ }
121
+
76
122
  /* clang-format on */
77
123
 
78
124
  const promptForScreenshot =
@@ -149,6 +195,8 @@ async function executeJsCode(
149
195
  const MAX_OBSERVATION_BYTE_LENGTH = 25_000;
150
196
  const OBSERVATION_TIMEOUT = 5_000;
151
197
 
198
+ export const AI_ASSISTANCE_FILTER_REGEX = `\\.${AI_ASSISTANCE_CSS_CLASS_NAME}-.*&`;
199
+
152
200
  type CreateExtensionScopeFunction = (changes: ChangeManager) => {
153
201
  install(): Promise<void>, uninstall(): Promise<void>,
154
202
  };
@@ -237,10 +285,11 @@ export class NodeContext extends ConversationContext<SDK.DOMModel.DOMNode> {
237
285
  * instance for a new conversation.
238
286
  */
239
287
  export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
240
- preamble = preamble;
288
+ preamble = getPreamble();
241
289
  readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_STYLING_AGENT;
242
290
  get userTier(): string|undefined {
243
- return Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
291
+ const greenDevEmulationEnabled = Greendev.Prototypes.instance().isEnabled('emulationCapabilities');
292
+ return greenDevEmulationEnabled ? 'TESTERS' : Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
244
293
  }
245
294
  get executionMode(): Root.Runtime.HostConfigFreestylerExecutionMode {
246
295
  return Root.Runtime.hostConfig.devToolsFreestyler?.executionMode ??
@@ -269,15 +318,19 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
269
318
 
270
319
  #changes: ChangeManager;
271
320
  #createExtensionScope: CreateExtensionScopeFunction;
321
+ #greenDevEmulationScreenshot: string|null = null;
322
+ #greenDevEmulationAxTree: string|null = null;
323
+ #currentTurnId = 0;
272
324
 
273
325
  constructor(opts: AgentOptions) {
274
326
  super(opts);
275
327
 
276
328
  this.#changes = opts.changeManager || new ChangeManager();
277
329
  this.#execJs = opts.execJs ?? executeJsCode;
278
- this.#createExtensionScope = opts.createExtensionScope ?? ((changes: ChangeManager) => {
279
- return new ExtensionScope(changes, this.sessionId, this.context?.getItem() ?? null);
280
- });
330
+ this.#createExtensionScope =
331
+ opts.createExtensionScope ?? ((changes: ChangeManager) => {
332
+ return new ExtensionScope(changes, this.sessionId, this.context?.getItem() ?? null, this.#currentTurnId);
333
+ });
281
334
 
282
335
  this.declareFunction<{
283
336
  elements: number[],
@@ -447,6 +500,37 @@ const data = {
447
500
  },
448
501
  });
449
502
  }
503
+
504
+ this.declareFunction<{
505
+ deviceName: string,
506
+ visionDeficiency?: string,
507
+ }>('activateDeviceEmulation', {
508
+ description:
509
+ 'Sets emulation viewing mode for a specific device and optionally enables vision deficiency emulation.',
510
+ parameters: {
511
+ type: Host.AidaClient.ParametersTypes.OBJECT,
512
+ description: '',
513
+ nullable: false,
514
+ properties: {
515
+ deviceName: {
516
+ type: Host.AidaClient.ParametersTypes.STRING,
517
+ description:
518
+ 'The name of the device to emulate. Allowed values: Pixel 3 XL, Pixel 7, Samsung Galaxy S8+, Samsung Galaxy S20 Ultra, Surface Pro 7, Surface Duo, Galaxy Z Fold 5, Asus Zenbook Fold, Samsung Galaxy A51/71, Nest Hub Max, Nest Hub, iPhone 4, iPhone 5/SE, iPhone 6/7/8, iPhone SE, iPhone XR, iPhone 12 Pro, iPhone 14 Pro Max, iPad Mini, iPad Air, iPad Pro.',
519
+ nullable: false,
520
+ },
521
+ visionDeficiency: {
522
+ type: Host.AidaClient.ParametersTypes.STRING,
523
+ description:
524
+ 'Optional vision deficiency to emulate. Allowed values: blurredVision, reducedContrast, achromatopsia, deuteranopia, protanopia, tritanopia.',
525
+ nullable: true,
526
+ },
527
+ },
528
+ required: ['deviceName']
529
+ },
530
+ handler: async params => {
531
+ return await this.activateDeviceEmulation(params.deviceName, params.visionDeficiency);
532
+ },
533
+ });
450
534
  }
451
535
 
452
536
  async generateObservation(
@@ -761,6 +845,198 @@ const data = {
761
845
  };
762
846
  }
763
847
 
848
+ async #compressScreenshot(base64Data: string): Promise<string> {
849
+ return await new Promise((resolve, reject) => {
850
+ const img = new Image();
851
+ img.onload = () => {
852
+ // eslint-disable-next-line @devtools/no-imperative-dom-api
853
+ const canvas = document.createElement('canvas');
854
+ const maxDimension = 2000;
855
+ let scale = 1;
856
+ if (img.width > maxDimension || img.height > maxDimension) {
857
+ scale = maxDimension / Math.max(img.width, img.height);
858
+ }
859
+ canvas.width = img.width * scale;
860
+ canvas.height = img.height * scale;
861
+
862
+ const ctx = canvas.getContext('2d');
863
+ if (!ctx) {
864
+ reject(new Error('Could not get canvas context'));
865
+ return;
866
+ }
867
+ ctx.imageSmoothingEnabled = true;
868
+ ctx.imageSmoothingQuality = 'high';
869
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
870
+ const dataUrl = canvas.toDataURL('image/jpeg', 0.9);
871
+ resolve(dataUrl.split(',')[1]);
872
+ };
873
+ img.onerror = e => reject(new Error('Image load error: ' + e));
874
+ img.src = 'data:image/png;base64,' + base64Data;
875
+ });
876
+ }
877
+
878
+ async activateDeviceEmulation(deviceName: string, visionDeficiency?: string):
879
+ Promise<FunctionCallHandlerResult<unknown>> {
880
+ const greenDevEmulationEnabled = Greendev.Prototypes.instance().isEnabled('emulationCapabilities');
881
+ if (!greenDevEmulationEnabled) {
882
+ return {error: `GreenDev emulation capabilities not enabled`};
883
+ }
884
+
885
+ // eslint-disable-next-line no-console
886
+ console.log('activateDeviceEmulation called with device:', deviceName, 'visionDeficiency:', visionDeficiency);
887
+
888
+ this.#greenDevEmulationScreenshot = null;
889
+ this.#greenDevEmulationAxTree = null;
890
+
891
+ const emulatedDevicesList = Emulation.EmulatedDevices.EmulatedDevicesList.instance();
892
+ const device = emulatedDevicesList.standard().find(d => d.title === deviceName);
893
+
894
+ if (!device) {
895
+ return {
896
+ error: `Could not find device "${deviceName}" in the list of emulated devices.`,
897
+ };
898
+ }
899
+
900
+ const deviceModeModel = Emulation.DeviceModeModel.DeviceModeModel.instance();
901
+
902
+ const verticalMode = device.modesForOrientation(Emulation.EmulatedDevices.Vertical)[0];
903
+ if (!verticalMode) {
904
+ return {
905
+ error: `Could not find vertical mode for "${deviceName}".`,
906
+ };
907
+ }
908
+ deviceModeModel.emulate(Emulation.DeviceModeModel.Type.Device, device, verticalMode);
909
+
910
+ // Get the selected node early to use for both vision deficiency and wait mechanism.
911
+ const selectedNode = this.#getSelectedNode();
912
+
913
+ // Apply vision deficiency if provided (and turn it off when not provided).
914
+ try {
915
+ if (selectedNode) {
916
+ const target = selectedNode.domModel().target();
917
+ const emulationModel = target.model(SDK.EmulationModel.EmulationModel);
918
+ if (emulationModel) {
919
+ let type = Protocol.Emulation.SetEmulatedVisionDeficiencyRequestType.None;
920
+ if (visionDeficiency && visionDeficiency !== 'none') {
921
+ type = visionDeficiency as Protocol.Emulation.SetEmulatedVisionDeficiencyRequestType;
922
+ }
923
+ await target.emulationAgent().invoke_setEmulatedVisionDeficiency({type});
924
+ }
925
+ } else {
926
+ console.error('No selected node context to retrieve EmulationModel.');
927
+ }
928
+ } catch {
929
+ return {
930
+ error: `Unable to apply vision deficiency "${visionDeficiency}".`,
931
+ };
932
+ }
933
+
934
+ // Wait for the layout to settle after emulation changes.
935
+ // We use a double requestAnimationFrame to ensure at least one frame is rendered.
936
+ if (selectedNode) {
937
+ try {
938
+ const code = 'await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))';
939
+ // We use throwOnSideEffect: false because this is a benign wait, not a modification of the page state relevant to the user.
940
+ await this.#execJs(code, {throwOnSideEffect: false, contextNode: selectedNode});
941
+ } catch (e) {
942
+ console.error('Failed to wait for layout settle:', e);
943
+ }
944
+ }
945
+
946
+ const orientation = device.orientationByName(Emulation.EmulatedDevices.Vertical);
947
+ const width = orientation.width;
948
+
949
+ // TODO(finnur): Investigate better screen capture alternatives (that can do the whole page).
950
+ let documentHeight = 2000;
951
+ if (selectedNode) {
952
+ try {
953
+ const heightJs = 'document.body.scrollHeight';
954
+ const result = await this.#execJs(heightJs, {throwOnSideEffect: false, contextNode: selectedNode});
955
+ const parsedHeight = Number(result);
956
+ if (!isNaN(parsedHeight)) {
957
+ documentHeight = Math.min(parsedHeight, 2000);
958
+ }
959
+ } catch (e) {
960
+ console.error('Failed to get document height:', e);
961
+ }
962
+ }
963
+
964
+ // Specify a clip capping the height to the top 5000px.
965
+ const clip: Protocol.Page.Viewport = {
966
+ x: 0,
967
+ y: 0,
968
+ width,
969
+ height: documentHeight,
970
+ scale: 1,
971
+ };
972
+
973
+ // Capture using the clip. fullSize must be false when clip is used.
974
+ const screenshot = await deviceModeModel.captureScreenshot(false, clip);
975
+
976
+ if (!screenshot) {
977
+ return {
978
+ error: `Emulation for ${deviceName} activated, but failed to capture screenshot.`,
979
+ };
980
+ }
981
+
982
+ try {
983
+ this.#greenDevEmulationScreenshot = await this.#compressScreenshot(screenshot);
984
+ } catch (e) {
985
+ console.error('Screenshot compression failed, using original', e);
986
+ this.#greenDevEmulationScreenshot = screenshot;
987
+ }
988
+
989
+ try {
990
+ if (selectedNode) {
991
+ const accessibilityModel = selectedNode.domModel().target().model(SDK.AccessibilityModel.AccessibilityModel);
992
+ if (accessibilityModel) {
993
+ await accessibilityModel.resumeModel();
994
+ const axResponse = await accessibilityModel.agent.invoke_getFullAXTree({});
995
+ if (!axResponse.getError()) {
996
+ this.#greenDevEmulationAxTree = JSON.stringify(axResponse.nodes);
997
+ } else {
998
+ console.error('Failed to capture Accessibility Tree:', axResponse.getError());
999
+ }
1000
+ }
1001
+ }
1002
+ } catch (e) {
1003
+ console.error('Exception capturing Accessibility Tree:', e);
1004
+ }
1005
+
1006
+ let resultMsg = `Emulation for ${deviceName} activated and screenshot has been captured.`;
1007
+ if (visionDeficiency) {
1008
+ resultMsg += ` Vision deficiency "${visionDeficiency}" was also applied.`;
1009
+ }
1010
+ resultMsg += ' Ready for analysis.';
1011
+
1012
+ return {
1013
+ result: resultMsg,
1014
+ };
1015
+ }
1016
+
1017
+ override popPendingMultimodalInput(): MultimodalInput|undefined {
1018
+ const greenDevEmulationEnabled = Greendev.Prototypes.instance().isEnabled('emulationCapabilities');
1019
+ if (!greenDevEmulationEnabled) {
1020
+ return undefined;
1021
+ }
1022
+
1023
+ if (this.#greenDevEmulationScreenshot) {
1024
+ const data = this.#greenDevEmulationScreenshot;
1025
+ this.#greenDevEmulationScreenshot = null;
1026
+ return {
1027
+ type: MultimodalInputType.SCREENSHOT,
1028
+ input: {
1029
+ inlineData: {
1030
+ data,
1031
+ mimeType: 'image/jpeg',
1032
+ },
1033
+ },
1034
+ id: crypto.randomUUID(),
1035
+ };
1036
+ }
1037
+ return undefined;
1038
+ }
1039
+
764
1040
  override async *
765
1041
  handleContextDetails(selectedElement: ConversationContext<SDK.DOMModel.DOMNode>|null):
766
1042
  AsyncGenerator<ContextResponse, void, void> {
@@ -777,15 +1053,46 @@ const data = {
777
1053
  };
778
1054
  }
779
1055
 
1056
+ protected override async preRun(): Promise<void> {
1057
+ this.#currentTurnId++;
1058
+ }
1059
+
1060
+ protected override async finalizeAnswer(answer: AnswerResponse): Promise<AnswerResponse> {
1061
+ if (!Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled) {
1062
+ return answer;
1063
+ }
1064
+
1065
+ const changedNodeIds = this.#changes.getChangedNodesForGroupId(this.sessionId, this.#currentTurnId);
1066
+ if (changedNodeIds.length === 0) {
1067
+ return answer;
1068
+ }
1069
+ answer.widgets = [
1070
+ ...(answer.widgets ?? []),
1071
+ ...changedNodeIds.map(id => ({
1072
+ name: 'STYLE_PROPERTIES' as const,
1073
+ data: {
1074
+ backendNodeId: id,
1075
+ selector: AI_ASSISTANCE_FILTER_REGEX,
1076
+ },
1077
+ })),
1078
+ ];
1079
+ return answer;
1080
+ }
780
1081
  override async enhanceQuery(
781
1082
  query: string, selectedElement: ConversationContext<SDK.DOMModel.DOMNode>|null,
782
1083
  multimodalInputType?: MultimodalInputType): Promise<string> {
1084
+ let multimodalInputEnhancementQuery =
1085
+ this.multimodalInputEnabled && multimodalInputType ? MULTIMODAL_ENHANCEMENT_PROMPTS[multimodalInputType] : '';
1086
+
1087
+ if (this.#greenDevEmulationAxTree) {
1088
+ multimodalInputEnhancementQuery += '\n# Accessibility Tree\n\n' + this.#greenDevEmulationAxTree;
1089
+ this.#greenDevEmulationAxTree = null;
1090
+ }
1091
+
783
1092
  const elementEnchancementQuery = selectedElement ?
784
1093
  `# Inspected element\n\n${
785
1094
  await StylingAgent.describeElement(selectedElement.getItem())}\n\n# User request\n\n` :
786
1095
  '';
787
- const multimodalInputEnhancementQuery =
788
- this.multimodalInputEnabled && multimodalInputType ? MULTIMODAL_ENHANCEMENT_PROMPTS[multimodalInputType] : '';
789
1096
  return `${multimodalInputEnhancementQuery}${elementEnchancementQuery}QUERY: ${query}`;
790
1097
  }
791
1098
  }
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as AgentProject from './AgentProject.js';
6
+ import * as AccessibilityAgent from './agents/AccessibilityAgent.js';
6
7
  import * as AiAgent from './agents/AiAgent.js';
7
8
  import * as BreakpointDebuggerAgent from './agents/BreakpointDebuggerAgent.js';
8
9
  import * as ContextSelectionAgent from './agents/ContextSelectionAgent.js';
@@ -32,6 +33,7 @@ import * as AIContext from './performance/AIContext.js';
32
33
  import * as AIQueries from './performance/AIQueries.js';
33
34
 
34
35
  export {
36
+ AccessibilityAgent,
35
37
  AgentProject,
36
38
  AiAgent,
37
39
  AICallTree,
@@ -203,6 +203,10 @@ export class PerformanceInsightFormatter {
203
203
  {title: 'Is my site polyfilling modern JavaScript features?'},
204
204
  {title: 'How can I reduce the amount of legacy JavaScript on my page?'},
205
205
  ];
206
+ case 'CharacterSet':
207
+ return [
208
+ {title: 'How do I declare a character encoding for my page?'},
209
+ ];
206
210
  default:
207
211
  throw new Error(`Unknown insight key '${this.#insight.insightKey}'`);
208
212
  }
@@ -881,6 +885,20 @@ ${requestSummary}`;
881
885
  * @param insight The Network Dependency Tree Insight Model to query.
882
886
  * @returns a string formatted for sending to Ask AI.
883
887
  */
888
+ formatCharacterSetInsight(insight: Trace.Insights.Models.CharacterSet.CharacterSetInsightModel): string {
889
+ let output = '';
890
+ if (insight.data) {
891
+ output += 'HTTP Content-Type header charset: ' + (insight.data.hasHttpCharset ? 'present' : 'missing') + '.\n';
892
+ output += 'HTML meta charset disposition: ' + (insight.data.metaCharsetDisposition ?? 'unknown') + '.\n';
893
+
894
+ if (!insight.data.hasHttpCharset && insight.data.metaCharsetDisposition !== 'found-in-first-1024-bytes') {
895
+ output +=
896
+ '\nThe page does not declare character encoding via HTTP header or a meta charset tag in the first 1024 bytes.\n';
897
+ }
898
+ }
899
+ return output;
900
+ }
901
+
884
902
  formatViewportInsight(insight: Trace.Insights.Models.Viewport.ViewportInsightModel): string {
885
903
  let output = '';
886
904
 
@@ -995,6 +1013,10 @@ ${this.#links()}`;
995
1013
  return this.formatViewportInsight(this.#insight);
996
1014
  }
997
1015
 
1016
+ if (Trace.Insights.Models.CharacterSet.isCharacterSetInsight(this.#insight)) {
1017
+ return this.formatCharacterSetInsight(this.#insight);
1018
+ }
1019
+
998
1020
  return '';
999
1021
  }
1000
1022
 
@@ -1074,6 +1096,9 @@ ${this.#links()}`;
1074
1096
  links.push('https://web.dev/articles/baseline-and-polyfills');
1075
1097
  links.push('https://philipwalton.com/articles/the-state-of-es5-on-the-web/');
1076
1098
  break;
1099
+ case 'CharacterSet':
1100
+ links.push('https://developer.chrome.com/docs/insights/charset/');
1101
+ break;
1077
1102
  }
1078
1103
 
1079
1104
  return links.map(link => '- ' + link).join('\n');
@@ -1158,6 +1183,8 @@ To pass this insight, ensure your server supports and prioritizes a modern HTTP
1158
1183
  return `This insight identified legacy JavaScript in your application's modules that may be creating unnecessary code.
1159
1184
 
1160
1185
  Polyfills and transforms enable older browsers to use new JavaScript features. However, many are not necessary for modern browsers. Consider modifying your JavaScript build process to not transpile Baseline features, unless you know you must support older browsers.`;
1186
+ case 'CharacterSet':
1187
+ return `This insight checks that the page declares a character encoding, ideally via the Content-Type HTTP response header. A missing or late charset declaration can force the browser to re-parse the document once it finally determines the encoding, delaying first contentful paint. Best practice: include charset=utf-8 in the Content-Type header and add <meta charset="utf-8"> as the very first element inside <head>.`;
1161
1188
  }
1162
1189
  }
1163
1190
  }
@@ -467,6 +467,7 @@ Here are all the insights that contain some related request from the given range
467
467
  - DocumentLatency: news.yahoo.com/ (news.yahoo.com) (eventKey: s-2116, ts: 157423489126)
468
468
  - ThirdParties: cmp.js (consent.cmp.oath.com) (eventKey: s-3382, ts: 157423742399), gpt.js (securepubads.g.doubleclick.net) (eventKey: s-6244, ts: 157423760529), loader.js (cdn.taboola.com) (eventKey: s-6352, ts: 157423761978)
469
469
  - Cache: prebid-2.0.js (s.yimg.com) (eventKey: s-6279, ts: 157423760925), cmp.js (consent.cmp.oath.com) (eventKey: s-3382, ts: 157423742399), d1irmdsmbztlvx.js (s.yimg.com) (eventKey: s-6185, ts: 157423759200), consent.js (s.yimg.com) (eventKey: s-3384, ts: 157423742450), wnsrvbjmeprtfrnfx.js (s.yimg.com) (eventKey: s-6273, ts: 157423760794)
470
+ - CharacterSet: news.yahoo.com/ (news.yahoo.com) (eventKey: s-2116, ts: 157423489126)
470
471
  - LegacyJavaScript: benji-2.2.99.js (s.yimg.com) (eventKey: s-3387, ts: 157423742567), 25fa214.caas-news_web.min.js (s.yimg.com) (eventKey: s-3412, ts: 157423743431), wnsrvbjmeprtfrnfx.js (s.yimg.com) (eventKey: s-6273, ts: 157423760794), consent.js (s.yimg.com) (eventKey: s-3384, ts: 157423742450), news.yahoo.com/ (news.yahoo.com) (eventKey: s-2116, ts: 157423489126)
471
472
  === end content
472
473
 
@@ -510,6 +511,10 @@ Available insights:
510
511
  description: 3rd party code can significantly impact load performance. [Reduce and defer loading of 3rd party code](https://developer.chrome.com/docs/performance/insights/third-parties) to prioritize your page's content.
511
512
  relevant trace bounds: {min: 1020034871372, max: 1020035171789}
512
513
  example question: Which third parties are having the largest impact on my page performance?
514
+ - insight name: CharacterSet
515
+ description: A character encoding declaration is required. It can be done with a meta charset tag in the first 1024 bytes of the HTML or in the Content-Type HTTP response header. [Learn more about declaring the character encoding](https://developer.chrome.com/docs/insights/charset/).
516
+ relevant trace bounds: {min: 1020034836013, max: 1020034892491}
517
+ example question: How do I declare a character encoding for my page?
513
518
  === end content
514
519
 
515
520
  Title: PerformanceTraceFormatter formatTraceSummary yahoo-news.json.gz
@@ -617,6 +622,10 @@ Available insights:
617
622
  relevant trace bounds: {min: 171608170438, max: 171608877165}
618
623
  example question: Show me the most impactful render-blocking requests that I should focus on
619
624
  example question: How can I reduce the number of render-blocking requests?
625
+ - insight name: CharacterSet
626
+ description: A character encoding declaration is required. It can be done with a meta charset tag in the first 1024 bytes of the HTML or in the Content-Type HTTP response header. [Learn more about declaring the character encoding](https://developer.chrome.com/docs/insights/charset/).
627
+ relevant trace bounds: {min: 171607584346, max: 171608171143}
628
+ example question: How do I declare a character encoding for my page?
620
629
 
621
630
  ## insight set id: NAVIGATION_1
622
631
 
@@ -641,6 +650,10 @@ Available insights:
641
650
  relevant trace bounds: {min: 171614330544, max: 171615043224}
642
651
  example question: Show me the most impactful render-blocking requests that I should focus on
643
652
  example question: How can I reduce the number of render-blocking requests?
653
+ - insight name: CharacterSet
654
+ description: A character encoding declaration is required. It can be done with a meta charset tag in the first 1024 bytes of the HTML or in the Content-Type HTTP response header. [Learn more about declaring the character encoding](https://developer.chrome.com/docs/insights/charset/).
655
+ relevant trace bounds: {min: 171613750986, max: 171614330870}
656
+ example question: How do I declare a character encoding for my page?
644
657
  === end content
645
658
 
646
659
  Title: PerformanceTraceFormatter formatTraceSummary deals with CrUX manager errors
@@ -714,6 +727,10 @@ Available insights:
714
727
  description: 3rd party code can significantly impact load performance. [Reduce and defer loading of 3rd party code](https://developer.chrome.com/docs/performance/insights/third-parties) to prioritize your page's content.
715
728
  relevant trace bounds: {min: 59728701403, max: 59729465969}
716
729
  example question: Which third parties are having the largest impact on my page performance?
730
+ - insight name: CharacterSet
731
+ description: A character encoding declaration is required. It can be done with a meta charset tag in the first 1024 bytes of the HTML or in the Content-Type HTTP response header. [Learn more about declaring the character encoding](https://developer.chrome.com/docs/insights/charset/).
732
+ relevant trace bounds: {min: 59728651057, max: 59728790724}
733
+ example question: How do I declare a character encoding for my page?
717
734
  === end content
718
735
 
719
736
  Title: PerformanceTraceFormatter formatTraceSummary image-delivery.json.gz
@@ -794,6 +811,10 @@ Available insights:
794
811
  description: 3rd party code can significantly impact load performance. [Reduce and defer loading of 3rd party code](https://developer.chrome.com/docs/performance/insights/third-parties) to prioritize your page's content.
795
812
  relevant trace bounds: {min: 59728701403, max: 59729465969}
796
813
  example question: Which third parties are having the largest impact on my page performance?
814
+ - insight name: CharacterSet
815
+ description: A character encoding declaration is required. It can be done with a meta charset tag in the first 1024 bytes of the HTML or in the Content-Type HTTP response header. [Learn more about declaring the character encoding](https://developer.chrome.com/docs/insights/charset/).
816
+ relevant trace bounds: {min: 59728651057, max: 59728790724}
817
+ example question: How do I declare a character encoding for my page?
797
818
  === end content
798
819
 
799
820
  Title: PerformanceTraceFormatter formatTraceSummary includes INP insight when there is no navigation
@@ -11,6 +11,7 @@ export interface GreenDevSettings {
11
11
  aiAnnotations: Common.Settings.Setting<boolean>;
12
12
  copyToGemini: Common.Settings.Setting<boolean>;
13
13
  breakpointDebuggerAgent: Common.Settings.Setting<boolean>;
14
+ emulationCapabilities: Common.Settings.Setting<boolean>;
14
15
  }
15
16
 
16
17
  export class Prototypes {
@@ -48,7 +49,12 @@ export class Prototypes {
48
49
  false,
49
50
  Common.Settings.SettingStorageType.LOCAL,
50
51
  );
52
+ const emulationCapabilities = settings.createSetting(
53
+ 'greendev-emulation-capabilities-enabled',
54
+ false,
55
+ Common.Settings.SettingStorageType.LOCAL,
56
+ );
51
57
 
52
- return {aiAnnotations, copyToGemini, breakpointDebuggerAgent};
58
+ return {aiAnnotations, copyToGemini, breakpointDebuggerAgent, emulationCapabilities};
53
59
  }
54
60
  }
@@ -352,6 +352,7 @@ export class TraceProcessor extends EventTarget {
352
352
  SlowCSSSelector: null,
353
353
  ForcedReflow: null,
354
354
  Cache: null,
355
+ CharacterSet: null,
355
356
  ModernHTTP: null,
356
357
  LegacyJavaScript: null,
357
358
  };
@@ -40,11 +40,17 @@ let metricScoresByFrameId = new Map<FrameId, Map<AnyNavigationStart, Map<MetricN
40
40
  */
41
41
  let allMarkerEvents: Types.Events.PageLoadEvent[] = [];
42
42
 
43
+ // Grouped by navigation to make it easier for insights to scope checks.
44
+ let metaCharsetCheckEventsByNavigation = new Map<AnyNavigationStart, Types.Events.MetaCharsetCheck[]>();
45
+ let metaCharsetCheckEventsArray: Types.Events.MetaCharsetCheck[] = [];
46
+
43
47
  export function reset(): void {
44
48
  metricScoresByFrameId = new Map();
45
49
  pageLoadEventsArray = [];
46
50
  allMarkerEvents = [];
47
51
  selectedLCPCandidateEvents = new Set();
52
+ metaCharsetCheckEventsByNavigation = new Map();
53
+ metaCharsetCheckEventsArray = [];
48
54
  }
49
55
 
50
56
  let pageLoadEventsArray: Types.Events.PageLoadEvent[] = [];
@@ -60,6 +66,11 @@ let pageLoadEventsArray: Types.Events.PageLoadEvent[] = [];
60
66
  let selectedLCPCandidateEvents = new Set<Types.Events.AnyLargestContentfulPaintCandidate>();
61
67
 
62
68
  export function handleEvent(event: Types.Events.Event): void {
69
+ if (Types.Events.isMetaCharsetCheck(event)) {
70
+ metaCharsetCheckEventsArray.push(event);
71
+ return;
72
+ }
73
+
63
74
  if (!Types.Events.eventIsPageLoadEvent(event)) {
64
75
  return;
65
76
  }
@@ -393,6 +404,23 @@ export async function finalize(): Promise<void> {
393
404
  storePageLoadMetricAgainstNavigationId(navigation, pageLoadEvent);
394
405
  }
395
406
  }
407
+
408
+ const {navigationsByFrameId} = metaHandlerData();
409
+ metaCharsetCheckEventsArray.sort((a, b) => a.ts - b.ts);
410
+ for (const metaCharsetCheckEvent of metaCharsetCheckEventsArray) {
411
+ const frameId = metaCharsetCheckEvent.args.data?.frame;
412
+ if (!frameId) {
413
+ continue;
414
+ }
415
+ const navigation = Helpers.Trace.getNavigationForTraceEvent(metaCharsetCheckEvent, frameId, navigationsByFrameId);
416
+ if (!navigation) {
417
+ continue;
418
+ }
419
+ const eventsForNavigation =
420
+ Platform.MapUtilities.getWithDefault(metaCharsetCheckEventsByNavigation, navigation, () => []);
421
+ eventsForNavigation.push(metaCharsetCheckEvent);
422
+ }
423
+
396
424
  // NOTE: if you are looking for the TBT calculation, it has temporarily been
397
425
  // removed. See crbug.com/1424335 for details.
398
426
  const allFinalLCPEvents = gatherFinalLCPEvents();
@@ -421,12 +449,17 @@ export interface PageLoadMetricsData {
421
449
  * main frame.
422
450
  */
423
451
  allMarkerEvents: Types.Events.PageLoadEvent[];
452
+ /**
453
+ * MetaCharsetCheck events grouped by navigation.
454
+ */
455
+ metaCharsetCheckEventsByNavigation: Map<AnyNavigationStart, Types.Events.MetaCharsetCheck[]>;
424
456
  }
425
457
 
426
458
  export function data(): PageLoadMetricsData {
427
459
  return {
428
460
  metricScoresByFrameId,
429
461
  allMarkerEvents,
462
+ metaCharsetCheckEventsByNavigation,
430
463
  };
431
464
  }
432
465