chrome-devtools-frontend 1.0.1001476 → 1.0.1003469

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 (88) hide show
  1. package/config/gni/devtools_grd_files.gni +5 -6
  2. package/front_end/core/common/ParsedURL.ts +3 -3
  3. package/front_end/core/host/InspectorFrontendHost.ts +30 -1
  4. package/front_end/core/host/InspectorFrontendHostAPI.ts +1 -0
  5. package/front_end/core/host/UserMetrics.ts +17 -1
  6. package/front_end/core/i18n/locales/en-US.json +24 -63
  7. package/front_end/core/i18n/locales/en-XL.json +24 -63
  8. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  9. package/front_end/core/root/Runtime.ts +7 -3
  10. package/front_end/core/sdk/ServiceWorkerManager.ts +6 -5
  11. package/front_end/core/sdk/TracingModel.ts +5 -4
  12. package/front_end/devtools_compatibility.js +1 -0
  13. package/front_end/entrypoints/formatter_worker/FormatterActions.ts +14 -0
  14. package/front_end/entrypoints/formatter_worker/ScopeParser.ts +491 -0
  15. package/front_end/entrypoints/formatter_worker/Substitute.ts +4 -440
  16. package/front_end/entrypoints/formatter_worker/formatter_worker.ts +2 -0
  17. package/front_end/entrypoints/main/MainImpl.ts +1 -0
  18. package/front_end/generated/InspectorBackendCommands.js +42 -12
  19. package/front_end/generated/SupportedCSSProperties.js +3 -5
  20. package/front_end/generated/protocol-mapping.d.ts +5 -1
  21. package/front_end/generated/protocol-proxy-api.d.ts +4 -1
  22. package/front_end/generated/protocol.ts +68 -14
  23. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  24. package/front_end/models/issues_manager/AttributionReportingIssue.ts +6 -34
  25. package/front_end/models/issues_manager/DeprecationIssue.ts +84 -172
  26. package/front_end/models/issues_manager/Issue.ts +8 -4
  27. package/front_end/models/issues_manager/descriptions/arInvalidHeader.md +3 -0
  28. package/front_end/models/persistence/NetworkPersistenceManager.ts +20 -0
  29. package/front_end/models/timeline_model/TimelineModel.ts +2 -49
  30. package/front_end/panels/application/AppManifestView.ts +3 -3
  31. package/front_end/panels/application/ApplicationPanelCacheSection.ts +3 -1
  32. package/front_end/panels/application/ApplicationPanelSidebar.ts +11 -6
  33. package/front_end/panels/application/ApplicationPanelTreeElement.ts +2 -2
  34. package/front_end/panels/application/BackgroundServiceView.ts +5 -4
  35. package/front_end/panels/application/ResourcesPanel.ts +1 -1
  36. package/front_end/panels/console/ConsoleViewMessage.ts +6 -3
  37. package/front_end/panels/css_overview/components/CSSOverviewStartView.ts +3 -2
  38. package/front_end/panels/elements/StylePropertyTreeElement.ts +19 -13
  39. package/front_end/panels/elements/StylesSidebarPane.ts +74 -5
  40. package/front_end/panels/elements/stylesSidebarPane.css +3 -0
  41. package/front_end/panels/issues/AffectedResourcesView.ts +4 -3
  42. package/front_end/panels/issues/AffectedSourcesView.ts +2 -1
  43. package/front_end/panels/issues/AttributionReportingIssueDetailsView.ts +9 -38
  44. package/front_end/panels/issues/IssueView.ts +1 -1
  45. package/front_end/panels/lighthouse/LighthouseController.ts +6 -3
  46. package/front_end/panels/lighthouse/LighthouseReporterTypes.ts +2 -1
  47. package/front_end/panels/lighthouse/lighthousePanel.css +4 -0
  48. package/front_end/panels/network/NetworkLogView.ts +32 -0
  49. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +2 -2
  50. package/front_end/panels/profiler/HeapSnapshotView.ts +2 -1
  51. package/front_end/panels/profiler/ProfileDataGrid.ts +1 -1
  52. package/front_end/panels/security/SecurityPanel.ts +6 -5
  53. package/front_end/panels/settings/SettingsScreen.ts +2 -3
  54. package/front_end/panels/sources/SourcesNavigator.ts +1 -1
  55. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  56. package/front_end/panels/timeline/PerformanceModel.ts +2 -6
  57. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +5 -26
  58. package/front_end/panels/timeline/TimelineUIUtils.ts +14 -12
  59. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +1036 -1088
  60. package/front_end/third_party/lighthouse/locales/en-US.json +244 -4
  61. package/front_end/third_party/lighthouse/locales/en-XL.json +244 -4
  62. package/front_end/third_party/lighthouse/report/bundle.d.ts +4 -22
  63. package/front_end/third_party/lighthouse/report/bundle.js +23 -366
  64. package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +1 -1
  65. package/front_end/ui/components/docs/linkifier/simple-url.ts +2 -1
  66. package/front_end/ui/components/docs/panel_feedback/basic.ts +3 -2
  67. package/front_end/ui/components/docs/panel_feedback/button.ts +2 -1
  68. package/front_end/ui/components/linkifier/LinkifierImpl.ts +4 -3
  69. package/front_end/ui/components/linkifier/LinkifierUtils.ts +2 -3
  70. package/front_end/ui/components/panel_feedback/FeedbackButton.ts +4 -6
  71. package/front_end/ui/components/panel_feedback/PanelFeedback.ts +5 -4
  72. package/front_end/ui/components/request_link_icon/RequestLinkIcon.ts +3 -3
  73. package/front_end/ui/components/text_editor/javascript.ts +6 -15
  74. package/front_end/ui/legacy/EmptyWidget.ts +2 -1
  75. package/front_end/ui/legacy/InspectorView.ts +29 -0
  76. package/front_end/ui/legacy/UIUtils.ts +4 -4
  77. package/front_end/ui/legacy/XLink.ts +12 -13
  78. package/front_end/ui/legacy/components/color_picker/ContrastDetails.ts +2 -4
  79. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +1 -1
  80. package/front_end/ui/legacy/components/perf_ui/LineLevelProfile.ts +0 -2
  81. package/front_end/ui/legacy/components/utils/ImagePreview.ts +3 -7
  82. package/front_end/ui/legacy/components/utils/Linkifier.ts +23 -23
  83. package/front_end/ui/legacy/toolbar.css +1 -1
  84. package/package.json +1 -1
  85. package/scripts/whitespaces.txt +1 -0
  86. package/front_end/models/issues_manager/descriptions/arInvalidAttributionSourceEventId.md +0 -3
  87. package/front_end/models/issues_manager/descriptions/arInvalidAttributionSourceExpiry.md +0 -4
  88. package/front_end/models/issues_manager/descriptions/arInvalidAttributionSourcePriority.md +0 -4
