chrome-devtools-frontend 1.0.1625854 → 1.0.1626437

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 (27) hide show
  1. package/front_end/core/host/AidaClient.ts +1 -1
  2. package/front_end/core/host/AidaClientTypes.ts +2 -0
  3. package/front_end/core/host/AidaGcaTranslation.ts +2 -2
  4. package/front_end/core/sdk/CSSMetadata.ts +60 -55
  5. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +11 -2
  6. package/front_end/models/ai_assistance/StorageItem.ts +16 -0
  7. package/front_end/models/ai_assistance/agents/StorageAgent.ts +82 -0
  8. package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
  9. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +9 -37
  10. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +26 -3
  11. package/front_end/models/javascript_metadata/NativeFunctions.js +0 -4
  12. package/front_end/models/web_mcp/WebMCPModel.ts +11 -2
  13. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +6 -1
  14. package/front_end/panels/application/WebMCPView.ts +74 -16
  15. package/front_end/panels/application/webMCPView.css +13 -2
  16. package/front_end/panels/elements/StandaloneStylesContainer.ts +3 -0
  17. package/front_end/panels/elements/StylePropertyTreeElement.ts +35 -2
  18. package/front_end/panels/elements/StylesContainer.ts +1 -0
  19. package/front_end/panels/elements/StylesSidebarPane.ts +10 -4
  20. package/front_end/panels/emulation/DeviceModeToolbar.ts +7 -3
  21. package/front_end/panels/sources/ScopeChainSidebarPane.ts +5 -1
  22. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +179 -145
  23. package/front_end/panels/timeline/timelineTreeView.css +2 -2
  24. package/front_end/third_party/chromium/README.chromium +1 -1
  25. package/front_end/ui/components/tooltips/Tooltip.ts +3 -3
  26. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  27. package/package.json +1 -1
@@ -191,6 +191,10 @@ const UIStrings = {
191
191
  * @description Notice to display when a tool has been unregistered
192
192
  */
193
193
  toolUnregisteredNotice: 'This tool has been unregistered',
194
+ /**
195
+ * @description Text preceding a nested error in a stack trace
196
+ */
197
+ causedBy: 'Caused by:',
194
198
  } as const;
195
199
  const str_ = i18n.i18n.registerUIStrings('panels/application/WebMCPView.ts', UIStrings);
196
200
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -219,6 +223,11 @@ export interface FilterMenuButtons {
219
223
  toolTypes: FilterMenuButton;
220
224
  statusTypes: FilterMenuButton;
221
225
  }
226
+ export const enum TabId {
227
+ DETAILS = 'webmcp.tool-details',
228
+ INPUT = 'webmcp.call-inputs',
229
+ OUTPUT = 'webmcp.call-outputs',
230
+ }
222
231
  export interface SelectedTool {
223
232
  tool: WebMCP.WebMCPModel.Tool;
224
233
  parameters?: Record<string, unknown>;
@@ -229,7 +238,8 @@ export interface ViewInput {
229
238
  onToolSelect: (tool: WebMCP.WebMCPModel.Tool|null) => void;
230
239
  onRevealTool: (tool: WebMCP.WebMCPModel.Tool, parameters?: Record<string, unknown>) => void;
231
240
  selectedCall: WebMCP.WebMCPModel.Call|null;
232
- onCallSelect: (call: WebMCP.WebMCPModel.Call|null) => void;
241
+ selectedTab?: TabId;
242
+ onCallSelect: (call: WebMCP.WebMCPModel.Call|null, tabId?: TabId) => void;
233
243
  filters: FilterState;
234
244
  filterButtons: FilterMenuButtons;
235
245
  onClearLogClick: () => void;
@@ -508,7 +518,10 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
508
518
  }, {jslogContext: 'webmcp.cancel-call'});
509
519
  }
510
520
  }}>
511
- <td>
521
+ <td @click=${(e: Event) => {
522
+ e.stopPropagation();
523
+ input.onCallSelect(call, TabId.DETAILS);
524
+ }}>
512
525
  <div class="name-cell">
