chrome-devtools-frontend 1.0.1605390 → 1.0.1606789

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 (60) hide show
  1. package/front_end/core/host/UserMetrics.ts +0 -1
  2. package/front_end/core/platform/api/HostRuntime.ts +9 -6
  3. package/front_end/core/platform/browser/HostRuntime.ts +2 -2
  4. package/front_end/core/platform/node/HostRuntime.ts +7 -7
  5. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  6. package/front_end/core/root/ExperimentNames.ts +0 -1
  7. package/front_end/core/sdk/CrashReportContextModel.ts +28 -0
  8. package/front_end/core/sdk/sdk.ts +2 -2
  9. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshotWorkerDispatcher.ts +3 -3
  10. package/front_end/entrypoints/heap_snapshot_worker/heap_snapshot_worker-entrypoint.ts +5 -4
  11. package/front_end/entrypoints/main/MainImpl.ts +0 -101
  12. package/front_end/models/ai_assistance/ChangeManager.ts +6 -6
  13. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +112 -5
  14. package/front_end/models/ai_assistance/agents/AiAgent.ts +0 -24
  15. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +2 -2
  16. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +6 -3
  17. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +13 -1
  18. package/front_end/models/ai_assistance/agents/FileAgent.ts +15 -1
  19. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +15 -1
  20. package/front_end/models/ai_assistance/agents/StylingAgent.ts +24 -15
  21. package/front_end/models/ai_assistance/ai_assistance.ts +0 -2
  22. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -5
  23. package/front_end/models/web_mcp/WebMCPModel.ts +187 -0
  24. package/front_end/models/web_mcp/web_mcp.ts +9 -0
  25. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -1
  26. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  27. package/front_end/panels/ai_assistance/components/AccessibilityAgentMarkdownRenderer.ts +88 -0
  28. package/front_end/panels/ai_assistance/components/ChatMessage.ts +19 -3
  29. package/front_end/panels/ai_assistance/components/ChatView.ts +11 -4
  30. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +12 -3
  31. package/front_end/panels/ai_assistance/components/chatMessage.css +10 -0
  32. package/front_end/panels/ai_assistance/components/chatView.css +0 -2
  33. package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +13 -0
  34. package/front_end/panels/application/WebMCPView.ts +126 -30
  35. package/front_end/panels/application/webMCPView.css +28 -2
  36. package/front_end/panels/elements/ElementsTreeElement.ts +1 -1
  37. package/front_end/panels/elements/StylePropertiesSection.ts +0 -4
  38. package/front_end/panels/elements/elements.ts +3 -0
  39. package/front_end/panels/elements/stylePropertiesTreeOutline.css +7 -0
  40. package/front_end/panels/lighthouse/LighthousePanel.ts +7 -1
  41. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +25 -2
  42. package/front_end/panels/profiler/HeapSnapshotProxy.ts +2 -6
  43. package/front_end/panels/profiler/HeapSnapshotView.ts +7 -16
  44. package/front_end/panels/profiler/ProfileHeader.ts +1 -4
  45. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +27 -22
  46. package/front_end/panels/timeline/TimelinePanel.ts +0 -153
  47. package/front_end/panels/timeline/TimelineTreeView.ts +11 -1
  48. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -1
  49. package/front_end/third_party/chromium/README.chromium +1 -1
  50. package/front_end/ui/components/markdown_view/CodeBlock.ts +47 -1
  51. package/front_end/ui/components/markdown_view/codeBlock.css +8 -0
  52. package/front_end/ui/legacy/TabbedPane.ts +123 -3
  53. package/front_end/ui/legacy/Widget.ts +67 -28
  54. package/front_end/ui/legacy/components/source_frame/JSONView.ts +1 -1
  55. package/front_end/ui/legacy/components/utils/jsUtils.css +2 -0
  56. package/front_end/ui/legacy/infobar.css +1 -0
  57. package/front_end/ui/visual_logging/KnownContextValues.ts +0 -1
  58. package/package.json +1 -1
  59. package/front_end/core/sdk/WebMCPModel.ts +0 -159
  60. package/front_end/models/ai_assistance/ConversationHandler.ts +0 -325
@@ -8,7 +8,6 @@ import * as Common from '../../core/common/common.js';
8
8
  import * as Host from '../../core/host/host.js';
9
9
  import * as i18n from '../../core/i18n/i18n.js';
10
10
  import * as Platform from '../../core/platform/platform.js';
11
- import * as Root from '../../core/root/root.js';
12
11
  import * as SDK from '../../core/sdk/sdk.js';
13
12
  import type * as Protocol from '../../generated/protocol.js';
14
13
  import * as Bindings from '../../models/bindings/bindings.js';
@@ -832,7 +831,7 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
832
831
 