@@ -98,7 +98,6 @@ export class TimelineModelImpl {
98
98
  private lastRecalculateStylesEvent!: SDK.TracingModel.Event|null;
99
99
  private currentScriptEvent!: SDK.TracingModel.Event|null;
100
100
  private eventStack!: SDK.TracingModel.Event[];
101
- private knownInputEvents!: Set<string>;
102
101
  private browserFrameTracking!: boolean;
103
102
  private persistentIds!: boolean;
104
103
  private legacyCurrentPage!: any;
@@ -226,7 +225,8 @@ export class TimelineModelImpl {
226
225
  }
227
226
 
228
227
  isMainFrameNavigationStartEvent(event: SDK.TracingModel.Event): boolean {
229
- return this.isNavigationStartEvent(event) && event.args['data']['isLoadingMainFrame'] &&
228
+ return this.isNavigationStartEvent(event) &&
229
+ (event.args['data']['isOutermostMainFrame'] ?? event.args['data']['isLoadingMainFrame']) &&
230
230
  event.args['data']['documentLoaderURL'];
231
231
  }
232
232
 
@@ -573,7 +573,6 @@ export class TimelineModelImpl {
573
573
  this.lastRecalculateStylesEvent = null;
574
574
  this.currentScriptEvent = null;
575
575
  this.eventStack = [];
576
- this.knownInputEvents = new Set();
577
576
  this.browserFrameTracking = false;
578
577
  this.persistentIds = false;
579
578
  this.legacyCurrentPage = null;
@@ -833,44 +832,6 @@ export class TimelineModelImpl {
833
832
  group(TrackType.Animation).push(asyncEvent);
834
833
  continue;
835
834
  }
836
-
837
- if (asyncEvent.hasCategory(TimelineModelImpl.Category.LatencyInfo) ||
838
- asyncEvent.name === RecordType.ImplSideFling) {
839
- const lastStep = asyncEvent.steps[asyncEvent.steps.length - 1];
840
- if (!lastStep) {
841
- throw new Error('AsyncEvent.steps access is out of bounds.');
842
- }
843
- if (lastStep.phase !== SDK.TracingModel.Phase.NestableAsyncEnd) {
844
- continue;
845
- }
846
- const chromeLatencyInfo = asyncEvent.args['chrome_latency_info'];
847
-
848
- const data = chromeLatencyInfo?.['component_info'];
849
- asyncEvent.causedFrame =
850
- Boolean(data?.some((c: any) => c['component_type'] === 'COMPONENT_INPUT_EVENT_LATENCY_RENDERER_SWAP'));
851
- if (asyncEvent.hasCategory(TimelineModelImpl.Category.LatencyInfo)) {
852
- if (lastStep.id && !this.knownInputEvents.has(chromeLatencyInfo.trace_id)) {
853
- continue;
854
- }
855
- if (asyncEvent.name === RecordType.InputLatencyMouseMove && !asyncEvent.causedFrame) {
856
- continue;
857
- }
858
- // Coalesced events are not really been processed, no need to track them.
859
- if (!data || data['is_coalesced']) {
860
- continue;
861
- }
862
-
863
- const rendererMain =
864
- data?.find((c: any) => c['component_type'] === 'COMPONENT_INPUT_EVENT_LATENCY_RENDERER_MAIN');
865
- if (rendererMain) {
866
- const time = rendererMain['time_us'] / 1000;
867
- TimelineData.forEvent(asyncEvent.steps[0]).timeWaitingForMainThread =
868
- time - asyncEvent.steps[0].startTime;
869
- }
870
- }
871
- group(TrackType.Input).push(asyncEvent);
872
- continue;
873
- }
874
835
  }
875
836
  }
