chrome-devtools-frontend 1.0.1621064 → 1.0.1621678

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 (26) hide show
  1. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  2. package/front_end/generated/protocol.ts +1 -1
  3. package/front_end/models/ai_assistance/agents/AiAgent.ts +1 -1
  4. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +31 -20
  5. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +5 -1
  6. package/front_end/models/bindings/SymbolizedError.ts +18 -1
  7. package/front_end/models/issues_manager/GenericIssue.ts +50 -1
  8. package/front_end/models/issues_manager/descriptions/genericFormModelContextMissingToolDescription.md +5 -0
  9. package/front_end/models/issues_manager/descriptions/genericFormModelContextMissingToolName.md +5 -0
  10. package/front_end/models/issues_manager/descriptions/genericFormModelContextParameterMissingName.md +5 -0
  11. package/front_end/models/issues_manager/descriptions/genericFormModelContextParameterMissingTitleAndDescription.md +5 -0
  12. package/front_end/models/issues_manager/descriptions/genericFormModelContextRequiredParameterMissingName.md +5 -0
  13. package/front_end/models/javascript_metadata/NativeFunctions.js +8 -0
  14. package/front_end/models/trace/insights/Common.ts +4 -0
  15. package/front_end/models/trace/insights/types.ts +1 -1
  16. package/front_end/models/web_mcp/WebMCPModel.ts +11 -1
  17. package/front_end/panels/ai_assistance/components/ChatMessage.ts +31 -1
  18. package/front_end/panels/application/WebMCPView.ts +16 -7
  19. package/front_end/panels/elements/AdoptedStyleSheetTreeElement.ts +0 -1
  20. package/front_end/panels/elements/ElementsTreeElement.ts +53 -61
  21. package/front_end/panels/elements/PropertiesWidget.ts +1 -1
  22. package/front_end/third_party/chromium/README.chromium +1 -1
  23. package/front_end/ui/components/text_editor/TextEditor.ts +1 -3
  24. package/front_end/ui/legacy/Treeoutline.ts +5 -1
  25. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  26. package/package.json +1 -1