513
526
  <span>${call.tool.name}</span>
514
527
  <button class="run-tool-action-button"
@@ -524,7 +537,10 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
524
537
  </button>
525
538
  </div>
526
539
  </td>
527
- <td>
540
+ <td @click=${(e: Event) => {
541
+ e.stopPropagation();
542
+ input.onCallSelect(call, TabId.OUTPUT);
543
+ }}>
528
544
  <div class="status-cell">
529
545
  ${iconName(call) ? html`<devtools-icon class="small" name=${iconName(call)}></devtools-icon>`
530
546
  : ''}
@@ -532,8 +548,14 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
532
548
  </div>
533
549
  </td>
534
550
  ${!input.selectedCall ? html`
535
- <td>${call.input}</td>
536
- <td>${call.result?.output ? JSON.stringify(call.result.output)
551
+ <td @click=${(e: Event) => {
552
+ e.stopPropagation();
553
+ input.onCallSelect(call, TabId.INPUT);
554
+ }}>${call.input}</td>
555
+ <td @click=${(e: Event) => {
556
+ e.stopPropagation();
557
+ input.onCallSelect(call, TabId.OUTPUT);
558
+ }}>${call.result?.output ? JSON.stringify(call.result.output)
537
559
  : call.result?.errorText ?? ''}</td>
538
560
  ` : nothing}
539
561
  </tr>
@@ -552,17 +574,20 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
552
574
  @click=${() => input.onCallSelect(null)}
553
575
  ></devtools-button>
554
576
  <devtools-widget
555
- id="webmcp.tool-details"
577
+ id=${TabId.DETAILS}
578
+ ?selected=${input.selectedTab === TabId.DETAILS}
556
579
  title=${i18nString(UIStrings.toolDetails)}
557
580
  ${widget(ToolDetailsWidget, {tool: input.selectedCall?.tool, isUnregistered: input.selectedCall ? !input.tools.includes(input.selectedCall.tool) : false})}>
558
581
  </devtools-widget>
559
582
  <devtools-widget
560
- id="webmcp.call-inputs"
583
+ id=${TabId.INPUT}
584
+ ?selected=${input.selectedTab === TabId.INPUT}
561
585
  title=${i18nString(UIStrings.input)}
562
586
  ${widget(PayloadWidget, parsePayload(input.selectedCall?.input))}>
563
587
  </devtools-widget>
564
588
  <devtools-widget
565
- id="webmcp.call-outputs"
589
+ id=${TabId.OUTPUT}
590
+ ?selected=${input.selectedTab === TabId.OUTPUT}
566
591
  title=${i18nString(UIStrings.output)}