876
837
 
@@ -1210,13 +1171,6 @@ export class TimelineModelImpl {
1210
1171
  }
1211
1172
 
1212
1173
  private processBrowserEvent(event: SDK.TracingModel.Event): void {
1213
- if (event.name === RecordType.LatencyInfoFlow) {
1214
- if (event.args.chrome_latency_info?.trace_id) {
1215
- this.knownInputEvents.add(event.args.chrome_latency_info.trace_id);
1216
- }
1217
- return;
1218
- }
1219
-
1220
1174
  if (event.name === RecordType.ResourceWillSendRequest) {
1221
1175
  const requestId = event.args?.data?.requestId;
1222
1176
  if (typeof requestId === 'string') {
@@ -1715,7 +1669,6 @@ export class Track {
1715
1669
  export enum TrackType {
1716
1670
  MainThread = 'MainThread',
1717
1671
  Worker = 'Worker',
1718
- Input = 'Input',
1719
1672
  Animation = 'Animation',
1720
1673
  Timings = 'Timings',
1721
1674
  Console = 'Console',
@@ -433,7 +433,7 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager
433
433
  .addChangeListener(this.updateManifest.bind(this, true));
434
434
 
435
435
  this.emptyView = new UI.EmptyWidget.EmptyWidget(i18nString(UIStrings.noManifestDetected));
436
- this.emptyView.appendLink('https://web.dev/add-manifest/');
436
+ this.emptyView.appendLink('https://web.dev/add-manifest/' as Platform.DevToolsPath.UrlString);
437
437
 
438
438
  this.emptyView.show(this.contentElement);
439
439
  this.emptyView.hideWidget();
@@ -693,7 +693,7 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager
693
693
  this.newNoteUrlField.parentElement?.classList.toggle('hidden', !hasNewNoteUrl);
694
694
  this.newNoteUrlField.removeChildren();
695
695
  if (hasNewNoteUrl) {
696
- const completeURL = (Common.ParsedURL.ParsedURL.completeURL(url, newNoteUrl) as string);
696
+ const completeURL = (Common.ParsedURL.ParsedURL.completeURL(url, newNoteUrl) as Platform.DevToolsPath.UrlString);
697
697
  const link = Components.Linkifier.Linkifier.linkifyURL(
698
698
  completeURL, ({text: newNoteUrl} as Components.Linkifier.LinkifyURLOptions));
699
699
  link.tabIndex = 0;
@@ -763,7 +763,7 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager
763
763
  shortcutSection.appendFlexedField('Description', shortcut.description);
764
764
  }
765
765
  const urlField = shortcutSection.appendFlexedField('URL');
766
- const shortcutUrl = (Common.ParsedURL.ParsedURL.completeURL(url, shortcut.url) as string);
766
+ const shortcutUrl = Common.ParsedURL.ParsedURL.completeURL(url, shortcut.url) as Platform.DevToolsPath.UrlString;
767
767
  const link = Components.Linkifier.Linkifier.linkifyURL(
768
768
  shortcutUrl, ({text: shortcut.url} as Components.Linkifier.LinkifyURLOptions));
769
769
  link.tabIndex = 0;
@@ -41,7 +41,9 @@ export class ServiceWorkerCacheTreeElement extends ExpandableApplicationPanelTre
41
41
  constructor(resourcesPanel: ResourcesPanel) {
42
42
  super(resourcesPanel, i18nString(UIStrings.cacheStorage), 'CacheStorage');
43
43
  const icon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item');
44
- this.setLink('https://developer.chrome.com/docs/devtools/storage/cache/?utm_source=devtools');
44
+ this.setLink(
45
+ 'https://developer.chrome.com/docs/devtools/storage/cache/?utm_source=devtools' as
46
+ Platform.DevToolsPath.UrlString);
45
47
  this.setLeadingIcons([icon]);
46
48
  this.swCacheModel = null;
47
49
  this.swCacheTreeElements = new Set();
@@ -243,7 +243,8 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
243
243
  this.localStorageListTreeElement =
244
244
  new ExpandableApplicationPanelTreeElement(panel, i18nString(UIStrings.localStorage), 'LocalStorage');
245
245
  this.localStorageListTreeElement.setLink(
246
- 'https://developer.chrome.com/docs/devtools/storage/localstorage/?utm_source=devtools');
246
+ 'https://developer.chrome.com/docs/devtools/storage/localstorage/?utm_source=devtools' as
247
+ Platform.DevToolsPath.UrlString);
247
248
  const localStorageIcon = UI.Icon.Icon.create('mediumicon-table', 'resource-tree-item');
248
249
  this.localStorageListTreeElement.setLeadingIcons([localStorageIcon]);
249
250
 
@@ -251,19 +252,22 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
251
252
  this.sessionStorageListTreeElement =
252
253
  new ExpandableApplicationPanelTreeElement(panel, i18nString(UIStrings.sessionStorage), 'SessionStorage');
253
254
  this.sessionStorageListTreeElement.setLink(
254
- 'https://developer.chrome.com/docs/devtools/storage/sessionstorage/?utm_source=devtools');
255
+ 'https://developer.chrome.com/docs/devtools/storage/sessionstorage/?utm_source=devtools' as
256
+ Platform.DevToolsPath.UrlString);
255
257
  const sessionStorageIcon = UI.Icon.Icon.create('mediumicon-table', 'resource-tree-item');
256
258
  this.sessionStorageListTreeElement.setLeadingIcons([sessionStorageIcon]);
257
259
 
258
260
  storageTreeElement.appendChild(this.sessionStorageListTreeElement);
259
261
  this.indexedDBListTreeElement = new IndexedDBTreeElement(panel);
260
262
  this.indexedDBListTreeElement.setLink(
261
- 'https://developer.chrome.com/docs/devtools/storage/indexeddb/?utm_source=devtools');
263
+ 'https://developer.chrome.com/docs/devtools/storage/indexeddb/?utm_source=devtools' as
264
+ Platform.DevToolsPath.UrlString);
262
265
  storageTreeElement.appendChild(this.indexedDBListTreeElement);
263
266
  this.databasesListTreeElement =
264
267
  new ExpandableApplicationPanelTreeElement(panel, i18nString(UIStrings.webSql), 'Databases');
265
268
  this.databasesListTreeElement.setLink(
266
- 'https://developer.chrome.com/docs/devtools/storage/websql/?utm_source=devtools');
269
+ 'https://developer.chrome.com/docs/devtools/storage/websql/?utm_source=devtools' as
270
+ Platform.DevToolsPath.UrlString);
267
271
  const databaseIcon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item');
268
272
  this.databasesListTreeElement.setLeadingIcons([databaseIcon]);
269
273
 
@@ -271,7 +275,8 @@ export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.Targe
271
275
  this.cookieListTreeElement =
272
276
  new ExpandableApplicationPanelTreeElement(panel, i18nString(UIStrings.cookies), 'Cookies');
273
277
  this.cookieListTreeElement.setLink(
274
- 'https://developer.chrome.com/docs/devtools/storage/cookies/?utm_source=devtools');
278
+ 'https://developer.chrome.com/docs/devtools/storage/cookies/?utm_source=devtools' as
279
+ Platform.DevToolsPath.UrlString);
275
280
  const cookieIcon = UI.Icon.Icon.create('mediumicon-cookie', 'resource-tree-item');
276
281
  this.cookieListTreeElement.setLeadingIcons([cookieIcon]);
277
282
  storageTreeElement.appendChild(this.cookieListTreeElement);
@@ -1505,7 +1510,7 @@ export class StorageCategoryView extends UI.Widget.VBox {
1505
1510
  this.emptyWidget.text = text;
1506
1511
  }
1507
1512
 
1508
- setLink(link: string|null): void {
1513
+ setLink(link: Platform.DevToolsPath.UrlString|null): void {
1509
1514
  if (link && !this.linkElement) {
1510
1515
  this.linkElement = this.emptyWidget.appendLink(link);
1511
1516
  }
@@ -47,7 +47,7 @@ export class ApplicationPanelTreeElement extends UI.TreeOutline.TreeElement {
47
47
  export class ExpandableApplicationPanelTreeElement extends ApplicationPanelTreeElement {
48
48
  protected readonly expandedSetting: Common.Settings.Setting<boolean>;
49
49
  protected readonly categoryName: string;
50
- protected categoryLink: string|null;
50
+ protected categoryLink: Platform.DevToolsPath.UrlString|null;
51
51
 
52
52
  constructor(resourcesPanel: ResourcesPanel, categoryName: string, settingsKey: string, settingsDefault = false) {
53
53
  super(resourcesPanel, categoryName, false);
@@ -61,7 +61,7 @@ export class ExpandableApplicationPanelTreeElement extends ApplicationPanelTreeE
61
61
  return 'category://' + this.categoryName as Platform.DevToolsPath.UrlString;
62
62
  }
63
63
 
64
- setLink(link: string): void {
64
+ setLink(link: Platform.DevToolsPath.UrlString): void {
65
65
  this.categoryLink = link;
66
66
  }
67
67
 
@@ -2,17 +2,18 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
- // eslint-disable-next-line rulesdir/es_modules_import
6
- import emptyWidgetStyles from '../../ui/legacy/emptyWidget.css.js';
7
- import backgroundServiceViewStyles from './backgroundServiceView.css.js';
8
5
  import type * as Common from '../../core/common/common.js';
9
6
  import * as i18n from '../../core/i18n/i18n.js';
10
7
  import * as Platform from '../../core/platform/platform.js';
11
8
  import * as SDK from '../../core/sdk/sdk.js';
9
+ import * as Protocol from '../../generated/protocol.js';
12
10
  import * as Bindings from '../../models/bindings/bindings.js';
13
11
  import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js';
12
+ // eslint-disable-next-line rulesdir/es_modules_import
13
+ import emptyWidgetStyles from '../../ui/legacy/emptyWidget.css.js';
14
14
  import * as UI from '../../ui/legacy/legacy.js';
15
- import * as Protocol from '../../generated/protocol.js';
15
+
16
+ import backgroundServiceViewStyles from './backgroundServiceView.css.js';
16
17
 
17
18
  import type {BackgroundServiceModel} from './BackgroundServiceModel.js';
18
19
  import {Events} from './BackgroundServiceModel.js';
@@ -140,7 +140,7 @@ export class ResourcesPanel extends UI.Panel.PanelWithSidebar {
140
140
  return view;
141
141
  }
142
142
 
143
- showCategoryView(categoryName: string, categoryLink: string|null): void {
143
+ showCategoryView(categoryName: string, categoryLink: Platform.DevToolsPath.UrlString|null): void {
144
144
  if (!this.categoryView) {
145
145
  this.categoryView = new StorageCategoryView();
146
146
  }
@@ -1439,7 +1439,8 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
1439
1439
  }
1440
1440
 
1441
1441
  // SyntaxErrors might not populate the URL field. Try to resolve it via scriptId.
1442
- const url = exceptionDetails.url || debuggerModel.scriptForId(scriptId)?.sourceURL;
1442
+ const url =
1443
+ exceptionDetails.url as Platform.DevToolsPath.UrlString || debuggerModel.scriptForId(scriptId)?.sourceURL;
1443
1444
  if (!url) {
1444
1445
  return;
1445
1446
  }
@@ -1531,7 +1532,9 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
1531
1532
  }
1532
1533
 
1533
1534
  private linkifyWithCustomLinkifier(
1534
- string: string, linkifier: (arg0: string, arg1: string, arg2?: number, arg3?: number) => Node): DocumentFragment {
1535
+ string: string,
1536
+ linkifier: (arg0: string, arg1: Platform.DevToolsPath.UrlString, arg2?: number, arg3?: number) => Node):
1537
+ DocumentFragment {
1535
1538
  if (string.length > getMaxTokenizableStringLength()) {
1536
1539
  const propertyValue = new ObjectUI.ObjectPropertiesSection.ExpandableTextPropertyValue(
1537
1540
  document.createElement('span'), string, getLongStringVisibleLength());
@@ -1563,7 +1566,7 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
1563
1566
  if (splitResult) {
1564
1567
  linkNode = linkifier(token.text, sourceURL, splitResult.lineNumber, splitResult.columnNumber);
1565
1568
  } else {
1566
- linkNode = linkifier(token.text, '');
1569
+ linkNode = linkifier(token.text, Platform.DevToolsPath.EmptyUrlString);
1567
1570
  }
1568
1571
  container.appendChild(linkNode);
1569
1572
  break;
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as i18n from '../../../core/i18n/i18n.js';
6
+ import type * as Platform from '../../../core/platform/platform.js';
6
7
  import * as Buttons from '../../../ui/components/buttons/buttons.js';
7
8
  import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
8
9
  import * as PanelFeedback from '../../../ui/components/panel_feedback/panel_feedback.js';
@@ -43,8 +44,8 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
43
44
 
44
45
  const {render, html} = LitHtml;
45
46
 
46
- const FEEDBACK_LINK = 'https://g.co/devtools/css-overview-feedback';
47
- const DOC_LINK = 'https://developer.chrome.com/docs/devtools/css-overview';
47
+ const FEEDBACK_LINK = 'https://g.co/devtools/css-overview-feedback' as Platform.DevToolsPath.UrlString;
48
+ const DOC_LINK = 'https://developer.chrome.com/docs/devtools/css-overview' as Platform.DevToolsPath.UrlString;
48
49
  export class OverviewStartRequestedEvent extends Event {
49
50
  static readonly eventName = 'overviewstartrequested';
50
51
 
@@ -848,45 +848,46 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
848
848
  return;
849
849
  }
850
850
 
851
+ const contextMenu = this.createCopyContextMenu(event);
852
+ void contextMenu.show();
853
+ }
854
+
855
+ createCopyContextMenu(event: Event): UI.ContextMenu.ContextMenu {
851
856
  const contextMenu = new UI.ContextMenu.ContextMenu(event);
852
- contextMenu.clipboardSection().appendItem(i18nString(UIStrings.copyDeclaration), () => {
857
+ contextMenu.headerSection().appendItem(i18nString(UIStrings.copyDeclaration), () => {
853
858
  const propertyText = `${this.property.name}: ${this.property.value};`;
854
859
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(propertyText);
855
860
  Host.userMetrics.styleTextCopied(Host.UserMetrics.StyleTextCopied.DeclarationViaContextMenu);
856
861
  });
857
862
 
858
- contextMenu.clipboardSection().appendItem(i18nString(UIStrings.copyProperty), () => {
863
+ contextMenu.headerSection().appendItem(i18nString(UIStrings.copyProperty), () => {
859
864
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(this.property.name);
860
865
  Host.userMetrics.styleTextCopied(Host.UserMetrics.StyleTextCopied.PropertyViaContextMenu);
861
866
  });
862
867
 
863
- contextMenu.clipboardSection().appendItem(i18nString(UIStrings.copyValue), () => {
868
+ contextMenu.headerSection().appendItem(i18nString(UIStrings.copyValue), () => {
864
869
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(this.property.value);
865
870
  Host.userMetrics.styleTextCopied(Host.UserMetrics.StyleTextCopied.ValueViaContextMenu);
866
871
  });
867
872
 
868
- contextMenu.defaultSection().appendItem(i18nString(UIStrings.copyRule), () => {
873
+ contextMenu.headerSection().appendItem(i18nString(UIStrings.copyRule), () => {
869
874
  const section = (this.section() as StylePropertiesSection);
870
875
  const ruleText = StylesSidebarPane.formatLeadingProperties(section).ruleText;
871
876
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(ruleText);
872
877
  Host.userMetrics.styleTextCopied(Host.UserMetrics.StyleTextCopied.RuleViaContextMenu);
873
878
  });
874
879
 
875
- contextMenu.defaultSection().appendItem(i18nString(UIStrings.copyAllDeclarations), () => {
880
+ contextMenu.headerSection().appendItem(
881
+ i18nString(UIStrings.copyCssDeclarationAsJs), this.copyCssDeclarationAsJs.bind(this));
882
+
883
+ contextMenu.clipboardSection().appendItem(i18nString(UIStrings.copyAllDeclarations), () => {
876
884
  const section = (this.section() as StylePropertiesSection);
877
885
  const allDeclarationText = StylesSidebarPane.formatLeadingProperties(section).allDeclarationText;
878
886
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(allDeclarationText);
879
887
  Host.userMetrics.styleTextCopied(Host.UserMetrics.StyleTextCopied.AllDeclarationsViaContextMenu);
880
888
  });
881
889
 
882
- contextMenu.defaultSection().appendItem(i18nString(UIStrings.viewComputedValue), () => {
883
- void this.viewComputedValue();
884
- });
885
-
886
890
  contextMenu.clipboardSection().appendItem(
887
- i18nString(UIStrings.copyCssDeclarationAsJs), this.copyCssDeclarationAsJs.bind(this));
888
-
889
- contextMenu.defaultSection().appendItem(
890
891
  i18nString(UIStrings.copyAllCssDeclarationsAsJs), this.copyAllCssDeclarationAsJs.bind(this));
891
892
 
892
893
  // TODO(changhaohan): conditionally add this item only when there are changes to copy
@@ -896,7 +897,11 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
896
897
  Host.userMetrics.styleTextCopied(Host.UserMetrics.StyleTextCopied.AllChangesViaStylesPane);
897
898
  });
898
899
 
899
- void contextMenu.show();
900
+ contextMenu.footerSection().appendItem(i18nString(UIStrings.viewComputedValue), () => {
901
+ void this.viewComputedValue();
902
+ });
903
+
904
+ return contextMenu;
900
905
  }
901
906
 
902
907
  private async viewComputedValue(): Promise<void> {
@@ -1513,6 +1518,7 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
1513
1518
  }
1514
1519
  if (updatedProperty) {
1515
1520
  this.listItemElement.classList.toggle('changed', this.isPropertyChanged(updatedProperty));
1521
+ this.parentPane().updateChangeStatus();
1516
1522
  }
1517
1523
 
1518
1524
  this.matchedStylesInternal.resetActiveProperties();
@@ -147,6 +147,14 @@ const UIStrings = {
147
147
  */
148
148
  automaticDarkMode: 'Automatic dark mode',
149
149
  /**
150
+ *@description Tooltip text that appears when hovering over the css changes button in the Styles Sidebar Pane of the Elements panel
151
+ */
152
+ copyAllCSSChanges: 'Copy all the CSS changes',
153
+ /**
154
+ *@description Tooltip text that appears after clicking on the copy CSS changes button
155
+ */
156
+ copiedToClipboard: 'Copied to clipboard',
157
+ /**
150
158
  *@description Text displayed on layer separators in the styles sidebar pane.
151
159
  */
152
160
  layer: 'Layer',
@@ -207,6 +215,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
207
215
  private readonly imagePreviewPopover: ImagePreviewPopover;
208
216
  activeCSSAngle: InlineEditor.CSSAngle.CSSAngle|null;
209
217
  #urlToChangeTracker: Map<Platform.DevToolsPath.UrlString, ChangeTracker> = new Map();
218
+ #copyChangesButton?: UI.Toolbar.ToolbarButton;
210
219
 
211
220
  static instance(): StylesSidebarPane {
212
221
  if (!stylesSidebarPaneInstance) {
@@ -579,6 +588,11 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
579
588
  if (!this.initialUpdateCompleted) {
580
589
  this.initialUpdateCompleted = true;
581
590
  this.appendToolbarItem(this.createRenderingShortcuts());
591
+ if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.STYLES_PANE_CSS_CHANGES)) {
592
+ this.#copyChangesButton = this.createCopyAllChangesButton();
593
+ this.appendToolbarItem(this.#copyChangesButton);
594
+ this.#copyChangesButton.element.classList.add('hidden');
595
+ }
582
596
  this.dispatchEventToListeners(Events.InitialUpdateCompleted);
583
597
  }
584
598
 
@@ -1109,6 +1123,22 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
1109
1123
  return changedLines.has(formattedLineNumber + 1);
1110
1124
  }
1111
1125
 
1126
+ updateChangeStatus(): void {
1127
+ if (!this.#copyChangesButton) {
1128
+ return;
1129
+ }
1130
+
1131
+ let hasChangedStyles = false;
1132
+ for (const changeTracker of this.#urlToChangeTracker.values()) {
1133
+ if (changeTracker.changedLines.size > 0) {
1134
+ hasChangedStyles = true;
1135
+ break;
1136
+ }
1137
+ }
1138
+
1139
+ this.#copyChangesButton.element.classList.toggle('hidden', !hasChangedStyles);
1140
+ }
1141
+
1112
1142
  private async refreshChangedLines(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
1113
1143
  const changeTracker = this.#urlToChangeTracker.get(uiSourceCode.url());
1114
1144
  if (!changeTracker) {
@@ -1290,6 +1320,29 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
1290
1320
 
1291
1321
  return button;
1292
1322
  }
1323
+
1324
+ private createCopyAllChangesButton(): UI.Toolbar.ToolbarButton {
1325
+ const copyAllChangesButton =
1326
+ new UI.Toolbar.ToolbarButton(i18nString(UIStrings.copyAllCSSChanges), 'largeicon-copy');
1327
+ // TODO(1296947): implement a dedicated component to share between all copy buttons
1328
+ copyAllChangesButton.element.setAttribute('data-content', i18nString(UIStrings.copiedToClipboard));
1329
+ let timeout: number|undefined;
1330
+ copyAllChangesButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, async () => {
1331
+ const allChanges = await this.getFormattedChanges();
1332
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(allChanges);
1333
+ Host.userMetrics.styleTextCopied(Host.UserMetrics.StyleTextCopied.AllChangesViaStylesPane);
1334
+ if (timeout) {
1335
+ clearTimeout(timeout);
1336
+ timeout = undefined;
1337
+ }
1338
+ copyAllChangesButton.element.classList.add('copied-to-clipboard');
1339
+ timeout = window.setTimeout(() => {
1340
+ copyAllChangesButton.element.classList.remove('copied-to-clipboard');
1341
+ timeout = undefined;
1342
+ }, 2000);
1343
+ });
1344
+ return copyAllChangesButton;
1345
+ }
1293
1346
  }
1294
1347
 
1295
1348
  export const enum Events {
@@ -1301,6 +1354,10 @@ export interface StylesUpdateCompletedEvent {
1301
1354
  hasMatchedStyles: boolean;
1302
1355
  }
1303
1356
 
1357
+ interface CompletionResult extends UI.SuggestBox.Suggestion {
1358
+ isCSSVariableColor?: boolean;
1359
+ }
1360
+
1304
1361
  export type EventTypes = {
1305
1362
  [Events.InitialUpdateCompleted]: void,
1306
1363
  [Events.StylesUpdateCompleted]: StylesUpdateCompletedEvent,
@@ -1596,8 +1653,8 @@ export class CSSPropertyPrompt extends UI.TextPrompt.TextPrompt {
1596
1653
  return Promise.resolve([]);
1597
1654
  }
1598
1655
 
1599
- const prefixResults: UI.SuggestBox.Suggestions = [];
1600
- const anywhereResults: UI.SuggestBox.Suggestions = [];
1656
+ const prefixResults: Array<CompletionResult> = [];
1657
+ const anywhereResults: Array<CompletionResult> = [];
1601
1658
  if (!editingVariable) {
1602
1659
  this.cssCompletions.forEach(completion => filterCompletions.call(this, completion, false /* variable */));
1603
1660
  }
@@ -1692,10 +1749,10 @@ export class CSSPropertyPrompt extends UI.TextPrompt.TextPrompt {
1692
1749
 
1693
1750
  if (this.isColorAware && !this.isEditingName) {
1694
1751
  results.sort((a, b) => {
1695
- if (Boolean(a.subtitleRenderer) === Boolean(b.subtitleRenderer)) {
1752
+ if (a.isCSSVariableColor && b.isCSSVariableColor) {
1696
1753
  return 0;
1697
1754
  }
1698
- return a.subtitleRenderer ? -1 : 1;
1755
+ return a.isCSSVariableColor ? -1 : 1;
1699
1756
  });
1700
1757
  }
1701
1758
  return Promise.resolve(results);
@@ -1703,7 +1760,7 @@ export class CSSPropertyPrompt extends UI.TextPrompt.TextPrompt {
1703
1760
  function filterCompletions(
1704
1761
  this: CSSPropertyPrompt, completion: string, variable: boolean, nameValue?: boolean): void {
1705
1762
  const index = completion.toLowerCase().indexOf(lowerQuery);
1706
- const result: UI.SuggestBox.Suggestion = {
1763
+ const result: CompletionResult = {
1707
1764
  text: completion,
1708
1765
  title: undefined,
1709
1766
  subtitle: undefined,
@@ -1714,6 +1771,7 @@ export class CSSPropertyPrompt extends UI.TextPrompt.TextPrompt {
1714
1771
  selectionRange: undefined,
1715
1772
  hideGhostText: undefined,
1716
1773
  iconElement: undefined,
1774
+ isCSSVariableColor: false,
1717
1775
  };
1718
1776
  if (variable) {
1719
1777
  const computedValue =
@@ -1722,6 +1780,9 @@ export class CSSPropertyPrompt extends UI.TextPrompt.TextPrompt {
1722
1780
  const color = Common.Color.Color.parse(computedValue);
1723
1781
  if (color) {
1724
1782
  result.subtitleRenderer = swatchRenderer.bind(null, color);
1783
+ result.isCSSVariableColor = true;
1784
+ } else {
1785
+ result.subtitleRenderer = computedValueSubtitleRenderer.bind(null, computedValue);
1725
1786
  }
1726
1787
  }
1727
1788
  }
@@ -1742,6 +1803,14 @@ export class CSSPropertyPrompt extends UI.TextPrompt.TextPrompt {
1742
1803
  swatch.style.pointerEvents = 'none';
1743
1804
  return swatch;
1744
1805
  }
1806
+ function computedValueSubtitleRenderer(computedValue: string): Element {
1807
+ const subtitleElement = document.createElement('span');
1808
+ subtitleElement.className = 'suggestion-subtitle';
1809
+ subtitleElement.textContent = `${computedValue}`;
1810
+ subtitleElement.style.maxWidth = '100px';
1811
+ subtitleElement.title = `${computedValue}`;
1812
+ return subtitleElement;
1813
+ }
1745
1814
  }
1746
1815
  }
1747
1816
 
@@ -232,6 +232,9 @@
232
232
 
233
233
  .sidebar-pane-section-toolbar.new-rule-toolbar {
234
234
  visibility: hidden;
235
+ margin-bottom: 5px;
236
+
237
+ --toolbar-height: 16px;
235
238
  }
236
239
 
237
240
  .styles-pane:not(.is-editing-style) .styles-section.matched-styles:not(.read-only):hover .sidebar-pane-section-toolbar.new-rule-toolbar {
@@ -5,6 +5,7 @@
5
5
  import * as Common from '../../core/common/common.js';
6
6
  import * as Host from '../../core/host/host.js';
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
+ import type * as Platform from '../../core/platform/platform.js';
8
9
  import * as SDK from '../../core/sdk/sdk.js';
9
10
  import type * as IssuesManager from '../../models/issues_manager/issues_manager.js';
10
11
  import * as Logs from '../../models/logs/logs.js';
@@ -44,7 +45,7 @@ export const enum AffectedItem {
44
45
  Source = 'Source',
45
46
  }
46
47
 
47
- export const extractShortPath = (path: string): string => {
48
+ export const extractShortPath = (path: Platform.DevToolsPath.UrlString): string => {
48
49
  // 1st regex matches everything after last '/'
49
50
  // if path ends with '/', 2nd regex returns everything between the last two '/'
50
51
  return (/[^/]+$/.exec(path) || /[^/]+\/$/.exec(path) || [''])[0];
@@ -255,8 +256,8 @@ export abstract class AffectedResourcesView extends UI.TreeOutline.TreeElement {
255
256
  // TODO(crbug.com/1108503): Add some mechanism to be able to add telemetry to this element.
256
257
  const linkifier = new Components.Linkifier.Linkifier(maxLengthForDisplayedURLs);
257
258
  const sourceAnchor = linkifier.linkifyScriptLocation(
258
- target || null, sourceLocation.scriptId || null, sourceLocation.url, sourceLocation.lineNumber,
259
- {columnNumber: sourceLocation.columnNumber, inlineFrameIndex: 0});
259
+ target || null, sourceLocation.scriptId || null, sourceLocation.url as Platform.DevToolsPath.UrlString,
260
+ sourceLocation.lineNumber, {columnNumber: sourceLocation.columnNumber, inlineFrameIndex: 0});
260
261
  sourceCodeLocation.appendChild(sourceAnchor);
261
262
  }
262
263
  element.appendChild(sourceCodeLocation);
@@ -42,7 +42,8 @@ export class AffectedSourcesView extends AffectedResourcesView {
42
42
  // Also, this element has a context menu, so we should be able to
43
43
  // track when the user use the context menu too.
44
44
  // TODO(crbug.com/1108503): Add some mechanism to be able to add telemetry to this element.
45
- const anchorElement = Components.Linkifier.Linkifier.linkifyURL(url, linkifierURLOptions);
45
+ const anchorElement =
46
+ Components.Linkifier.Linkifier.linkifyURL(url as Platform.DevToolsPath.UrlString, linkifierURLOptions);
46
47
  cellElement.appendChild(anchorElement);
47
48
  const rowElement = document.createElement('tr');
48
49
  rowElement.classList.add('affected-resource-source');