@@ -349,7 +349,7 @@ inspectorBackend.registerCommand("CrashReportContext.getEntries", [], ["entries"
349
349
  inspectorBackend.registerType("CrashReportContext.CrashReportContextEntry", [{"name": "key", "type": "string", "optional": false, "description": "", "typeRef": null}, {"name": "value", "type": "string", "optional": false, "description": "", "typeRef": null}, {"name": "frameId", "type": "string", "optional": false, "description": "The ID of the frame where the key-value pair was set.", "typeRef": "Page.FrameId"}]);
350
350
 
351
351
  // DOM.
352
- inspectorBackend.registerEnum("DOM.PseudoType", {FirstLine: "first-line", FirstLetter: "first-letter", Checkmark: "checkmark", Before: "before", After: "after", ExpandIcon: "expand-icon", PickerIcon: "picker-icon", InterestHint: "interest-hint", Marker: "marker", Backdrop: "backdrop", Column: "column", Selection: "selection", SearchText: "search-text", TargetText: "target-text", SpellingError: "spelling-error", GrammarError: "grammar-error", Highlight: "highlight", FirstLineInherited: "first-line-inherited", ScrollMarker: "scroll-marker", ScrollMarkerGroup: "scroll-marker-group", ScrollButton: "scroll-button", Scrollbar: "scrollbar", ScrollbarThumb: "scrollbar-thumb", ScrollbarButton: "scrollbar-button", ScrollbarTrack: "scrollbar-track", ScrollbarTrackPiece: "scrollbar-track-piece", ScrollbarCorner: "scrollbar-corner", Resizer: "resizer", InputListButton: "input-list-button", ViewTransition: "view-transition", ViewTransitionGroup: "view-transition-group", ViewTransitionImagePair: "view-transition-image-pair", ViewTransitionGroupChildren: "view-transition-group-children", ViewTransitionOld: "view-transition-old", ViewTransitionNew: "view-transition-new", Placeholder: "placeholder", FileSelectorButton: "file-selector-button", DetailsContent: "details-content", Picker: "picker", PermissionIcon: "permission-icon", OverscrollAreaParent: "overscroll-area-parent"});
352
+ inspectorBackend.registerEnum("DOM.PseudoType", {FirstLine: "first-line", FirstLetter: "first-letter", Checkmark: "checkmark", Before: "before", After: "after", ExpandIcon: "expand-icon", PickerIcon: "picker-icon", InterestButton: "interest-button", Marker: "marker", Backdrop: "backdrop", Column: "column", Selection: "selection", SearchText: "search-text", TargetText: "target-text", SpellingError: "spelling-error", GrammarError: "grammar-error", Highlight: "highlight", FirstLineInherited: "first-line-inherited", ScrollMarker: "scroll-marker", ScrollMarkerGroup: "scroll-marker-group", ScrollButton: "scroll-button", Scrollbar: "scrollbar", ScrollbarThumb: "scrollbar-thumb", ScrollbarButton: "scrollbar-button", ScrollbarTrack: "scrollbar-track", ScrollbarTrackPiece: "scrollbar-track-piece", ScrollbarCorner: "scrollbar-corner", Resizer: "resizer", InputListButton: "input-list-button", ViewTransition: "view-transition", ViewTransitionGroup: "view-transition-group", ViewTransitionImagePair: "view-transition-image-pair", ViewTransitionGroupChildren: "view-transition-group-children", ViewTransitionOld: "view-transition-old", ViewTransitionNew: "view-transition-new", Placeholder: "placeholder", FileSelectorButton: "file-selector-button", DetailsContent: "details-content", Picker: "picker", PermissionIcon: "permission-icon", OverscrollAreaParent: "overscroll-area-parent"});
353
353
  inspectorBackend.registerEnum("DOM.ShadowRootType", {UserAgent: "user-agent", Open: "open", Closed: "closed"});
354
354
  inspectorBackend.registerEnum("DOM.CompatibilityMode", {QuirksMode: "QuirksMode", LimitedQuirksMode: "LimitedQuirksMode", NoQuirksMode: "NoQuirksMode"});
355
355
  inspectorBackend.registerEnum("DOM.PhysicalAxes", {Horizontal: "Horizontal", Vertical: "Vertical", Both: "Both"});
@@ -4604,7 +4604,7 @@ export namespace DOM {
4604
4604
  After = 'after',
4605
4605
  ExpandIcon = 'expand-icon',
4606
4606
  PickerIcon = 'picker-icon',
4607
- InterestHint = 'interest-hint',
4607
+ InterestButton = 'interest-button',
4608
4608
  Marker = 'marker',
4609
4609
  Backdrop = 'backdrop',
4610
4610
  Column = 'column',
@@ -251,7 +251,7 @@ export interface PerformanceTraceAiWidget {
251
251
  export interface PerfInsightAiWidget {
252
252
  name: 'PERF_INSIGHT';
253
253
  data: {
254
- insight: 'lcp',
254
+ insight: Trace.Insights.Types.InsightKeys,
255
255
  insightData: Trace.Insights.Types.InsightModel,
256
256
  };
257
257
  }
@@ -222,6 +222,12 @@ enum ScorePriority {
222
222
  DEFAULT = 1,
223
223
  }
224
224
 
225
+ // TODO(crbug.com/503296282): Remove this when we add support for all insights
226
+ const SUPPORTED_INSIGHT_WIDGETS = new Set<Trace.Insights.Types.InsightKeys>([
227
+ Trace.Insights.Types.InsightKeys.LCP_BREAKDOWN,
228
+ Trace.Insights.Types.InsightKeys.RENDER_BLOCKING,
229
+ ]);
230
+
225
231
  export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
226
232
  static fromParsedTrace(parsedTrace: Trace.TraceModel.ParsedTrace): PerformanceTraceContext {
227
233
  return new PerformanceTraceContext(AgentFocus.fromParsedTrace(parsedTrace));
@@ -334,8 +340,8 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
334
340
  const failingInsightSuggestions =
335
341
  Object.values(insightSet.model)
336
342
  .filter(model => {
337
- return model.state !== 'pass' &&
338
- !poorMetrics.has(model.insightKey as Trace.Insights.Types.InsightKeys);
343
+ return model.state !== 'pass' && Trace.Insights.Common.isInsightKey(model.insightKey) &&
344
+ !poorMetrics.has(model.insightKey);
339
345
  })
340
346
  .map(model => new PerformanceInsightFormatter(focus, model).getSuggestions().at(-1))
341
347
  .filter((suggestion): suggestion is ConversationSuggestion => !!suggestion)
@@ -536,15 +542,18 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
536
542
  return widgets;
537
543
  }
538
544
 
539
- // Case 2: LCP Insight -> LCP breakdown & CWV widgets
540
- if (focus.insight && Trace.Insights.Models.LCPBreakdown.isLCPBreakdownInsight(focus.insight)) {
541
- widgets.push({
542
- name: 'PERF_INSIGHT',
543
- data: {
544
- insight: 'lcp',
545
- insightData: focus.insight,
546
- },
547
- });
545
+ // Case 2: Insight -> PERF_INSIGHT widget
546
+ if (focus.insight) {
547
+ const insightKey = focus.insight.insightKey;
548
+ if (Trace.Insights.Common.isInsightKey(insightKey) && SUPPORTED_INSIGHT_WIDGETS.has(insightKey)) {
549
+ widgets.push({
550
+ name: 'PERF_INSIGHT',
551
+ data: {
552
+ insight: insightKey,
553
+ insightData: focus.insight,
554
+ },
555
+ });
556
+ }
548
557
  }
549
558
 
550
559
  // Case 3: Whole Trace or insight other than LCP -> CWV widget
@@ -1006,15 +1015,17 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
1006
1015
  }
1007
1016
  }
1008
1017
  }
1009
- if (params.insightName === 'LCPBreakdown') {
1010
- widgets.push({
1011
- name: 'PERF_INSIGHT',
1012
- data: {
1013
- insight: 'lcp',
1014
- insightData: insight as Trace.Insights.Types.InsightModel,
1015
- },
1016
- });
1017
- }
1018
+ }
1019
+
1020
+ const insightKey = params.insightName;
1021
+ if (Trace.Insights.Common.isInsightKey(insightKey) && SUPPORTED_INSIGHT_WIDGETS.has(insightKey)) {
1022
+ widgets.push({
1023
+ name: 'PERF_INSIGHT',
1024
+ data: {
1025
+ insight: insightKey,
1026
+ insightData: insight as Trace.Insights.Types.InsightModel,
1027
+ },
1028
+ });
1018
1029
  }
1019
1030
 
1020
1031
  const key = `getInsightDetails('${params.insightSetId}', '${params.insightName}')`;
@@ -21,10 +21,11 @@ import {NetworkProject} from './NetworkProject.js';
21
21
  import type {ResourceMapping} from './ResourceMapping.js';
22
22
  import {type ResourceScriptFile, ResourceScriptMapping} from './ResourceScriptMapping.js';
23
23
  import {
24
+ isErrorLike,
24
25
  type SymbolizedError,
25
26
  SymbolizedErrorObject,
26
27
  SymbolizedSyntaxError,
27
- UnparsableError
28
+ UnparsableError,
28
29
  } from './SymbolizedError.js';
29
30
 
30
31
  export class DebuggerWorkspaceBinding implements SDK.TargetManager.SDKModelObserver<SDK.DebuggerModel.DebuggerModel> {
@@ -247,6 +248,9 @@ export class DebuggerWorkspaceBinding implements SDK.TargetManager.SDKModelObser
247
248
  }
248
249
  } else if (remoteObject.type === 'string') {
249
250
  errorStack = remoteObject.description || '';
251
+ if (!isErrorLike(errorStack)) {
252
+ return null;
253
+ }
250
254
  } else {
251
255
  return null;
252
256
  }