833
832
  inspectedObjectChanged(
834
833
  event: Common.EventTarget.EventTargetEvent<DataGrid.DataGrid.DataGridNode<HeapSnapshotGridNode>>): void {
835
- const selectedNode = (event.data as HeapSnapshotGridNode);
834
+ const selectedNode = event.data;
836
835
  const heapProfilerModel = this.profile.heapProfilerModel();
837
836
  if (heapProfilerModel && selectedNode instanceof HeapSnapshotGenericObjectNode) {
838
837
  void heapProfilerModel.addInspectedHeapObject(
@@ -1339,12 +1338,10 @@ export class HeapSnapshotProfileType extends
1339
1338
  }
1340
1339
 
1341
1340
  override customContent(): Element|null {
1342
- const showOptionToExposeInternalsInHeapSnapshot = Root.Runtime.experiments.isEnabled(
1343
- Root.ExperimentNames.ExperimentName.SHOW_OPTION_TO_EXPOSE_INTERNALS_IN_HEAP_SNAPSHOT);
1344
1341
  const exposeInternalsInHeapSnapshotCheckbox =
1345
1342
  SettingsUI.SettingsUI.createSettingCheckbox(i18nString(UIStrings.exposeInternals), this.exposeInternals);
1346
1343
  this.customContentInternal = exposeInternalsInHeapSnapshotCheckbox;
1347
- return showOptionToExposeInternalsInHeapSnapshot ? exposeInternalsInHeapSnapshotCheckbox : null;
1344
+ return exposeInternalsInHeapSnapshotCheckbox;
1348
1345
  }
1349
1346
 
1350
1347
  override setCustomContentEnabled(enable: boolean): void {
@@ -1809,9 +1806,7 @@ export class HeapProfileHeader extends ProfileHeader {
1809
1806
  return;
1810
1807
  }
1811
1808
  if (this.tempFile) {
1812
- const error = (await this.tempFile.copyToOutputStream(fileOutputStream, this.onChunkTransferred.bind(this)) as {
1813
- message: string,
1814
- } | null);
1809
+ const error = await this.tempFile.copyToOutputStream(fileOutputStream, this.onChunkTransferred.bind(this));
1815
1810
  if (error) {
1816
1811
  Common.Console.Console.instance().error('Failed to read heap snapshot from temp file: ' + error.message);
1817
1812
  }
@@ -1841,15 +1836,11 @@ export class HeapProfileHeader extends ProfileHeader {
1841
1836
  this.setupWorker();
1842
1837
  const reader = new Bindings.FileUtils.ChunkedFileReader(file, 10000000);
1843
1838
  const success = await reader.read((this.receiver as Common.StringOutputStream.OutputStream));
1844
- if (!success) {
1845
- const error = (reader.error() as {
1846
- message: string,
1847
- } | null);
1848
- if (error) {
1849
- this.updateStatus(error.message);
1850
- }
1839
+ const error = reader.error();
1840
+ if (!success && error) {
1841
+ this.updateStatus(error.message);
1851
1842
  }
1852
- return success ? null : reader.error();
1843
+ return success ? null : error;
1853
1844
  }
1854
1845
 
1855
1846
  override profileType(): HeapSnapshotProfileType {
@@ -156,10 +156,7 @@ export class ProfileType extends Common.ObjectWrapper.ObjectWrapper<ProfileEvent
156
156
  }
157
157
 
158
158
  getProfiles(): ProfileHeader[] {
159
- function isFinished(this: ProfileType, profile: ProfileHeader): boolean {
160
- return this.#profileBeingRecorded !== profile;
161
- }
162
- return this.profiles.filter(isFinished.bind(this));
159
+ return this.profiles.filter(profile => this.#profileBeingRecorded !== profile);
163
160
  }
164
161
 
165
162
  customContent(): Element|null {
@@ -675,28 +675,33 @@ type InfoWidgetView = (input: InfoWidgetViewInput, output: undefined, target: HT
675
675
 
676
676
  const INFO_WIDGET_VIEW: InfoWidgetView = (input, _output, target) => {
677
677
  // clang-format off
678
- render(widget(UI.TabbedPane.TabbedPane, {
679
- tabs: [
680
- {
681
- id: 'request',
682
- title: i18nString(UIStrings.request),
683
- view: input.type === undefined ?
684
- new UI.EmptyWidget.EmptyWidget(
685
- i18nString(UIStrings.noMessageSelected), i18nString(UIStrings.selectAMessageToView)) :
686
- SourceFrame.JSONView.JSONView.createViewSync(input.request || null),
687
- enabled: input.type === 'sent',
688
- selected: input.selectedTab === 'request',
689
- },
690
- {
691
- id: 'response',
692
- title: i18nString(UIStrings.response),
693
- view: input.type === undefined ?
694
- new UI.EmptyWidget.EmptyWidget(
695
- i18nString(UIStrings.noMessageSelected), i18nString(UIStrings.selectAMessageToView)) :
696
- SourceFrame.JSONView.JSONView.createViewSync(input.response || null),
697
- selected: input.selectedTab === 'response',
698
- }
699
- ]}), target);
678
+ render(html`
679
+ <devtools-tabbed-pane>${input.type === undefined ? html`
680
+ <devtools-widget
681
+ id="request" title=${i18nString(UIStrings.request)}
682
+ ?selected=${input.selectedTab === 'request'} disabled
683
+ ${widget(UI.EmptyWidget.EmptyWidget, {
684
+ header: i18nString(UIStrings.noMessageSelected),
685
+ text: i18nString(UIStrings.selectAMessageToView)})}>
686
+ </devtools-widget>
687
+ <devtools-widget
688
+ id="response" title=${i18nString(UIStrings.response)}
689
+ ?selected=${input.selectedTab === 'response'}
690
+ ${widget(UI.EmptyWidget.EmptyWidget, {
691
+ header: i18nString(UIStrings.noMessageSelected),
692
+ text: i18nString(UIStrings.selectAMessageToView)})}>
693
+ </devtools-widget>`: html`
694
+ <devtools-widget
695
+ id="request" title=${i18nString(UIStrings.request)}
696
+ ?selected=${input.selectedTab === 'request'} ?disabled=${input.type !== 'sent'}
697
+ ${widget(SourceFrame.JSONView.SearchableJsonView, {jsonObject: input.request})}>
698
+ </devtools-widget>
699
+ <devtools-widget
700
+ id="response" title=${i18nString(UIStrings.response)}
701
+ ?selected=${input.selectedTab === 'response'}
702
+ ${widget(SourceFrame.JSONView.SearchableJsonView, {jsonObject: input.response})}>
703
+ </devtools-widget>`}
704
+ </devtools-tabbed-pane>`, target);
700
705
  // clang-format on
701
706
  };
702
707
 
@@ -54,7 +54,6 @@ import * as TraceBounds from '../../services/trace_bounds/trace_bounds.js';
54
54
  import * as Tracing from '../../services/tracing/tracing.js';
55
55
  import * as Adorners from '../../ui/components/adorners/adorners.js';
56
56
  import * as Dialogs from '../../ui/components/dialogs/dialogs.js';
57
- import * as Snackbars from '../../ui/components/snackbars/snackbars.js';
58
57
  import {Link} from '../../ui/kit/kit.js';
59
58
  import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
60
59
  import * as SettingsUI from '../../ui/legacy/components/settings_ui/settings_ui.js';
@@ -311,10 +310,6 @@ const UIStrings = {
311
310
  * @description Title of the shortcuts dialog shown to the user that lists keyboard shortcuts.
312
311
  */
313
312
  shortcutsDialogTitle: 'Keyboard shortcuts for flamechart',
314
- /**
315
- * @description Notification shown to the user whenever DevTools receives an external request.
316
- */
317
- externalRequestReceived: '`DevTools` received an external request',
318
313
  } as const;
319
314
  const str_ = i18n.i18n.registerUIStrings('panels/timeline/TimelinePanel.ts', UIStrings);
320
315
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -407,7 +402,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
407
402
  private traceLoadStart!: Trace.Types.Timing.Milli|null;
408
403
 
409
404
  #traceEngineModel: Trace.TraceModel.Model;
410
- #externalAIConversationData: AiAssistanceModel.ConversationHandler.ExternalPerformanceAIConversationData|null = null;
411
405
  #sourceMapsResolver: SourceMapsResolver.SourceMapsResolver|null = null;
412
406
  #entityMapper: Trace.EntityMapper.EntityMapper|null = null;
413
407
  #onSourceMapsNodeNamesResolvedBound = this.#onSourceMapsNodeNamesResolved.bind(this);
@@ -976,39 +970,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
976
970
  return this.#traceEngineModel;
977
971
  }
978
972
 
979
- getOrCreateExternalAIConversationData(): AiAssistanceModel.ConversationHandler.ExternalPerformanceAIConversationData {
980
- if (!this.#externalAIConversationData) {
981
- const conversationHandler = AiAssistanceModel.ConversationHandler.ConversationHandler.instance();
982
- const focus = AiAssistanceModel.AIContext.getPerformanceAgentFocusFromModel(this.model);
983
- if (!focus) {
984
- throw new Error('could not create performance agent focus');
985
- }
986
-
987
- const conversation = new AiAssistanceModel.AiConversation.AiConversation({
988
- type: AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE,
989
- data: [],
990
- isReadOnly: true,
991
- aidaClient: conversationHandler.aidaClient,
992
- isExternal: true,
993
- });
994
-
995
- const selected = new AiAssistanceModel.PerformanceAgent.PerformanceTraceContext(focus);
996
- selected.external = true;
997
-
998
- this.#externalAIConversationData = {
999
- conversationHandler,
1000
- conversation,
1001
- selected,
1002
- };
1003
- }
1004
-
1005
- return this.#externalAIConversationData;
1006
- }
1007
-
1008
- invalidateExternalAIConversationData(): void {
1009
- this.#externalAIConversationData = null;
1010
- }
1011
-
1012
973
  /**
1013
974
  * NOTE: this method only exists to enable some layout tests to be migrated to the new engine.
1014
975
  * DO NOT use this method within DevTools. It is marked as deprecated so
@@ -3102,120 +3063,6 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
3102
3063
  return trace;
3103
3064
  }
3104
3065
 
3105
- static async *
3106
- handleExternalRecordRequest(): AsyncGenerator<
3107
- AiAssistanceModel.AiAgent.ExternalRequestResponse, AiAssistanceModel.AiAgent.ExternalRequestResponse> {
3108
- yield {
3109
- type: AiAssistanceModel.AiAgent.ExternalRequestResponseType.NOTIFICATION,
3110
- message: 'Recording performance trace',
3111
- };
3112
- TimelinePanel.instance().invalidateExternalAIConversationData();
3113
- void VisualLogging.logFunctionCall('timeline.record-reload', 'external');
3114
- Snackbars.Snackbar.Snackbar.show({message: i18nString(UIStrings.externalRequestReceived)});
3115
-
3116
- const panelInstance = TimelinePanel.instance();
3117
- // Given how the current UX works, it's nice to show the user the Perf
3118
- // Panel so they see what's happening
3119
- await UI.ViewManager.ViewManager.instance().showView('timeline');
3120
-
3121
- function onRecordingCompleted(eventData: EventTypes[Events.RECORDING_COMPLETED]):
3122
- AiAssistanceModel.AiAgent.ExternalRequestResponse {
3123
- if ('errorText' in eventData) {
3124
- return {
3125
- type: AiAssistanceModel.AiAgent.ExternalRequestResponseType.ERROR,
3126
- message: `Error running the trace: ${eventData.errorText}`,
3127
- };
3128
- }
3129
-
3130
- const parsedTrace = panelInstance.model.parsedTrace(eventData.traceIndex);
3131
- if (!parsedTrace || !parsedTrace.insights || parsedTrace.insights.size === 0) {
3132
- return {
3133
- type: AiAssistanceModel.AiAgent.ExternalRequestResponseType.ERROR,
3134
- message: 'The trace was loaded successfully but no Insights were detected.',
3135
- };
3136
- }
3137
-
3138
- const insightSetId = Array.from(parsedTrace.insights.keys()).find(k => k !== 'NO_NAVIGATION');
3139
- if (!insightSetId) {
3140
- return {
3141
- type: AiAssistanceModel.AiAgent.ExternalRequestResponseType.ERROR,
3142
- message: 'The trace was loaded successfully but no navigation was detected.',
3143
- };
3144
- }
3145
-
3146
- const insightsForNav = parsedTrace.insights.get(insightSetId);
3147
- if (!insightsForNav) {
3148
- return {
3149
- type: AiAssistanceModel.AiAgent.ExternalRequestResponseType.ERROR,
3150
- message: 'The trace was loaded successfully but no Insights were detected.',
3151
- };
3152
- }
3153
-
3154
- let responseTextForNonPassedInsights = '';
3155
- // We still return info on the passed insights, but we put it at the
3156
- // bottom of the response under a heading.
3157
- let responseTextForPassedInsights = '';
3158
-
3159
- // TODO(b/442392194): use PerformanceTraceFormatter summary instead.
3160
- for (const insight of Object.values(insightsForNav.model)) {
3161
- const focus = AiAssistanceModel.AIContext.AgentFocus.fromParsedTrace(parsedTrace);
3162
- const formatter = new AiAssistanceModel.PerformanceInsightFormatter.PerformanceInsightFormatter(focus, insight);
3163
- if (!formatter.insightIsSupported()) {
3164
- // Not all Insights are integrated with "Ask AI" yet, let's avoid
3165
- // filling up the response with those ones because there will be no
3166
- // useful information.
3167
- continue;
3168
- }
3169
-
3170
- const formatted = formatter.formatInsight({headingLevel: 3});
3171
-
3172
- if (insight.state === 'pass') {
3173
- responseTextForPassedInsights += `${formatted}\n\n`;
3174
- continue;
3175
- } else {
3176
- responseTextForNonPassedInsights += `${formatted}\n\n`;
3177
- }
3178
- }
3179
-
3180
- const finalText = `# Trace recording results
3181
-
3182
- ## Non-passing insights:
3183
-
3184
- These insights highlight potential problems and opportunities to improve performance.
3185
- ${responseTextForNonPassedInsights}
3186
-
3187
- ## Passing insights:
3188
-
3189
- These insights are passing, which means they are not considered to highlight considerable performance problems.
3190
- ${responseTextForPassedInsights}`;
3191
-
3192
- return {
3193
- type: AiAssistanceModel.AiAgent.ExternalRequestResponseType.ANSWER,
3194
- message: finalText,
3195
- devToolsLogs: [],
3196
- };
3197
- }
3198
-
3199
- return await new Promise(resolve => {
3200
- function listener(e: Common.EventTarget.EventTargetEvent<EventTypes[Events.RECORDING_COMPLETED]>): void {
3201
- resolve(onRecordingCompleted(e.data));
3202
- panelInstance.removeEventListener(Events.RECORDING_COMPLETED, listener);
3203
- }
3204
- panelInstance.addEventListener(Events.RECORDING_COMPLETED, listener);
3205
-
3206
- panelInstance.recordReload();
3207
- });
3208
- }
3209
-
3210
- static async handleExternalAnalyzeRequest(prompt: string): Promise<AsyncGenerator<
3211
- AiAssistanceModel.AiAgent.ExternalRequestResponse, AiAssistanceModel.AiAgent.ExternalRequestResponse>> {
3212
- const data = TimelinePanel.instance().getOrCreateExternalAIConversationData();
3213
- return await data.conversationHandler.handleExternalRequest({
3214
- conversationType: AiAssistanceModel.AiHistoryStorage.ConversationType.PERFORMANCE,
3215
- prompt,
3216
- data,
3217
- });
3218
- }
3219
3066
  }
3220
3067
 
3221
3068
  export const enum State {
@@ -200,6 +200,7 @@ export class TimelineTreeView extends
200
200
  // Compact mode is used to render the tree view in a more compact UI,
201
201
  // suitable for AI assistance widgets. It removes sidebars and toolbars.
202
202
  #compactMode = false;
203
+ #maxLinkLength: number|undefined = undefined;
203
204
 
204
205
  /**
205
206
  * Determines if the first child in the data grid will be selected
@@ -272,6 +273,14 @@ export class TimelineTreeView extends
272
273
  }
273
274
  }
274
275
 
276
+ get maxLinkLength(): number|undefined {
277
+ return this.#maxLinkLength;
278
+ }
279
+
280
+ set maxLinkLength(maxLinkLength: number|undefined) {
281
+ this.#maxLinkLength = maxLinkLength;
282
+ }
283
+
275
284
  #applyCompactMode(): void {
276
285
  if (this.#compactMode && this.dataGrid) {
277
286
  this.splitWidget?.detach();
@@ -900,7 +909,8 @@ export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<Gri
900
909
  const linkifier = this.treeView.linkifier;
901
910
  const isFreshOrEnhanced =
902
911
  Boolean(parsedTrace && Tracing.FreshRecording.Tracker.instance().recordingIsFreshOrEnhanced(parsedTrace));
903
- this.linkElement = TimelineUIUtils.linkifyTopCallFrame(event, target, linkifier, isFreshOrEnhanced);
912
+ const maxLength = this.treeView.maxLinkLength;
913
+ this.linkElement = TimelineUIUtils.linkifyTopCallFrame(event, target, linkifier, isFreshOrEnhanced, maxLength);
904
914
  if (this.linkElement) {
905
915
  container.createChild('div', 'activity-link').appendChild(this.linkElement);
906
916
  }
@@ -785,7 +785,7 @@ export class TimelineUIUtils {
785
785
 
786
786
  static linkifyTopCallFrame(
787
787
  event: Trace.Types.Events.Event, target: SDK.Target.Target|null, linkifier: LegacyComponents.Linkifier.Linkifier,
788
- isFreshOrEnhanced = false): Element|null {
788
+ isFreshOrEnhanced = false, maxLength?: number): Element|null {
789
789
  let frame = Trace.Helpers.Trace.getZeroIndexedStackTraceInEventPayload(event)?.[0];
790
790
  if (Trace.Types.Events.isProfileCall(event)) {
791
791
  frame = event.callFrame;
@@ -800,6 +800,7 @@ export class TimelineUIUtils {
800
800
  showColumnNumber: true,
801
801
  columnNumber: frame.columnNumber,
802
802
  lineNumber: frame.lineNumber,
803
+ maxLength,
803
804
  };
804
805
  if (isFreshOrEnhanced) {
805
806
  return linkifier.maybeLinkifyConsoleCallFrame(target, frame, {showColumnNumber: true, inlineFrameIndex: 0});
@@ -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: c9e49d7cdbbc11a6a70bb2573f5a18cb23c1789b
4
+ Revision: ed66cb5e93e6205f5231c1dcfac9b91b2aa4ffd6
5
5
  Update Mechanism: Manual (https://crbug.com/428069060)
6
6
  License: BSD-3-Clause
7
7
  License File: LICENSE
@@ -34,6 +34,11 @@ const UIStrings = {
34
34
  * @description Disclaimer shown in the code blocks.
35
35
  */
36
36
  disclaimer: 'Use code snippets with caution',
37
+ /**
38
+ * @description The title of the button to show all lines of a code block.
39
+ * @example {5} PH1
40
+ */
41
+ showAllLines: 'Show all lines ({PH1} more)',
37
42
  } as const;
38
43
  const str_ = i18n.i18n.registerUIStrings('ui/components/markdown_view/CodeBlock.ts', UIStrings);
39
44
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -130,6 +135,7 @@ export class CodeBlock extends HTMLElement {
130
135
  #copied = false;
131
136
  #editorState?: CodeMirror.EditorState;
132
137
  #languageConf = new CodeMirror.Compartment();
138
+ #truncationConf = new CodeMirror.Compartment();
133
139
  /**
134
140
  * Whether to display a notice "​​Use code snippets with caution" in code
135
141
  * blocks.
@@ -138,6 +144,7 @@ export class CodeBlock extends HTMLElement {
138
144
  #header?: string;
139
145
  #showCopyButton = true;
140
146
  #citations: Citation[] = [];
147
+ #displayLimit = Number.MAX_VALUE;
141
148
 
142
149
  connectedCallback(): void {
143
150
  void this.#render();
@@ -152,6 +159,7 @@ export class CodeBlock extends HTMLElement {
152
159
  CodeMirror.EditorState.readOnly.of(true),
153
160
  CodeMirror.EditorView.lineWrapping,
154
161
  this.#languageConf.of(CodeMirror.javascript.javascript()),
162
+ this.#truncationConf.of([]),
155
163
  ],
156
164
  });
157
165
  void this.#render();
@@ -190,6 +198,15 @@ export class CodeBlock extends HTMLElement {
190
198
  this.#citations = citations;
191
199
  }
192
200
 
201
+ set displayLimit(value: number) {
202
+ this.#displayLimit = value;
203
+ void this.#render();
204
+ }
205
+
206
+ get displayLimit(): number {
207
+ return this.#displayLimit;
208
+ }
209
+
193
210
  #onCopy(): void {
194
211
  UI.UIUtils.copyTextToClipboard(this.#code, i18nString(UIStrings.copied));
195
212
  this.#copied = true;
@@ -256,6 +273,9 @@ export class CodeBlock extends HTMLElement {
256
273
  throw new Error('Unexpected: trying to render the text editor without editorState');
257
274
  }
258
275
 
276
+ const linesCount = this.#editorState.doc.lines;
277
+ const isTruncated = linesCount > this.#displayLimit;
278
+
259
279
  // clang-format off
260
280
  Lit.render(
261
281
  html`<div class='codeblock' jslog=${VisualLogging.section('code')}>
@@ -271,6 +291,19 @@ export class CodeBlock extends HTMLElement {
271
291
  <div class="code">
272
292
  <devtools-text-editor .state=${this.#editorState}></devtools-text-editor>
273
293
  </div>
294
+ ${isTruncated ? html`
295
+ <div class="show-all-container">
296
+ <devtools-button
297
+ .variant=${Buttons.Button.Variant.OUTLINED}
298
+ .size=${Buttons.Button.Size.SMALL}
299
+ .jslogContext=${'show-all'}
300
+ .title=${i18nString(UIStrings.showAllLines, { PH1: linesCount - this.#displayLimit })}
301
+ @click=${() => {
302
+ this.displayLimit = Number.MAX_VALUE;
303
+ }}
304
+ >${i18nString(UIStrings.showAllLines, { PH1: linesCount - this.#displayLimit })}</devtools-button>
305
+ </div>
306
+ ` : Lit.nothing}
274
307
  </div>
275
308
  ${this.#displayNotice ? this.#renderNotice() : Lit.nothing}
276
309
  </div>`,
@@ -288,8 +321,21 @@ export class CodeBlock extends HTMLElement {
288
321
  }
289
322
 
290
323
  const language = await languageFromToken(this.#codeLang);
324
+ let truncationExtension: CodeMirror.Extension = [];
325
+ if (isTruncated) {
326
+ truncationExtension = CodeMirror.EditorView.decorations.of(CodeMirror.Decoration.set(
327
+ CodeMirror.Decoration.replace({}).range(
328
+ this.#editorState.doc.line(this.#displayLimit).to,
329
+ this.#editorState.doc.length,
330
+ ),
331
+ ));
332
+ }
333
+
291
334
  editor.dispatch({
292
- effects: this.#languageConf.reconfigure(language),
335
+ effects: [
336
+ this.#languageConf.reconfigure(language),
337
+ this.#truncationConf.reconfigure(truncationExtension),
338
+ ],
293
339
  });
294
340
  }
295
341
  }
@@ -108,3 +108,11 @@
108
108
  text-decoration-line: underline;
109
109
  }
110
110
  }
111
+
112
+ .show-all-container {
113
+ display: flex;
114
+ justify-content: center;
115
+ align-items: center;
116
+ padding: var(--sys-size-4) 0;
117
+ background-color: var(--code-block-background-color); /* stylelint-disable-line plugin/use_theme_colors */
118
+ }
@@ -4,8 +4,6 @@
4
4
 
5
5
  /* eslint-disable @devtools/no-imperative-dom-api, @devtools/no-lit-render-outside-of-view */
6
6
 
7
- import './Toolbar.js';
8
-
9
7
  import * as Common from '../../core/common/common.js';
10
8
  import * as i18n from '../../core/i18n/i18n.js';
11
9
  import * as Platform from '../../core/platform/platform.js';
@@ -22,7 +20,7 @@ import tabbedPaneStyles from './tabbedPane.css.js';
22
20
  import type {Toolbar} from './Toolbar.js';
23
21
  import {Tooltip} from './Tooltip.js';
24
22
  import {installDragHandle} from './UIUtils.js';
25
- import {VBox, type Widget} from './Widget.js';
23
+ import {registerWidgetConfig, VBox, Widget, widgetConfig, WidgetElement} from './Widget.js';
26
24
  import {Events as ZoomManagerEvents, ZoomManager} from './ZoomManager.js';
27
25
 
28
26
  const UIStrings = {
@@ -123,7 +121,26 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
123
121
  this.contentElement.tabIndex = -1;
124
122
  this.setDefaultFocusedElement(this.contentElement);
125
123
  this.#headerElement = this.contentElement.createChild('div', 'tabbed-pane-header');
124
+ const leftSlot = document.createElement('slot');
125
+ leftSlot.name = 'left';
126
+ leftSlot.classList.add('tabbed-pane-left-toolbar');
127
+ this.#headerElement.appendChild(leftSlot);
128
+ leftSlot.addEventListener('slotchange', () => {
129
+ this.#leftToolbar = leftSlot.assignedElements()[0] as Toolbar | undefined;
130
+ this.requestUpdate();
131
+ });
132
+
126
133
  this.headerContentsElement = this.#headerElement.createChild('div', 'tabbed-pane-header-contents');
134
+
135
+ const rightSlot = document.createElement('slot');
136
+ rightSlot.name = 'right';
137
+ rightSlot.classList.add('tabbed-pane-right-toolbar');
138
+ this.#headerElement.appendChild(rightSlot);
139
+ rightSlot.addEventListener('slotchange', () => {
140
+ this.#rightToolbar = rightSlot.assignedElements()[0] as Toolbar | undefined;
141
+ this.requestUpdate();
142
+ });
143
+
127
144
  this.tabSlider = document.createElement('div');
128
145
  this.tabSlider.classList.add('tabbed-pane-tab-slider');
129
146
  this.tabsElement = this.headerContentsElement.createChild('div', 'tabbed-pane-header-tabs');
@@ -547,6 +564,9 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
547
564
  if (existingTab) {
548
565
  this.changeTabView(tab.id, tab.view);
549
566
  this.changeTabTitle(tab.id, tab.title, tab.tabTooltip);
567
+ if (tab.jslogContext !== undefined) {
568
+ existingTab.jslogContext = tab.jslogContext;
569
+ }
550
570
  if (tab.isCloseable !== undefined) {
551
571
  existingTab.closeable = tab.isCloseable;
552
572
  }
@@ -1031,6 +1051,10 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
1031
1051
  }
1032
1052
 
1033
1053
  leftToolbar(): Toolbar {
1054
+ if (!this.#leftToolbar) {
1055
+ const leftSlot = this.#headerElement.querySelector('slot[name="left"]') as HTMLSlotElement | null;
1056
+ this.#leftToolbar = leftSlot?.assignedElements()[0] as Toolbar | undefined;
1057
+ }
1034
1058
  if (!this.#leftToolbar) {
1035
1059
  this.#leftToolbar = document.createElement('devtools-toolbar');
1036
1060
  this.#leftToolbar.classList.add('tabbed-pane-left-toolbar');
@@ -1040,6 +1064,10 @@ export class TabbedPane extends Common.ObjectWrapper.eventMixin<EventTypes, type
1040
1064
  }
1041
1065
 
1042
1066
  rightToolbar(): Toolbar {
1067
+ if (!this.#rightToolbar) {
1068
+ const rightSlot = this.#headerElement.querySelector('slot[name="right"]') as HTMLSlotElement | null;
1069
+ this.#rightToolbar = rightSlot?.assignedElements()[0] as Toolbar | undefined;
1070
+ }
1043
1071
  if (!this.#rightToolbar) {
1044
1072
  this.#rightToolbar = document.createElement('devtools-toolbar');
1045
1073
  this.#rightToolbar.classList.add('tabbed-pane-right-toolbar');
@@ -1191,6 +1219,10 @@ export class TabbedPaneTab {
1191
1219
  return this.#jslogContext ?? (this.#id === 'console-view' ? 'console' : this.#id);
1192
1220
  }
1193
1221
 
1222
+ set jslogContext(jslogContext: string|undefined) {
1223
+ this.#jslogContext = jslogContext;
1224
+ }
1225
+
1194
1226
  get tabAnnotationIcon(): boolean {
1195
1227
  return this.#tabAnnotationIcon;
1196
1228
  }
@@ -1614,3 +1646,91 @@ export interface TabbedPaneTabDelegate {
1614
1646
  closeTabs(tabbedPane: TabbedPane, ids: string[]): void;
1615
1647
  onContextMenu(tabId: string, contextMenu: ContextMenu): void;
1616
1648
  }
1649
+
1650
+ export class TabbedPaneElement extends WidgetElement<TabbedPane> {
1651
+ readonly #tabObserver = new MutationObserver(() => this.#updateTabs());
1652
+
1653
+ constructor() {
1654
+ super();
1655
+
1656
+ registerWidgetConfig(this, widgetConfig(element => {
1657
+ const widget = new TabbedPane(element as TabbedPaneElement);
1658
+ const slot = widget.contentElement.querySelector('slot:not([name])');
1659
+ if (slot) {
1660
+ slot.addEventListener('slotchange', () => this.#syncTabs());
1661
+ }
1662
+ widget.addEventListener(Events.TabSelected, () => {
1663
+ const slot =
1664
+ widget.contentElement.querySelector('slot:not([name])') as HTMLSlotElement | null;
1665
+ const nodes = slot ? slot.assignedElements() : [];
1666
+ for (const child of nodes) {
1667
+ if (child.id === widget.selectedTabId) {
1668
+ child.setAttribute('selected', '');
1669
+ } else {
1670
+ child.removeAttribute('selected');
1671
+ }
1672
+ }
1673
+ });
1674
+ this.#syncTabs(widget);
1675
+ return widget;
1676
+ }));
1677
+ }
1678
+
1679
+ override disconnectedCallback(): void {
1680
+ super.disconnectedCallback();
1681
+ this.#tabObserver.disconnect();
1682
+ }
1683
+
1684
+ #syncTabs(widget = this.getWidget()): void {
1685
+ if (!widget) {
1686
+ return;
1687
+ }
1688
+ this.#updateObserver(widget);
1689
+ this.#updateTabs(widget);
1690
+ }
1691
+
1692
+ #updateObserver(widget: TabbedPane): void {
1693
+ this.#tabObserver.disconnect();
1694
+ const slot = widget.contentElement.querySelector('slot:not([name])') as HTMLSlotElement | null;
1695
+ const nodes = slot ? slot.assignedElements() : [];
1696
+ for (const child of nodes) {
1697
+ this.#tabObserver.observe(
1698
+ child, {attributes: true, attributeFilter: ['title', 'jslogcontext', 'selected', 'disabled']});
1699
+ }
1700
+ }
1701
+
1702
+ #updateTabs(widget = this.getWidget()): void {
1703
+ if (!widget) {
1704
+ return;
1705
+ }
1706
+ const tabs: TabInfo[] = [];
1707
+ const slot = widget.contentElement.querySelector('slot:not([name])') as HTMLSlotElement | null;
1708
+ const nodes = slot ? slot.assignedElements() : [];
1709
+ for (const child of nodes) {
1710
+ const id = child.id;
1711
+ const title = child.getAttribute('title') || '';
1712
+ const jslogContext = child.getAttribute('jslogcontext') || undefined;
1713
+ const selected = child.hasAttribute('selected');
1714
+ const enabled = !child.hasAttribute('disabled');
1715
+ const view = Widget.getOrCreateWidget(child as HTMLElement);
1716
+ view.setHideOnDetach();
1717
+ if (widget.selectedTabId !== id) {
1718
+ view.hideWidget();
1719
+ } else {
1720
+ view.showWidget();
1721
+ }
1722
+ tabs.push({
1723
+ id,
1724
+ title,
1725
+ view,
1726
+ jslogContext,
1727
+ selected,
1728
+ enabled,
1729
+ });
1730
+ }
1731
+
1732
+ widget.tabs = tabs;
1733
+ }
1734
+ }
1735
+
1736
+ customElements.define('devtools-tabbed-pane', TabbedPaneElement);