567
592
  ${widget(PayloadWidget, {
568
593
  valueObject: input.selectedCall?.result?.output,
@@ -697,6 +722,8 @@ export class WebMCPView extends UI.Widget.VBox {
697
722
  readonly #view: View;
698
723
  #selectedTool: SelectedTool|null = null;
699
724
  #selectedCall: WebMCP.WebMCPModel.Call|null = null;
725
+ #selectedTab: TabId|undefined = undefined;
726
+ #lastDevToolsInvocationId: string|null = null;
700
727
 
701
728
  #filterState: FilterState = {
702
729
  text: '',
@@ -799,17 +826,26 @@ export class WebMCPView extends UI.Widget.VBox {
799
826
  #webMCPModelAdded(model: WebMCP.WebMCPModel.WebMCPModel): void {
800
827
  model.addEventListener(WebMCP.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
801
828
  model.addEventListener(WebMCP.WebMCPModel.Events.TOOLS_REMOVED, this.#toolsRemoved, this);
802
- model.addEventListener(WebMCP.WebMCPModel.Events.TOOL_INVOKED, this.requestUpdate, this);
829
+ model.addEventListener(WebMCP.WebMCPModel.Events.TOOL_INVOKED, this.#toolInvoked, this);
803
830
  model.addEventListener(WebMCP.WebMCPModel.Events.TOOL_RESPONDED, this.requestUpdate, this);
804
831
  }
805
832
 
806
833
  #webMCPModelRemoved(model: WebMCP.WebMCPModel.WebMCPModel): void {
807
834
  model.removeEventListener(WebMCP.WebMCPModel.Events.TOOLS_ADDED, this.requestUpdate, this);
808
835
  model.removeEventListener(WebMCP.WebMCPModel.Events.TOOLS_REMOVED, this.#toolsRemoved, this);
809
- model.removeEventListener(WebMCP.WebMCPModel.Events.TOOL_INVOKED, this.requestUpdate, this);
836
+ model.removeEventListener(WebMCP.WebMCPModel.Events.TOOL_INVOKED, this.#toolInvoked, this);
810
837
  model.removeEventListener(WebMCP.WebMCPModel.Events.TOOL_RESPONDED, this.requestUpdate, this);
811
838
  }
812
839
 
840
+ #toolInvoked(event: Common.EventTarget.EventTargetEvent<WebMCP.WebMCPModel.Call>): void {
841
+ const call = event.data;
842
+ if (call.invocationId === this.#lastDevToolsInvocationId) {
843
+ this.#selectedCall = call;
844
+ this.#lastDevToolsInvocationId = null;
845
+ }
846
+ this.requestUpdate();
847
+ }
848
+
813
849
  #toolsRemoved(event: Common.EventTarget.EventTargetEvent<readonly WebMCP.WebMCPModel.Tool[]>): void {
814
850
  if (this.#selectedTool && event.data.includes(this.#selectedTool.tool)) {
815
851
  this.#selectedTool = null;
@@ -861,8 +897,16 @@ export class WebMCPView extends UI.Widget.VBox {
861
897
  this.requestUpdate();
862
898
  },
863
899
  selectedCall: this.#selectedCall,
864
- onCallSelect: call => {
865
- this.#selectedCall = call;
900
+ selectedTab: this.#selectedTab,
901
+ onCallSelect: (call, tabId) => {
902
+ if (call === null) {
903
+ this.#selectedCall = null;
904
+ } else if (this.#selectedCall === null) {
905
+ this.#selectedCall = call;
906
+ this.#selectedTab = tabId;
907
+ } else {
908
+ this.#selectedCall = call;
909
+ }
866
910
  this.requestUpdate();
867
911
  },
868
912
  toolCalls: filteredCalls,
@@ -870,9 +914,20 @@ export class WebMCPView extends UI.Widget.VBox {
870
914
  filterButtons: this.#filterButtons,
871
915
  onClearLogClick: this.#handleClearLogClick,
872
916
  onFilterChange: this.#handleFilterChange,
873
- onRunTool: event => {
917
+ onRunTool: async event => {
874
918
  if (this.#selectedTool) {
875
- void this.#selectedTool.tool.invoke(event.data.parameters || {});
919
+ this.#selectedTool.parameters = event.data.parameters || {};
920
+ this.#lastDevToolsInvocationId = await this.#selectedTool.tool.invoke(this.#selectedTool.parameters) ?? null;
921
+ if (this.#lastDevToolsInvocationId) {
922
+ const models = SDK.TargetManager.TargetManager.instance().models(WebMCP.WebMCPModel.WebMCPModel);
923
+ const call =
924
+ models.flatMap(model => model.toolCalls).find(c => c.invocationId === this.#lastDevToolsInvocationId);
925
+ if (call) {
926
+ this.#selectedCall = call;
927
+ this.#lastDevToolsInvocationId = null;
928
+ }
929
+ }
930
+ this.requestUpdate();
876
931
  }
877
932
  },
878
933
  onPaste: async () => {
@@ -891,6 +946,7 @@ export class WebMCPView extends UI.Widget.VBox {
891
946
  },
892
947
  };
893
948
  this.#view(input, {}, this.contentElement);
949
+ this.#selectedTab = undefined;
894
950
  }
895
951
  }
896
952
  export interface PayloadViewInput {
@@ -972,7 +1028,9 @@ export const PAYLOAD_DEFAULT_VIEW = (input: PayloadViewInput, output: object, ta
972
1028
  <div class="payload-value source-code error-text">
973
1029
  ${details.frames.length === 0 && details.description ? html`<span>${details.description}\n</span>` : nothing}
974
1030
  <div>${details.frames.map(renderFrame)}</div>
975
- ${details.cause ? html`\nCaused by:\n${createException(details.cause, linkifier)}` : nothing}</div>`;
1031
+ ${
1032
+ details.cause ? html`\n${i18nString(UIStrings.causedBy)}\n${createException(details.cause, linkifier)}` :
1033
+ nothing}</div>`;
976
1034
  };
977
1035
 
978
1036
  render(
@@ -1124,7 +1182,7 @@ const TOOL_DETAILS_VIEW = (input: ToolDetailsViewInput, output: undefined, targe
1124
1182
  ></devtools-button>
1125
1183
  </div>` : origin ? html`
1126
1184
  <div class="label">Origin</div>
1127
- <div class="value">
1185
+ <div class="value stack-trace">
1128
1186
  ${widget(Components.JSPresentationUtils.StackTracePreviewContent,
1129
1187
  {stackTrace: origin, options: { expandable: true}})}
1130
1188
  </div>` : nothing}
@@ -88,6 +88,14 @@
88
88
  display: flex;
89
89
  gap: var(--sys-size-5);
90
90
  align-items: center;
91
+ min-width: 0;
92
+ }
93
+
94
+ .name-cell > span {
95
+ white-space: nowrap;
96
+ overflow: hidden;
97
+ text-overflow: ellipsis;
98
+ min-width: 0;
91
99
  }
92
100
 
93
101
  .run-tool-action-button {
@@ -141,8 +149,11 @@
141
149
  color: var(--sys-color-on-surface);
142
150
  overflow-wrap: anywhere;
143
151
 
144
- &:has(> .stack-preview-container) {
145
- padding: var(--sys-size-4) 0;
152
+ &.stack-trace {
153
+ display: flex;
154
+ padding: 0;
155
+ margin-top: calc(-1 * (var(--sys-size-1) + var(--sys-size-2)));
156
+ margin-left: calc(-1 * var(--sys-size-3));
146
157
  }
147
158
 
148
159
  &.tool-origin-container {
@@ -314,6 +314,9 @@ export class StandaloneStylesContainer extends Common.ObjectWrapper.eventMixin<E
314
314
  jumpToFontPaletteDefinition(_paletteName: string): void {
315
315
  }
316
316
 
317
+ jumpToCounterStyleDefinition(_counterStyleName: string): void {
318
+ }
319
+
317
320
  jumpToDeclaration(_valueSource: SDK.CSSMatchedStyles.CSSValueSource): void {
318
321
  }
319
322
 
@@ -1156,10 +1156,19 @@ export class LinkableNameRenderer extends rendererBase(SDK.CSSPropertyParserMatc
1156
1156
  return {
1157
1157
  jslogContext: 'css-font-palette',
1158
1158
  metric: null,
1159
- ruleBlock: '@font-*',
1159
+ ruleBlock: '', // Not used
1160
1160
  isDefined: Boolean(this.#matchedStyles.atRules().find(
1161
1161
  ar => ar.type() === 'font-palette-values' && ar.name()?.text === match.text)),
1162
1162
  };
1163
+ case SDK.CSSPropertyParserMatchers.LinkableNameProperties.LIST_STYLE:
1164
+ case SDK.CSSPropertyParserMatchers.LinkableNameProperties.LIST_STYLE_TYPE:
1165
+ return {
1166
+ jslogContext: 'css-list-style-type',
1167
+ metric: null,
1168
+ ruleBlock: '', // Not used
1169
+ isDefined: Boolean(this.#matchedStyles.atRules().find(
1170
+ ar => ar.type() === 'counter-style' && ar.name()?.text === match.text)),
1171
+ };
1163
1172
  case SDK.CSSPropertyParserMatchers.LinkableNameProperties.POSITION_TRY:
1164
1173
  case SDK.CSSPropertyParserMatchers.LinkableNameProperties.POSITION_TRY_FALLBACKS:
1165
1174
  return {
@@ -1182,6 +1191,10 @@ export class LinkableNameRenderer extends rendererBase(SDK.CSSPropertyParserMatc
1182
1191
  metric && Host.userMetrics.swatchActivated(metric);
1183
1192
  if (match.propertyName === SDK.CSSPropertyParserMatchers.LinkableNameProperties.FONT_PALETTE) {
1184
1193
  this.#stylesContainer.jumpToFontPaletteDefinition(match.text);
1194
+ } else if (
1195
+ match.propertyName === SDK.CSSPropertyParserMatchers.LinkableNameProperties.LIST_STYLE ||
1196
+ match.propertyName === SDK.CSSPropertyParserMatchers.LinkableNameProperties.LIST_STYLE_TYPE) {
1197
+ this.#stylesContainer.jumpToCounterStyleDefinition(match.text);
1185
1198
  } else {
1186
1199
  this.#stylesContainer.jumpToSectionBlock(`${ruleBlock} ${match.text}`);
1187
1200
  }
@@ -3635,6 +3648,9 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
3635
3648
  return;
3636
3649
  }
3637
3650
  this.#clearGhostTextInValue();
3651
+ if (this.value === text) {
3652
+ return;
3653
+ }
3638
3654
  if (this.value) {
3639
3655
  // If there is an existing value, and user is editing the name field
3640
3656
  // which leads to a new value suggestion, then the previous value should no
@@ -3648,7 +3664,24 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
3648
3664
  }
3649
3665
 
3650
3666
  #clearGhostTextInValue(): void {
3651
- this.listItemElement.querySelector('.ghost-value-prediction')?.remove();
3667
+ const ghostTextElement = this.listItemElement.querySelector('.ghost-value-prediction');
3668
+ if (!ghostTextElement) {
3669
+ return;
3670
+ }
3671
+ ghostTextElement.remove();
3672
+ // If there's no value, there's nothing to restore
3673
+ if (!this.value) {
3674
+ return;
3675
+ }
3676
+ // Restore original classes
3677
+ if (this.property.parsedOk) {
3678
+ this.listItemElement.classList.remove('not-parsed-ok', 'invalid-property-value');
3679
+ } else {
3680
+ const invalidPropertyValue = SDK.CSSMetadata.cssMetadata().isCSSPropertyName(this.property.name);
3681
+ if (!invalidPropertyValue) {
3682
+ this.listItemElement.classList.remove('invalid-property-value');
3683
+ }
3684
+ }
3652
3685
  }
3653
3686
  }
3654
3687
 
@@ -52,6 +52,7 @@ export interface StylesContainer {
52
52
  jumpToProperty(propertyName: string, sectionName?: string, blockName?: string): boolean;
53
53
  jumpToSectionBlock(section: string): void;
54
54
  jumpToFontPaletteDefinition(paletteName: string): void;
55
+ jumpToCounterStyleDefinition(counterStyleName: string): void;
55
56
  jumpToDeclaration(valueSource: SDK.CSSMatchedStyles.CSSValueSource): void;
56
57
  setActiveProperty(treeElement: StylePropertyTreeElement|null): void;
57
58
  addStyleUpdateListener(listener: () => void): void;
@@ -140,6 +140,10 @@ const UIStrings = {
140
140
  * @example {color: blue;} PH1
141
141
  */
142
142
  aiSuggestionAccepted: '{PH1} Suggestion accepted.',
143
+ /**
144
+ * @description Title of the general at-rule section
145
+ */
146
+ atRuleSection: 'Other @rules',
143
147
  } as const;
144
148
 
145
149
  const str_ = i18n.i18n.registerUIStrings('panels/elements/StylesSidebarPane.ts', UIStrings);
@@ -154,8 +158,6 @@ const MIN_FOLDED_SECTIONS_COUNT = 5;
154
158
  export const REGISTERED_PROPERTY_SECTION_NAME = '@property';
155
159
  /** Title of the function section **/
156
160
  export const FUNCTION_SECTION_NAME = '@function';
157
- /** Title of the general at-rule section */
158
- export const AT_RULE_SECTION_NAME = '@font-*';
159
161
 
160
162
  // Highlightable properties are those that can be hovered in the sidebar to trigger a specific
161
163
  // highlighting mode on the current element.
@@ -347,7 +349,11 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
347
349
  }
348
350
 
349
351
  jumpToFontPaletteDefinition(paletteName: string): void {
350
- this.jumpToSection(`@font-palette-values ${paletteName}`, AT_RULE_SECTION_NAME);
352
+ this.jumpToSection(`@font-palette-values ${paletteName}`, i18nString(UIStrings.atRuleSection));
353
+ }
354
+
355
+ jumpToCounterStyleDefinition(counterStyleName: string): void {
356
+ this.jumpToSection(`@counter-style ${counterStyleName}`, i18nString(UIStrings.atRuleSection));
351
357
  }
352
358
 
353
359
  forceUpdate(): void {
@@ -1710,7 +1716,7 @@ export class SectionBlock {
1710
1716
  const separatorElement = document.createElement('div');
1711
1717
  const block = new SectionBlock(separatorElement, true, expandedByDefault);
1712
1718
  separatorElement.className = 'sidebar-separator';
1713
- separatorElement.appendChild(document.createTextNode(AT_RULE_SECTION_NAME));
1719
+ separatorElement.appendChild(document.createTextNode(i18nString(UIStrings.atRuleSection)));
1714
1720
  return block;
1715
1721
  }
1716
1722
 
@@ -248,6 +248,10 @@ export class DeviceModeToolbar {
248
248
  'emulation.device-mode-value', {device: '', orientation: '', mode: ''});
249
249
 
250
250
  this.model.toolbarControlsEnabledSetting().addChangeListener(this.update, this);
251
+ this.model.scaleSetting().addChangeListener(this.update, this);
252
+ this.model.uaSetting().addChangeListener(this.update, this);
253
+ this.model.deviceScaleFactorSetting().addChangeListener(this.update, this);
254
+ this.model.addEventListener(EmulationModel.DeviceModeModel.Events.UPDATED, this.update, this);
251
255
 
252
256
  this.update();
253
257
  }
@@ -449,11 +453,11 @@ export class DeviceModeToolbar {
449
453
 
450
454
  <div class="device-mode-empty-toolbar-element"></div>
451
455
  <devtools-button class="toolbar-button"
452
- .data=${{variant: Buttons.Button.Variant.TOOLBAR, iconName: 'screen-rotation'} as Buttons.Button.ButtonData}
456
+ .data=${{variant: Buttons.Button.Variant.TOOLBAR, iconName: 'screen-rotation',
457
+ disabled: modeButtonDisabled} as Buttons.Button.ButtonData}
453
458
  jslog=${VisualLogging.action('screen-rotation').track({click: true})}
454
459
  @click=${this.modeMenuClicked.bind(this)}
455
- .title=${modeButtonTitle}
456
- .disabled=${modeButtonDisabled}>
460
+ .title=${modeButtonTitle}>
457
461
  </devtools-button>
458
462
 
459
463
  <!-- Show dual screen toolbar -->
@@ -104,6 +104,10 @@ export class ScopeChainSidebarPane extends UI.Widget.VBox implements UI.ContextF
104
104
  return scopeChainSidebarPaneInstance;
105
105
  }
106
106
 
107
+ treeOutlineForTest(): ObjectUI.ObjectPropertiesSection.ObjectPropertiesSectionsTreeOutline {
108
+ return this.treeOutline;
109
+ }
110
+
107
111
  flavorChanged(callFrame: StackTrace.StackTrace.DebuggableFrameFlavor|null): void {
108
112
  this.#scopeChainModel?.dispose();
109
113
  this.#scopeChainModel = null;
@@ -210,6 +214,6 @@ export class ScopeChainSidebarPane extends UI.Widget.VBox implements UI.ContextF
210
214
  return section;
211
215
  }
212
216
 
213
- private sidebarPaneUpdatedForTest(): void {
217
+ sidebarPaneUpdatedForTest(): void {
214
218
  }
215
219
  }