@@ -11,6 +11,10 @@ import type * as Workspace from '../workspace/workspace.js';
11
11
  import type {DebuggerWorkspaceBinding} from './DebuggerWorkspaceBinding.js';
12
12
  import {type LiveLocation, LiveLocationPool} from './LiveLocation.js';
13
13
 
14
+ export function isErrorLike(stack: string): boolean {
15
+ return /\n\s*at\s/.test(stack) || stack.startsWith('SyntaxError:');
16
+ }
17
+
14
18
  export type SymbolizedError = SymbolizedErrorObject|SymbolizedSyntaxError|UnparsableError;
15
19
 
16
20
  export class UnparsableError extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
@@ -21,6 +25,19 @@ export class UnparsableError extends Common.ObjectWrapper.ObjectWrapper<EventTyp
21
25
  super();
22
26
  this.errorStack = errorStack;
23
27
  this.cause = cause;
28
+
29
+ this.cause?.addEventListener(Events.UPDATED, this.#fireUpdated, this);
30
+ }
31
+
32
+ dispose(): void {
33
+ this.cause?.removeEventListener(Events.UPDATED, this.#fireUpdated, this);
34
+ if (this.cause instanceof SymbolizedErrorObject || this.cause instanceof UnparsableError) {
35
+ this.cause.dispose();
36
+ }
37
+ }
38
+
39
+ #fireUpdated(): void {
40
+ this.dispatchEventToListeners(Events.UPDATED);
24
41
  }
25
42
  }
26
43
 
@@ -42,7 +59,7 @@ export class SymbolizedErrorObject extends Common.ObjectWrapper.ObjectWrapper<Ev
42
59
  dispose(): void {
43
60
  this.stackTrace.removeEventListener(StackTrace.StackTrace.Events.UPDATED, this.#fireUpdated, this);
44
61
  this.cause?.removeEventListener(Events.UPDATED, this.#fireUpdated, this);
45
- if (this.cause instanceof SymbolizedErrorObject) {
62
+ if (this.cause instanceof SymbolizedErrorObject || this.cause instanceof UnparsableError) {
46
63
  this.cause.dispose();
47
64
  }
48
65
  }
@@ -193,6 +193,31 @@ export const genericNavigationEntryMarkedSkippable = {
193
193
  }],
194
194
  };
195
195
 
196
+ export const genericFormModelContextMissingToolName = {
197
+ file: 'genericFormModelContextMissingToolName.md',
198
+ links: [],
199
+ };
200
+
201
+ export const genericFormModelContextMissingToolDescription = {
202
+ file: 'genericFormModelContextMissingToolDescription.md',
203
+ links: [],
204
+ };
205
+
206
+ export const genericFormModelContextParameterMissingTitleAndDescription = {
207
+ file: 'genericFormModelContextParameterMissingTitleAndDescription.md',
208
+ links: [],
209
+ };
210
+
211
+ export const genericFormModelContextRequiredParameterMissingName = {
212
+ file: 'genericFormModelContextRequiredParameterMissingName.md',
213
+ links: [],
214
+ };
215
+
216
+ export const genericFormModelContextParameterMissingName = {
217
+ file: 'genericFormModelContextParameterMissingName.md',
218
+ links: [],
219
+ };
220
+
196
221
  const issueDescriptions = new Map<Protocol.Audits.GenericIssueErrorType, LazyMarkdownIssueDescription>([
197
222
  [Protocol.Audits.GenericIssueErrorType.FormLabelForNameError, genericFormLabelForNameError],
198
223
  [Protocol.Audits.GenericIssueErrorType.FormInputWithNoLabelError, genericFormInputWithNoLabelError],
@@ -233,6 +258,26 @@ const issueDescriptions = new Map<Protocol.Audits.GenericIssueErrorType, LazyMar
233
258
  Protocol.Audits.GenericIssueErrorType.NavigationEntryMarkedSkippable,
234
259
  genericNavigationEntryMarkedSkippable,
235
260
  ],
261
+ [
262
+ Protocol.Audits.GenericIssueErrorType.FormModelContextMissingToolName,
263
+ genericFormModelContextMissingToolName,
264
+ ],
265
+ [
266
+ Protocol.Audits.GenericIssueErrorType.FormModelContextMissingToolDescription,
267
+ genericFormModelContextMissingToolDescription,
268
+ ],
269
+ [
270
+ Protocol.Audits.GenericIssueErrorType.FormModelContextParameterMissingTitleAndDescription,
271
+ genericFormModelContextParameterMissingTitleAndDescription,
272
+ ],
273
+ [
274
+ Protocol.Audits.GenericIssueErrorType.FormModelContextRequiredParameterMissingName,
275
+ genericFormModelContextRequiredParameterMissingName,
276
+ ],
277
+ [
278
+ Protocol.Audits.GenericIssueErrorType.FormModelContextParameterMissingName,
279
+ genericFormModelContextParameterMissingName,
280
+ ],
236
281
  ]);
237
282
 
238
283
  const issueTypes = new Map<Protocol.Audits.GenericIssueErrorType, IssueKind>([
@@ -249,5 +294,9 @@ const issueTypes = new Map<Protocol.Audits.GenericIssueErrorType, IssueKind>([
249
294
  [Protocol.Audits.GenericIssueErrorType.FormLabelForMatchesNonExistingIdError, IssueKind.PAGE_ERROR],
250
295
  [Protocol.Audits.GenericIssueErrorType.FormLabelHasNeitherForNorNestedInputError, IssueKind.IMPROVEMENT],
251
296
  [Protocol.Audits.GenericIssueErrorType.FormInputHasWrongButWellIntendedAutocompleteValueError, IssueKind.IMPROVEMENT],
252
-
297
+ [Protocol.Audits.GenericIssueErrorType.FormModelContextMissingToolName, IssueKind.PAGE_ERROR],
298
+ [Protocol.Audits.GenericIssueErrorType.FormModelContextMissingToolDescription, IssueKind.PAGE_ERROR],
299
+ [Protocol.Audits.GenericIssueErrorType.FormModelContextParameterMissingTitleAndDescription, IssueKind.PAGE_ERROR],
300
+ [Protocol.Audits.GenericIssueErrorType.FormModelContextRequiredParameterMissingName, IssueKind.PAGE_ERROR],
301
+ [Protocol.Audits.GenericIssueErrorType.FormModelContextParameterMissingName, IssueKind.PAGE_ERROR],
253
302
  ]);
@@ -0,0 +1,5 @@
1
+ # Missing tool description for a WebMCP declarative tool definition
2
+
3
+ This form is intended to be used as a WebMCP declarative tool definition, but it lacks a description explaining what the tool does. Descriptions are critical for AI models to decide when to use a given tool.
4
+
5
+ To fix this issue, provide a tool description via the `tooldescription` attribute.
@@ -0,0 +1,5 @@
1
+ # Missing tool name for a WebMCP declarative tool definition
2
+
3
+ This form is intended to be used as a WebMCP declarative tool definition, but it does not specify a name. A tool name is required for an AI model to correctly identify and invoke the tool.
4
+
5
+ To fix this issue, provide a tool name via the `toolname` attribute.
@@ -0,0 +1,5 @@
1
+ # Parameter missing name for a WebMCP declarative tool definition
2
+
3
+ An input element within this form lacks a `name` attribute. Without a name, the AI model cannot format the parameter payload correctly when invoking the tool.
4
+
5
+ To fix this issue, ensure form parameters have a `name` attribute.
@@ -0,0 +1,5 @@
1
+ # Missing parameter title and description for a WebMCP declarative tool definition
2
+
3
+ A parameter within this form (such as an `<input>` field) is missing both a title and a description. An AI model needs this metadata to understand what information should be passed to this parameter when invoking the tool.
4
+
5
+ To fix this issue, add a title and description to the form parameter.
@@ -0,0 +1,5 @@
1
+ # Required parameter missing name for a WebMCP declarative tool definition
2
+
3
+ A required input element within this form lacks a `name` attribute. Without a name, the AI model cannot format the parameter payload correctly when invoking the tool.
4
+
5
+ To fix this issue, ensure all required form parameters have a `name` attribute.
@@ -7639,6 +7639,14 @@ export const NativeFunctions = [
7639
7639
  name: "Sanitizer",
7640
7640
  signatures: [["?configuration"]]
7641
7641
  },
7642
+ {
7643
+ name: "allowProcessingInstruction",
7644
+ signatures: [["pi"]]
7645
+ },
7646
+ {
7647
+ name: "removeProcessingInstruction",
7648
+ signatures: [["pi"]]
7649
+ },
7642
7650
  {
7643
7651
  name: "TaskController",
7644
7652
  signatures: [["?init"]]
@@ -26,6 +26,10 @@ export function getInsight<InsightName extends keyof InsightModels>(
26
26
  return insightSet.model[insightName];
27
27
  }
28
28
 
29
+ export function isInsightKey(key: string): key is InsightKeys {
30
+ return Object.values(InsightKeys).includes(key as InsightKeys);
31
+ }
32
+
29
33
  export function getLCP(insightSet: InsightSet):
30
34
  {value: Types.Timing.Micro, event: Types.Events.AnyLargestContentfulPaintCandidate}|null {
31
35
  const insight = getInsight(InsightKeys.LCP_BREAKDOWN, insightSet);
@@ -145,7 +145,7 @@ export type InsightModelErrors = {
145
145
  */
146
146
  export type TraceInsightSets = Map<Types.Events.NavigationId, InsightSet>;
147
147
 
148
- export const enum InsightKeys {
148
+ export enum InsightKeys {
149
149
  LCP_BREAKDOWN = 'LCPBreakdown',
150
150
  INP_BREAKDOWN = 'INPBreakdown',
151
151
  CLS_CULPRITS = 'CLSCulprits',
@@ -90,6 +90,7 @@ export interface Call {
90
90
  tool: Tool;
91
91
  input: string;
92
92
  result?: Result;
93
+ cancel: () => void;
93
94
  }
94
95
 
95
96
  export class Tool {
@@ -244,7 +245,16 @@ export class WebMCPModel extends SDK.SDKModel.SDKModel<EventTypes> implements Pr
244
245
  if (!tool) {
245
246
  return;
246
247
  }
247
- const call: Call = {tool, input: params.input, invocationId: params.invocationId};
248
+ const call: Call = {
249
+ tool,
250
+ input: params.input,
251
+ invocationId: params.invocationId,
252
+ cancel: () => {
253
+ if (call.result === undefined) {
254
+ void this.agent.invoke_cancelInvocation({invocationId: params.invocationId});
255
+ }
256
+ },
257
+ };
248
258
  this.#calls.set(params.invocationId, call);
249
259
  this.dispatchEventToListeners(Events.TOOL_INVOKED, call);
250
260
  }
@@ -201,6 +201,10 @@ const UIStringsNotTranslate = {
201
201
  * @description Accessible label for the reveal button in the LCP breakdown widget.
202
202
  */
203
203
  revealLcpBreakdown: 'Reveal LCP breakdown',
204
+ /**
205
+ * @description Accessible label for the reveal button in the render-blocking requests widget.
206
+ */
207
+ revealRenderBlockingBreakdown: 'Reveal render-blocking requests',
204
208
  /**
205
209
  * @description Accessible label for the reveal button in the LCP element widget.
206
210
  */
@@ -221,6 +225,10 @@ const UIStringsNotTranslate = {
221
225
  * @description Title for the LCP breakdown widget.
222
226
  */
223
227
  lcpBreakdown: 'LCP breakdown',
228
+ /**
229
+ * @description Title for the render-blocking requests widget.
230
+ */
231
+ renderBlockingBreakdown: 'Render-blocking requests',
224
232
  /**
225
233
  * @description Title for the LCP element widget.
226
234
  */
@@ -916,7 +924,7 @@ async function makeStylePropertiesWidget(widgetData: StylePropertiesAiWidget): P
916
924
 
917
925
  async function makePerfInsightWidget(widgetData: PerfInsightAiWidget): Promise<WidgetMakerResponse|null> {
918
926
  switch (widgetData.data.insight) {
919
- case 'lcp': {
927
+ case Trace.Insights.Types.InsightKeys.LCP_BREAKDOWN: {
920
928
  const insight = widgetData.data.insightData;
921
929
  if (!insight || !Trace.Insights.Models.LCPBreakdown.isLCPBreakdownInsight(insight)) {
922
930
  return null;
@@ -938,6 +946,28 @@ async function makePerfInsightWidget(widgetData: PerfInsightAiWidget): Promise<W
938
946
  jslogContext: 'lcp-breakdown',
939
947
  };
940
948
  }
949
+ case Trace.Insights.Types.InsightKeys.RENDER_BLOCKING: {
950
+ const insight = widgetData.data.insightData;
951
+ if (!insight || !Trace.Insights.Models.RenderBlocking.isRenderBlockingInsight(insight)) {
952
+ return null;
953
+ }
954
+ // clang-format off
955
+ const renderedWidget = html`<devtools-widget
956
+ class="render-blocking-widget"
957
+ ${widget(TimelineInsights.RenderBlocking.RenderBlocking, {
958
+ model: insight,
959
+ minimal: true,
960
+ })}></devtools-widget>`;
961
+ // clang-format on
962
+
963
+ return {
964
+ renderedWidget,
965
+ revealable: new TimelineUtils.Helpers.RevealableInsight(insight),
966
+ accessibleRevealLabel: lockedString(UIStringsNotTranslate.revealRenderBlockingBreakdown),
967
+ title: lockedString(UIStringsNotTranslate.renderBlockingBreakdown),
968
+ jslogContext: 'render-blocking-widget',
969
+ };
970
+ }
941
971
  default:
942
972
  return null;
943
973
  }
@@ -167,6 +167,10 @@ const UIStrings = {
167
167
  * @description Context menu action to copy the description of a tool
168
168
  */
169
169
  copyDescription: 'Copy description',
170
+ /**
171
+ * @description Context menu action to cancel an in-progress tool call
172
+ */
173
+ cancelCall: 'Cancel',
170
174
  /**
171
175
  * @description Text for the header of the tool run section
172
176
  */
@@ -498,6 +502,11 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
498
502
  const payload = parsePayload(call.input);
499
503
  input.onRevealTool(call.tool, payload.valueObject as Record<string, unknown> | undefined);
500
504
  }, {jslogContext: 'webmcp.edit-and-run', disabled: isUnregistered});
505
+ if (call.result === undefined) {
506
+ contextMenu.defaultSection().appendItem(i18nString(UIStrings.cancelCall), () => {
507
+ call.cancel();
508
+ }, {jslogContext: 'webmcp.cancel-call'});
509
+ }
501
510
  }}>
502
511
  <td>
503
512
  <div class="name-cell">
@@ -598,11 +607,11 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
598
607
  ` : html`
599
608
  <devtools-list class="square-corners">
600
609
  ${tools.map(tool => html`
601
- <div class=${Directives.classMap({'tool-item': true, selected: tool === input.selectedTool?.tool})}
602
- @click=${() => input.onToolSelect(tool)}
603
- @contextmenu=${(e: Event) => onToolContextMenu(e, tool)}>
604
- <div class="tool-name-container">
605
- <div class="tool-name source-code">${tool.name}</div>
610
+ <div class=${Directives.classMap({'tool-item': true, selected: tool === input.selectedTool?.tool})}
611
+ @click=${() => input.onToolSelect(tool)}
612
+ @contextmenu=${(e: Event) => onToolContextMenu(e, tool)}>
613
+ <div class="tool-name-container">
614
+ <div class="tool-name source-code">${tool.name}</div>
606
615
  <div class="tool-icons">
607
616
  ${getIconGroupsFromStats(toolStats.stats.get(tool)).map(group => html`
608
617
  <icon-button
@@ -613,8 +622,8 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
613
622
  } as IconButton.IconButton.IconButtonData}
614
623
  @click=${(e: Event) => e.stopPropagation()}></icon-button>`)}
615
624
  </div>
616
- </div>
617
- <div class="tool-description">${tool.description}</div>
625
+ </div>
626
+ <div class="tool-description">${tool.description}</div>
618
627
  </div>`)}
619
628
  </devtools-list>
620
629
  `}
@@ -154,7 +154,6 @@ export class AdoptedStyleSheetContentsTreeElement extends UI.TreeOutline.TreeEle
154
154
  this.editing = {
155
155
  commit: editorHandles.commit,
156
156
  cancel: editorHandles.cancel,
157
- editor: undefined,
158
157
  resize: () => {},
159
158
  };
160
159
 
@@ -460,10 +460,14 @@ export interface ViewInput {
460
460
  descendantDecorations: Decoration[];
461
461
  decorationsTooltip: string;
462
462
  indent: number;
463
+
464
+ editorState: CodeMirror.EditorState|null;
465
+ editorWidth: number|null;
463
466
  }
464
467
 
465
468
  export interface ViewOutput {
466
469
  contentElement?: HTMLElement;
470
+ editorRef?: TextEditor.TextEditor.TextEditor;
467
471
  }
468
472
 
469
473
  export function adornerRef(): DirectiveResult<typeof Lit.Directives.RefDirective> {
@@ -936,11 +940,12 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
936
940
  const gutterContainerClasses = {
937
941
  'has-decorations': input.decorations.length || input.descendantDecorations.length,
938
942
  'gutter-container': true,
943
+ hidden: Boolean(input.editorState),
939
944
  };
940
945
  // clang-format off
941
946
  render(html`
942
947
  <div ${ref(el => { output.contentElement = el as HTMLElement; })}>
943
- ${input.node ? html`<span class="highlight">${renderTitle(
948
+ ${input.node ? html`<span class="highlight ${input.editorState ? 'hidden' : ''}">${renderTitle(
944
949
  input.node,
945
950
  input.isClosingTag,
946
951
  input.expanded,
@@ -951,7 +956,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
951
956
  input.onExpand,
952
957
  )}</span>` : nothing}
953
958
  ${input.isHovered || input.isSelected ? html`
954
- <div class="selection fill" style=${`margin-left: ${-input.indent}px`}></div>
959
+ <div class="selection fill ${input.editorState ? 'hidden' : ''}" style=${`margin-left: ${-input.indent}px`}></div>
955
960
  ` : nothing}
956
961
  <div class=${Lit.Directives.classMap(gutterContainerClasses)}
957
962
  style="left: ${-input.indent}px"
@@ -964,7 +969,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
964
969
  ${input.descendantDecorations.map(d => html`<div class="elements-gutter-decoration elements-has-decorated-children" style="--decoration-color: ${d.color}"></div>`)}
965
970
  </div>` : nothing}
966
971
  </div>
967
- ${hasAdorners ? html`<div class="adorner-container ${!hasAdorners ? 'hidden' : ''}">
972
+ ${hasAdorners ? html`<div class="adorner-container ${(input.editorState) ? 'hidden' : ''}">
968
973
  ${maybeRenderAdAdorner(input)}
969
974
  ${input.showViewSourceAdorner ? html`<devtools-adorner
970
975
  .name=${ElementsComponents.AdornerManager.RegisteredAdorners.VIEW_SOURCE}
@@ -1123,10 +1128,10 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
1123
1128
  </devtools-adorner>` : nothing}
1124
1129
  </div>`: nothing}
1125
1130
  ${input.isSelected ? html`
1126
- <span class="selected-hint" title=${i18nString(UIStrings.useSInTheConsoleToReferToThis, { PH1: '$0' })} aria-hidden="true"></span>
1131
+ <span class="selected-hint ${input.editorState ? 'hidden' : ''}" title=${i18nString(UIStrings.useSInTheConsoleToReferToThis, { PH1: '$0' })} aria-hidden="true"></span>
1127
1132
  ` : nothing}
1128
1133
  ${input.showAiButton ? html`
1129
- <span class="ai-button-container">
1134
+ <span class="ai-button-container ${input.editorState ? 'hidden' : ''}">
1130
1135
  <devtools-floating-button
1131
1136
  icon-name=${AIAssistance.AiUtils.getIconName()}
1132
1137
  title=${input.aiButtonTitle || ''}
@@ -1136,6 +1141,15 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
1136
1141
  </devtools-floating-button>
1137
1142
  </span>
1138
1143
  ` : nothing}
1144
+ ${input.editorState ? html`<div @keydown=${(event: KeyboardEvent) => {
1145
+ if (event.key === 'Escape') {
1146
+ event.consume(true);
1147
+ }
1148
+ }} class="source-code elements-tree-editor" style="width: ${input.editorWidth ?? 0}px;">
1149
+ <devtools-text-editor .state=${input.editorState} ${ref(el => {
1150
+ output.editorRef = el as TextEditor.TextEditor.TextEditor;
1151
+ })}></devtools-text-editor>
1152
+ </div>`: nothing}
1139
1153
  </div>
1140
1154
  `, target);
1141
1155
  // clang-format on
@@ -1151,7 +1165,9 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1151
1165
  private inClipboard: boolean;
1152
1166
  #hovered: boolean;
1153
1167
  private editing: EditorHandles|null;
1154
- private htmlEditElement?: HTMLElement;
1168
+ #editorRef?: TextEditor.TextEditor.TextEditor;
1169
+ #editorState: CodeMirror.EditorState|null = null;
1170
+ #editorWidth: number|null = null;
1155
1171
  expandAllButtonElement: UI.TreeOutline.TreeElement|null;
1156
1172
  #elementIssues = new Map<string, IssuesManager.Issue.Issue>();
1157
1173
  #nodeElementToIssue = new Map<Element, IssuesManager.Issue.Issue[]>();
@@ -1288,9 +1304,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1288
1304
 
1289
1305
  // ClearNode param is used to clean DOM after in-place editing..
1290
1306
  performUpdate(clearNode = false): void {
1291
- if (this.editing) {
1292
- return;
1293
- }
1294
1307
  const output: ViewOutput = {};
1295
1308
  DEFAULT_VIEW(
1296
1309
  {
@@ -1380,10 +1393,13 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1380
1393
  void action.execute();
1381
1394
  }
1382
1395
  },
1396
+ editorState: this.#editorState,
1397
+ editorWidth: this.#editorWidth,
1383
1398
  },
1384
1399
  output, this.listItemElement);
1385
1400
 
1386
1401
  this.#contentElement = output.contentElement;
1402
+ this.#editorRef = output.editorRef;
1387
1403
  if (this.#updateRecord) {
1388
1404
  this.#updateRecord = null;
1389
1405
  }
@@ -1683,10 +1699,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1683
1699
  }
1684
1700
  }
1685
1701
 
1686
- override onunbind(): void {
1687
- if (this.editing) {
1688
- this.editing.cancel();
1689
- }
1702
+ clearView(): void {
1690
1703
  // Update the element to clean up adorner registrations with the
1691
1704
  // ElementsPanel.
1692
1705
  // We do not change the ElementsTreeElement state in case the
@@ -1744,9 +1757,17 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1744
1757
  descendantDecorations: [],
1745
1758
  decorationsTooltip: '',
1746
1759
  indent: 0,
1760
+ editorState: null,
1761
+ editorWidth: null,
1747
1762
  },
1748
1763
  {}, this.listItemElement);
1764
+ }
1749
1765
 
1766
+ override onunbind(): void {
1767
+ if (this.editing) {
1768
+ this.editing.cancel();
1769
+ }
1770
+ this.clearView();
1750
1771
  if (this.treeOutline && this.treeOutline.treeElementByNode.get(this.nodeInternal) === this) {
1751
1772
  this.treeOutline.treeElementByNode.delete(this.nodeInternal);
1752
1773
  }
@@ -2553,7 +2574,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2553
2574
  this.editing = {
2554
2575
  commit: editorHandles.commit,
2555
2576
  cancel: editorHandles.cancel,
2556
- editor: undefined,
2557
2577
  resize: () => {},
2558
2578
  };
2559
2579
  }
@@ -2569,31 +2589,13 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2569
2589
  return;
2570
2590
  }
2571
2591
 
2572
- const initialValue = convertUnicodeCharsToHTMLEntities(maybeInitialValue).text;
2573
- this.htmlEditElement = document.createElement('div');
2574
- this.htmlEditElement.className = 'source-code elements-tree-editor';
2575
-
2576
- // Hide header items.
2577
- let child: (ChildNode|null) = this.listItemElement.firstChild;
2578
- while (child) {
2579
- if (child instanceof HTMLElement) {
2580
- child.style.display = 'none';
2581
- }
2582
- child = child.nextSibling;
2583
- }
2584
2592
  // Hide children item.
2585
2593
  if (this.childrenListElement) {
2586
2594
  this.childrenListElement.style.display = 'none';
2587
2595
  }
2596
+ const initialValue = convertUnicodeCharsToHTMLEntities(maybeInitialValue).text;
2588
2597
  // Append editor.
2589
- this.listItemElement.append(this.htmlEditElement);
2590
- this.htmlEditElement.addEventListener('keydown', event => {
2591
- if (event.key === 'Escape') {
2592
- event.consume(true);
2593
- }
2594
- });
2595
-
2596
- const editor = new TextEditor.TextEditor.TextEditor(CodeMirror.EditorState.create({
2598
+ this.#editorState = CodeMirror.EditorState.create({
2597
2599
  doc: initialValue,
2598
2600
  extensions: [
2599
2601
  CodeMirror.keymap.of([
@@ -2623,58 +2625,49 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2623
2625
  }),
2624
2626
  CodeMirror.EditorView.domEventHandlers({
2625
2627
  focusout: event => {
2628
+ if (!this.#editorRef) {
2629
+ return;
2630
+ }
2626
2631
  // The relatedTarget is null when no element gains focus, e.g. switching windows.
2627
2632
  const relatedTarget = (event.relatedTarget as Node | null);
2628
- if (relatedTarget && !relatedTarget.isSelfOrDescendant(editor)) {
2633
+ if (relatedTarget && !relatedTarget.isSelfOrDescendant(this.#editorRef)) {
2629
2634
  this.editing?.commit();
2630
2635
  }
2631
2636
  },
2632
2637
  }),
2633
2638
  ],
2634
- }));
2635
- this.editing = {commit: commit.bind(this), cancel: dispose.bind(this), editor, resize: resize.bind(this)};
2639
+ });
2640
+ this.performUpdate();
2636
2641
  resize.call(this);
2637
- this.htmlEditElement.appendChild(editor);
2638
- editor.editor.focus();
2639
-
2642
+ this.#editorRef?.focus();
2643
+ this.editing = {commit: commit.bind(this), cancel: dispose.bind(this), resize: resize.bind(this)};
2640
2644
  this.treeOutline?.setMultilineEditing(this.editing);
2641
2645
 
2642
2646
  function resize(this: ElementsTreeElement): void {
2643
- if (this.treeOutline && this.htmlEditElement) {
2644
- this.htmlEditElement.style.width = this.treeOutline.visibleWidth() - this.computeLeftIndent() - 30 + 'px';
2647
+ if (this.treeOutline) {
2648
+ this.#editorWidth = this.treeOutline.visibleWidth() - this.computeLeftIndent() - 30;
2649
+ this.performUpdate();
2645
2650
  }
2646
2651
  }
2647
2652
 
2648
2653
  function commit(this: ElementsTreeElement): void {
2649
- if (this.editing?.editor) {
2650
- commitCallback(initialValue, this.editing.editor.state.doc.toString());
2654
+ if (this.#editorRef) {
2655
+ commitCallback(initialValue, this.#editorRef.editor.state.doc.toString());
2651
2656
  }
2652
2657
  dispose.call(this);
2653
2658
  }
2654
2659
 
2655
2660
  function dispose(this: ElementsTreeElement): void {
2656
- if (!this.editing?.editor) {
2661
+ if (!this.#editorRef) {
2657
2662
  return;
2658
2663
  }
2659
2664
  this.editing = null;
2660
-
2661
- // Remove editor.
2662
- if (this.htmlEditElement) {
2663
- this.listItemElement.removeChild(this.htmlEditElement);
2664
- }
2665
- this.htmlEditElement = undefined;
2665
+ this.#editorState = null;
2666
+ this.performUpdate();
2666
2667
  // Unhide children item.
2667
2668
  if (this.childrenListElement) {
2668
2669
  this.childrenListElement.style.removeProperty('display');
2669
2670
  }
2670
- // Unhide header items.
2671
- let child: (ChildNode|null) = this.listItemElement.firstChild;
2672
- while (child) {
2673
- if (child instanceof HTMLElement) {
2674
- child.style.removeProperty('display');
2675
- }
2676
- child = child.nextSibling;
2677
- }
2678
2671
 
2679
2672
  if (this.treeOutline) {
2680
2673
  this.treeOutline.setMultilineEditing(null);
@@ -3016,7 +3009,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
3016
3009
  }
3017
3010
 
3018
3011
  toggleEditAsHTML(callback?: ((arg0: boolean) => void), startEditing?: boolean): void {
3019
- if (this.editing && this.htmlEditElement) {
3012
+ if (this.editing && this.#editorState) {
3020
3013
  this.editing.commit();
3021
3014
  return;
3022
3015
  }
@@ -3222,7 +3215,6 @@ export function convertUnicodeCharsToHTMLEntities(text: string): {
3222
3215
  export interface EditorHandles {
3223
3216
  commit: () => void;
3224
3217
  cancel: () => void;
3225
- editor?: TextEditor.TextEditor.TextEditor;
3226
3218
  resize: () => void;
3227
3219
  }
3228
3220
 
@@ -103,7 +103,7 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
103
103
  ${input.objectTree && input.allChildrenFiltered ? html`
104
104
  <div class="gray-info-message">${i18nString(UIStrings.noMatchingProperty)}</div>
105
105
  ` : nothing}
106
- <devtools-tree @treeelementexpand=${onExpand} .template=${html`
106
+ <devtools-tree show-selection-on-keyboard-focus @treeelementexpand=${onExpand} .template=${html`
107
107
  <ul role=tree class="source-code object-properties-section">
108
108
  <style>${ObjectUI.ObjectPropertiesSection.objectValueStyles}</style>;
109
109
  <style>${ObjectUI.ObjectPropertiesSection.objectPropertiesSectionStyles}</style>;
@@ -1,7 +1,7 @@
1
1
  Name: Dependencies sourced from the upstream `chromium` repository
2
2
  URL: Internal
3
3
  Version: N/A
4
- Revision: 3e3e498dbe4d68709d4f6b2e2a834a7a85a17a83
4
+ Revision: b1e55a8961dbd7de8febb16a1bd10f31de63c4c6
5
5
  Update Mechanism: Manual (https://crbug.com/428069060)
6
6
  License: BSD-3-Clause
7
7
  License File: LICENSE
@@ -137,9 +137,7 @@ export class TextEditor extends HTMLElement {
137
137
  }
138
138
 
139
139
  override focus(): void {
140
- if (this.#activeEditor) {
141
- this.#activeEditor.focus();
142
- }
140
+ this.editor.focus();
143
141
  }
144
142
 
145
143
  #ensureSettingListeners(): void {
@@ -1752,7 +1752,8 @@ function removeNode(node: TreeElement, preserveParentExpandable = false): void {
1752
1752
  * @attribute hide-overflow
1753
1753
  */
1754
1754
  export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1755
- static readonly observedAttributes = ['navigation-variant', 'hide-overflow', 'dense'];
1755
+ static readonly observedAttributes =
1756
+ ['navigation-variant', 'hide-overflow', 'dense', 'show-selection-on-keyboard-focus'];
1756
1757
  readonly #treeOutline = new TreeOutlineInShadow(undefined, this);
1757
1758
 
1758
1759
  constructor() {
@@ -1918,6 +1919,9 @@ export class TreeViewElement extends HTMLElementWithLightDOMTemplate {
1918
1919
  case 'dense':
1919
1920
  this.#treeOutline.setDense(booleanValueIsTrue);
1920
1921
  break;
1922
+ case 'show-selection-on-keyboard-focus':
1923
+ this.#treeOutline.setShowSelectionOnKeyboardFocus(booleanValueIsTrue);
1924
+ break;
1921
1925
  }
1922
1926
  }
1923
1927
  }
@@ -3235,6 +3235,7 @@ export const knownContextValues = new Set([
3235
3235
  'removed-size',
3236
3236
  'rename',
3237
3237
  'render-blocking',
3238
+ 'render-blocking-widget',
3238
3239
  'rendering',
3239
3240
  'rendering-emulations',
3240
3241
  'rendering.toggle-prefers-color-scheme',
@@ -4382,6 +4383,7 @@ export const knownContextValues = new Set([
4382
4383
  'webgl-warning-fired',
4383
4384
  'webmcp.call-inputs',
4384
4385
  'webmcp.call-outputs',
4386
+ 'webmcp.cancel-call',
4385
4387
  'webmcp.canceled',
4386
4388
  'webmcp.completed',
4387
4389
  'webmcp.copy-tool-description',
package/package.json CHANGED
@@ -104,5 +104,5 @@
104
104
  "flat-cache": "6.1.12"
105
105
  }
106
106
  },
107
- "version": "1.0.1621064"
107
+ "version": "1.0.1621678"
108
108
  }