chrome-devtools-frontend 1.0.980472 → 1.0.982087

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 (65) hide show
  1. package/front_end/core/common/ParsedURL.ts +42 -4
  2. package/front_end/core/host/InspectorFrontendHost.ts +7 -6
  3. package/front_end/core/host/InspectorFrontendHostAPI.ts +8 -8
  4. package/front_end/core/i18n/locales/en-US.json +15 -0
  5. package/front_end/core/i18n/locales/en-XL.json +15 -0
  6. package/front_end/core/platform/DevToolsPath.ts +3 -0
  7. package/front_end/core/sdk/CSSRule.ts +2 -2
  8. package/front_end/core/sdk/DOMDebuggerModel.ts +2 -2
  9. package/front_end/core/sdk/NetworkRequest.ts +1 -1
  10. package/front_end/core/sdk/SourceMap.ts +14 -5
  11. package/front_end/core/sdk/Target.ts +4 -3
  12. package/front_end/generated/InspectorBackendCommands.js +6 -2
  13. package/front_end/generated/protocol.ts +16 -0
  14. package/front_end/legacy_test_runner/bindings_test_runner/BindingsTestRunner.js +5 -0
  15. package/front_end/models/bindings/BreakpointManager.ts +0 -2
  16. package/front_end/models/bindings/ContentProviderBasedProject.ts +6 -4
  17. package/front_end/models/bindings/FileUtils.ts +3 -2
  18. package/front_end/models/persistence/EditFileSystemView.ts +3 -1
  19. package/front_end/models/persistence/FileSystemWorkspaceBinding.ts +14 -9
  20. package/front_end/models/persistence/IsolatedFileSystem.ts +66 -40
  21. package/front_end/models/persistence/IsolatedFileSystemManager.ts +4 -3
  22. package/front_end/models/persistence/NetworkPersistenceManager.ts +6 -5
  23. package/front_end/models/persistence/PlatformFileSystem.ts +15 -10
  24. package/front_end/models/timeline_model/TimelineModel.ts +1 -0
  25. package/front_end/models/workspace/FileManager.ts +9 -6
  26. package/front_end/models/workspace/UISourceCode.ts +4 -2
  27. package/front_end/models/workspace/WorkspaceImpl.ts +9 -5
  28. package/front_end/panels/application/BackgroundServiceView.ts +2 -1
  29. package/front_end/panels/application/ServiceWorkerCacheViews.ts +1 -1
  30. package/front_end/panels/console/ConsoleFormat.ts +23 -0
  31. package/front_end/panels/console/ConsoleView.ts +3 -1
  32. package/front_end/panels/console/ConsoleViewMessage.ts +3 -19
  33. package/front_end/panels/coverage/CoverageView.ts +2 -1
  34. package/front_end/panels/emulation/AdvancedApp.ts +6 -2
  35. package/front_end/panels/emulation/DeviceModeToolbar.ts +2 -1
  36. package/front_end/panels/input/InputTimeline.ts +2 -1
  37. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +1 -1
  38. package/front_end/panels/network/NetworkLogView.ts +2 -2
  39. package/front_end/panels/network/ResourceWebSocketFrameView.ts +1 -2
  40. package/front_end/panels/profiler/HeapSnapshotView.ts +3 -2
  41. package/front_end/panels/profiler/ProfileView.ts +2 -2
  42. package/front_end/panels/protocol_monitor/ProtocolMonitor.ts +4 -2
  43. package/front_end/panels/screencast/ScreencastView.ts +4 -1
  44. package/front_end/panels/settings/SettingsScreen.ts +4 -1
  45. package/front_end/panels/snippets/ScriptSnippetFileSystem.ts +25 -19
  46. package/front_end/panels/sources/NavigatorView.ts +9 -5
  47. package/front_end/panels/sources/SourcesNavigator.ts +2 -2
  48. package/front_end/panels/sources/components/HeadersView.css +17 -2
  49. package/front_end/panels/sources/components/HeadersView.ts +102 -0
  50. package/front_end/panels/timeline/TimelinePanel.ts +2 -1
  51. package/front_end/panels/timeline/TimelineUIUtils.ts +7 -0
  52. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +963 -886
  53. package/front_end/third_party/lighthouse/report/bundle.js +3 -2
  54. package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +1 -1
  55. package/front_end/ui/components/buttons/Button.ts +11 -1
  56. package/front_end/ui/components/buttons/button.css +31 -10
  57. package/front_end/ui/components/docs/button/basic.ts +47 -1
  58. package/front_end/ui/components/panel_feedback/FeedbackButton.ts +4 -1
  59. package/front_end/ui/legacy/XLink.ts +8 -3
  60. package/front_end/ui/legacy/components/color_picker/ContrastDetails.ts +3 -1
  61. package/front_end/ui/legacy/components/utils/ImagePreview.ts +6 -2
  62. package/front_end/ui/legacy/components/utils/Linkifier.ts +7 -2
  63. package/front_end/ui/legacy/tabbedPane.css +1 -0
  64. package/front_end/ui/legacy/themeColors.css +4 -0
  65. package/package.json +1 -1
@@ -53,7 +53,7 @@ import * as UI from '../../ui/legacy/legacy.js';
53
53
  import objectValueStyles from '../../ui/legacy/components/object_ui/objectValue.css.js';
54
54
  import type {Chrome} from '../../../extension-api/ExtensionAPI.js'; // eslint-disable-line rulesdir/es_modules_import
55
55
 
56
- import {format} from './ConsoleFormat.js';
56
+ import {format, updateStyle} from './ConsoleFormat.js';
57
57
  import type {ConsoleViewportElement} from './ConsoleViewport.js';
58
58
  import consoleViewStyles from './consoleView.css.js';
59
59
  import {augmentErrorStackWithScriptIds, parseSourcePositionsFromErrorStack} from './ErrorStackParser.js';
@@ -925,26 +925,10 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
925
925
  }
926
926
  break;
927
927
  }
928
- case 'style': {
928
+ case 'style':
929
929
  // Make sure that allowed properties do not interfere with link visibility.
930
- const ALLOWED_PROPERTY_PREFIXES =
931
- ['background', 'border', 'color', 'font', 'line', 'margin', 'padding', 'text'];
932
-
933
- currentStyle.clear();
934
- const buffer = document.createElement('span');
935
- buffer.setAttribute('style', token.value);
936
- for (const property of buffer.style) {
937
- if (!ALLOWED_PROPERTY_PREFIXES.some(
938
- prefix => property.startsWith(prefix) || property.startsWith(`-webkit-${prefix}`))) {
939
- continue;
940
- }
941
- currentStyle.set(property, {
942
- value: buffer.style.getPropertyValue(property),
943
- priority: buffer.style.getPropertyPriority(property),
944
- });
945
- }
930
+ updateStyle(currentStyle, token.value);
946
931
  break;
947
- }
948
932
  }
949
933
  }
950
934
  return args;
@@ -506,7 +506,8 @@ export class CoverageView extends UI.Widget.VBox {
506
506
 
507
507
  private async exportReport(): Promise<void> {
508
508
  const fos = new Bindings.FileUtils.FileOutputStream();
509
- const fileName = `Coverage-${Platform.DateUtilities.toISO8601Compact(new Date())}.json`;
509
+ const fileName =
510
+ `Coverage-${Platform.DateUtilities.toISO8601Compact(new Date())}.json` as Platform.DevToolsPath.RawPathString;
510
511
  const accepted = await fos.open(fileName);
511
512
  if (!accepted) {
512
513
  return;
@@ -2,9 +2,10 @@
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
- import * as Common from '../../core/common/common.js';
5
+ import type * as Common from '../../core/common/common.js';
6
6
  import * as Host from '../../core/host/host.js';
7
7
  import * as UI from '../../ui/legacy/legacy.js';
8
+ import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
8
9
 
9
10
  import {DeviceModeWrapper} from './DeviceModeWrapper.js';
10
11
  import type {Bounds} from './InspectedPagePlaceholder.js';
@@ -77,7 +78,10 @@ export class AdvancedApp implements Common.App.App {
77
78
  }
78
79
 
79
80
  deviceModeEmulationFrameLoaded(toolboxDocument: Document): void {
80
- Common.Settings.Settings.instance().createSetting('uiTheme', 'default');
81
+ ThemeSupport.ThemeSupport.instance().applyTheme(toolboxDocument);
82
+ ThemeSupport.ThemeSupport.instance().addEventListener(ThemeSupport.ThemeChangeEvent.eventName, () => {
83
+ ThemeSupport.ThemeSupport.instance().applyTheme(toolboxDocument);
84
+ });
81
85
  UI.UIUtils.initializeUIUtils(toolboxDocument);
82
86
  UI.UIUtils.installComponentRootStyles((toolboxDocument.body as Element));
83
87
  UI.ContextMenu.ContextMenu.installHandler(toolboxDocument);
@@ -6,6 +6,7 @@ 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
8
  import * as Root from '../../core/root/root.js';
9
+ import type * as Platform from '../../core/platform/platform.js';
9
10
  import * as EmulationModel from '../../models/emulation/emulation.js';
10
11
  import * as UI from '../../ui/legacy/legacy.js';
11
12
  import * as MobileThrottling from '../mobile_throttling/mobile_throttling.js';
@@ -389,7 +390,7 @@ export class DeviceModeToolbar {
389
390
 
390
391
  private experimentalClicked(): void {
391
392
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.openInNewTab(
392
- 'chrome://flags/#enable-experimental-web-platform-features');
393
+ 'chrome://flags/#enable-experimental-web-platform-features' as Platform.DevToolsPath.UrlString);
393
394
  }
394
395
 
395
396
  private fillOptionsToolbar(toolbar: UI.Toolbar.Toolbar): void {
@@ -182,7 +182,8 @@ export class InputTimeline extends UI.Widget.VBox implements Timeline.TimelineLo
182
182
  return;
183
183
  }
184
184
 
185
- const fileName = `InputProfile-${Platform.DateUtilities.toISO8601Compact(new Date())}.json`;
185
+ const fileName = `InputProfile-${Platform.DateUtilities.toISO8601Compact(new Date())}.json` as
186
+ Platform.DevToolsPath.RawPathString;
186
187
  const stream = new Bindings.FileUtils.FileOutputStream();
187
188
 
188
189
  const accepted = await stream.open(fileName);
@@ -184,7 +184,7 @@ export class LighthouseReportUIFeatures extends LighthouseReport.ReportUIFeature
184
184
  const sanitizedDomain = domain.replace(/[^a-z0-9.-]+/gi, '_');
185
185
  const timestamp = Platform.DateUtilities.toISO8601Compact(new Date(this.json.fetchTime));
186
186
  const ext = blob.type.match('json') ? '.json' : '.html';
187
- const basename = `${sanitizedDomain}-${timestamp}${ext}`;
187
+ const basename = `${sanitizedDomain}-${timestamp}${ext}` as Platform.DevToolsPath.RawPathString;
188
188
  const text = await blob.text();
189
189
  void Workspace.FileManager.FileManager.instance().save(basename, text, true /* forceSaveAs */);
190
190
  }
@@ -1673,10 +1673,10 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
1673
1673
  }
1674
1674
  const url = mainTarget.inspectedURL();
1675
1675
  const parsedURL = Common.ParsedURL.ParsedURL.fromString(url);
1676
- const filename = parsedURL ? parsedURL.host : 'network-log';
1676
+ const filename = (parsedURL ? parsedURL.host : 'network-log') as Platform.DevToolsPath.RawPathString;
1677
1677
  const stream = new Bindings.FileUtils.FileOutputStream();
1678
1678
 
1679
- if (!await stream.open(filename + '.har')) {
1679
+ if (!await stream.open(Common.ParsedURL.ParsedURL.concatenate(filename, '.har'))) {
1680
1680
  return;
1681
1681
  }
1682
1682
 
@@ -462,8 +462,7 @@ export class ResourceWebSocketFrameNode extends DataGrid.SortableDataGrid.Sortab
462
462
  if (!this.binaryViewInternal) {
463
463
  if (this.dataTextInternal.length > 0) {
464
464
  this.binaryViewInternal = new BinaryResourceView(
465
- this.dataTextInternal, /* url */ '' as Platform.DevToolsPath.UrlString,
466
- Common.ResourceType.resourceTypes.WebSocket);
465
+ this.dataTextInternal, Platform.DevToolsPath.EmptyUrlString, Common.ResourceType.resourceTypes.WebSocket);
467
466
  }
468
467
  }
469
468
  return this.binaryViewInternal;
@@ -1557,7 +1557,7 @@ export class HeapProfileHeader extends ProfileHeader {
1557
1557
  onTempFileReady: (() => void)|null;
1558
1558
  failedToCreateTempFile?: boolean;
1559
1559
  wasDisposed?: boolean;
1560
- fileName?: string;
1560
+ fileName?: Platform.DevToolsPath.RawPathString;
1561
1561
 
1562
1562
  constructor(
1563
1563
  heapProfilerModel: SDK.HeapProfilerModel.HeapProfilerModel|null, type: HeapSnapshotProfileType, title?: string) {
@@ -1713,7 +1713,8 @@ export class HeapProfileHeader extends ProfileHeader {
1713
1713
  saveToFile(): void {
1714
1714
  const fileOutputStream = new Bindings.FileUtils.FileOutputStream();
1715
1715
  this.fileName = this.fileName ||
1716
- 'Heap-' + Platform.DateUtilities.toISO8601Compact(new Date()) + this.profileType().fileExtension();
1716
+ 'Heap-' + Platform.DateUtilities.toISO8601Compact(new Date()) + this.profileType().fileExtension() as
1717
+ Platform.DevToolsPath.RawPathString;
1717
1718
  const onOpen = async(accepted: boolean): Promise<void> => {
1718
1719
  if (!accepted) {
1719
1720
  return;
@@ -549,7 +549,7 @@ export const enum ViewTypes {
549
549
 
550
550
  export class WritableProfileHeader extends ProfileHeader implements Common.StringOutputStream.OutputStream {
551
551
  readonly debuggerModel: SDK.DebuggerModel.DebuggerModel|null;
552
- fileName?: string;
552
+ fileName?: Platform.DevToolsPath.RawPathString;
553
553
  jsonifiedProfile?: string|null;
554
554
  profile?: Protocol.Profiler.Profile;
555
555
  protocolProfileInternal?: Protocol.Profiler.Profile;
@@ -599,7 +599,7 @@ export class WritableProfileHeader extends ProfileHeader implements Common.Strin
599
599
  const now = Platform.DateUtilities.toISO8601Compact(new Date());
600
600
  const fileExtension = this.profileType().fileExtension();
601
601
 
602
- this.fileName = `${this.profileType().typeName()}-${now}${fileExtension}`;
602
+ this.fileName = `${this.profileType().typeName()}-${now}${fileExtension}` as Platform.DevToolsPath.RawPathString;
603
603
  }
604
604
 
605
605
  const accepted = await fileOutputStream.open(this.fileName);
@@ -247,7 +247,8 @@ export class ProtocolMonitorImpl extends UI.Widget.VBox {
247
247
  const [domain, method] = String(methodColumn.value).split('.');
248
248
  const type = typeColumn.value === 'sent' ? 'method' : 'event';
249
249
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.openInNewTab(
250
- `https://chromedevtools.github.io/devtools-protocol/tot/${domain}#${type}-${method}`);
250
+ `https://chromedevtools.github.io/devtools-protocol/tot/${domain}#${type}-${method}` as
251
+ Platform.DevToolsPath.UrlString);
251
252
  });
252
253
  },
253
254
  },
@@ -466,7 +467,8 @@ export class ProtocolMonitorImpl extends UI.Widget.VBox {
466
467
 
467
468
  private async saveAsFile(): Promise<void> {
468
469
  const now = new Date();
469
- const fileName = 'ProtocolMonitor-' + Platform.DateUtilities.toISO8601Compact(now) + '.json';
470
+ const fileName = 'ProtocolMonitor-' + Platform.DateUtilities.toISO8601Compact(now) + '.json' as
471
+ Platform.DevToolsPath.RawPathString;
470
472
  const stream = new Bindings.FileUtils.FileOutputStream();
471
473
 
472
474
  const accepted = await stream.open(fileName);
@@ -32,6 +32,7 @@ import * as Common from '../../core/common/common.js';
32
32
  import * as Host from '../../core/host/host.js';
33
33
  import * as i18n from '../../core/i18n/i18n.js';
34
34
  import * as SDK from '../../core/sdk/sdk.js';
35
+ import type * as Platform from '../../core/platform/platform.js';
35
36
  import * as Protocol from '../../generated/protocol.js';
36
37
  import * as UI from '../../ui/legacy/legacy.js';
37
38
 
@@ -743,7 +744,9 @@ export class ScreencastView extends UI.Widget.VBox implements SDK.OverlayModel.H
743
744
  if (match) {
744
745
  url = match[1];
745
746
  }
746
- Host.InspectorFrontendHost.InspectorFrontendHostInstance.inspectedURLChanged(url);
747
+ // TODO(crbug.com/1253323): Cast to UrlString will be removed when migration to branded types is complete.
748
+ Host.InspectorFrontendHost.InspectorFrontendHostInstance.inspectedURLChanged(
749
+ url as Platform.DevToolsPath.UrlString);
747
750
  this.navigationUrl.value = decodeURI(url);
748
751
  }
749
752
 
@@ -32,6 +32,7 @@ import * as Common from '../../core/common/common.js';
32
32
  import * as Host from '../../core/host/host.js';
33
33
  import * as i18n from '../../core/i18n/i18n.js';
34
34
  import * as Root from '../../core/root/root.js';
35
+ import type * as Platform from '../../core/platform/platform.js';
35
36
  import * as IconButton from '../../ui/components/icon_button/icon_button.js';
36
37
  import * as Components from '../../ui/legacy/components/utils/utils.js';
37
38
  import * as UI from '../../ui/legacy/legacy.js';
@@ -508,9 +509,11 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
508
509
  case 'settings.show':
509
510
  void SettingsScreen.showSettingsScreen({focusTabHeader: true} as ShowSettingsScreenOptions);
510
511
  return true;
512
+ // TODO(crbug.com/1253323): Cast to UrlString will be removed when migration to branded types is complete.
511
513
  case 'settings.documentation':
512
514
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.openInNewTab(
513
- UI.UIUtils.addReferrerToURL('https://developer.chrome.com/docs/devtools/'));
515
+ UI.UIUtils.addReferrerToURL('https://developer.chrome.com/docs/devtools/') as
516
+ Platform.DevToolsPath.UrlString);
514
517
  return true;
515
518
  case 'settings.shortcuts':
516
519
  void SettingsScreen.showSettingsScreen({name: 'keybinds', focusTabHeader: true});
@@ -13,8 +13,6 @@ import type * as TextUtils from '../../models/text_utils/text_utils.js';
13
13
  import * as UI from '../../ui/legacy/legacy.js';
14
14
  import * as Workspace from '../../models/workspace/workspace.js';
15
15
 
16
- // TODO(crbug.com/1253323): Cast to EncodedPathString will be removed from this file when migration to branded types is complete.
17
-
18
16
  const UIStrings = {
19
17
  /**
20
18
  *@description Default snippet name when a new snippet is created in the Sources panel
@@ -30,12 +28,12 @@ const UIStrings = {
30
28
  const str_ = i18n.i18n.registerUIStrings('panels/snippets/ScriptSnippetFileSystem.ts', UIStrings);
31
29
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
32
30
 
33
- function escapeSnippetName(name: string): Platform.DevToolsPath.EncodedPathString {
34
- return Common.ParsedURL.ParsedURL.rawPathToEncodedPathString(name as Platform.DevToolsPath.RawPathString);
31
+ function escapeSnippetName(name: Platform.DevToolsPath.RawPathString): Platform.DevToolsPath.EncodedPathString {
32
+ return Common.ParsedURL.ParsedURL.rawPathToEncodedPathString(name);
35
33
  }
36
34
 
37
- function unescapeSnippetName(name: string): string {
38
- return Common.ParsedURL.ParsedURL.encodedPathToRawPathString(name as Platform.DevToolsPath.EncodedPathString);
35
+ function unescapeSnippetName(name: Platform.DevToolsPath.EncodedPathString): string {
36
+ return Common.ParsedURL.ParsedURL.encodedPathToRawPathString(name);
39
37
  }
40
38
 
41
39
  export class SnippetFileSystem extends Persistence.PlatformFileSystem.PlatformFileSystem {
@@ -53,11 +51,13 @@ export class SnippetFileSystem extends Persistence.PlatformFileSystem.PlatformFi
53
51
  return savedSnippets.map(snippet => escapeSnippetName(snippet.name));
54
52
  }
55
53
 
56
- async createFile(_path: string, _name: string|null): Promise<string|null> {
54
+ async createFile(_path: Platform.DevToolsPath.EncodedPathString, _name: Platform.DevToolsPath.RawPathString|null):
55
+ Promise<Platform.DevToolsPath.EncodedPathString|null> {
57
56
  const nextId = this.lastSnippetIdentifierSetting.get() + 1;
58
57
  this.lastSnippetIdentifierSetting.set(nextId);
59
58
 
60
- const snippetName = i18nString(UIStrings.scriptSnippet, {PH1: nextId});
59
+ const snippetName =
60
+ i18nString(UIStrings.scriptSnippet, {PH1: nextId}) as string as Platform.DevToolsPath.RawPathString;
61
61
  const snippets = this.snippetsSetting.get();
62
62
  snippets.push({name: snippetName, content: ''});
63
63
  this.snippetsSetting.set(snippets);
@@ -65,8 +65,8 @@ export class SnippetFileSystem extends Persistence.PlatformFileSystem.PlatformFi
65
65
  return escapeSnippetName(snippetName);
66
66
  }
67
67
 
68
- async deleteFile(path: string): Promise<boolean> {
69
- const name = unescapeSnippetName(path.substring(1));
68
+ async deleteFile(path: Platform.DevToolsPath.EncodedPathString): Promise<boolean> {
69
+ const name = unescapeSnippetName(Common.ParsedURL.ParsedURL.substring(path, 1));
70
70
  const allSnippets: Snippet[] = this.snippetsSetting.get();
71
71
  const snippets = allSnippets.filter(snippet => snippet.name !== name);
72
72
  if (allSnippets.length !== snippets.length) {
@@ -76,8 +76,9 @@ export class SnippetFileSystem extends Persistence.PlatformFileSystem.PlatformFi
76
76
  return false;
77
77
  }
78
78
 
79
- async requestFileContent(path: string): Promise<TextUtils.ContentProvider.DeferredContent> {
80
- const name = unescapeSnippetName(path.substring(1));
79
+ async requestFileContent(path: Platform.DevToolsPath.EncodedPathString):
80
+ Promise<TextUtils.ContentProvider.DeferredContent> {
81
+ const name = unescapeSnippetName(Common.ParsedURL.ParsedURL.substring(path, 1));
81
82
  const snippets: Snippet[] = this.snippetsSetting.get();
82
83
  const snippet = snippets.find(snippet => snippet.name === name);
83
84
  if (snippet) {
@@ -86,8 +87,9 @@ export class SnippetFileSystem extends Persistence.PlatformFileSystem.PlatformFi
86
87
  return {content: null, isEncoded: false, error: `A snippet with name '${name}' was not found`};
87
88
  }
88
89
 
89
- async setFileContent(path: string, content: string, _isBase64: boolean): Promise<boolean> {
90
- const name = unescapeSnippetName(path.substring(1));
90
+ async setFileContent(path: Platform.DevToolsPath.EncodedPathString, content: string, _isBase64: boolean):
91
+ Promise<boolean> {
92
+ const name = unescapeSnippetName(Common.ParsedURL.ParsedURL.substring(path, 1));
91
93
  const snippets: Snippet[] = this.snippetsSetting.get();
92
94
  const snippet = snippets.find(snippet => snippet.name === name);
93
95
  if (snippet) {
@@ -98,11 +100,13 @@ export class SnippetFileSystem extends Persistence.PlatformFileSystem.PlatformFi
98
100
  return false;
99
101
  }
100
102
 
101
- renameFile(path: string, newName: string, callback: (arg0: boolean, arg1?: string|undefined) => void): void {
102
- const name = unescapeSnippetName(path.substring(1));
103
+ renameFile(
104
+ path: Platform.DevToolsPath.EncodedPathString, newName: Platform.DevToolsPath.RawPathString,
105
+ callback: (arg0: boolean, arg1?: string|undefined) => void): void {
106
+ const name = unescapeSnippetName(Common.ParsedURL.ParsedURL.substring(path, 1));
103
107
  const snippets: Snippet[] = this.snippetsSetting.get();
104
108
  const snippet = snippets.find(snippet => snippet.name === name);
105
- newName = newName.trim();
109
+ newName = Common.ParsedURL.ParsedURL.trim(newName);
106
110
  if (!snippet || newName.length === 0 || snippets.find(snippet => snippet.name === newName)) {
107
111
  callback(false);
108
112
  return;
@@ -128,7 +132,9 @@ export class SnippetFileSystem extends Persistence.PlatformFileSystem.PlatformFi
128
132
  }
129
133
 
130
134
  tooltipForURL(url: Platform.DevToolsPath.UrlString): string {
131
- return i18nString(UIStrings.linkedTo, {PH1: unescapeSnippetName(url.substring(this.path().length))});
135
+ return i18nString(
136
+ UIStrings.linkedTo,
137
+ {PH1: unescapeSnippetName(Common.ParsedURL.ParsedURL.sliceUrlToEncodedPathString(url, this.path().length))});
132
138
  }
133
139
 
134
140
  supportsAutomapping(): boolean {
@@ -215,6 +221,6 @@ export function findSnippetsProject(): Workspace.Workspace.Project {
215
221
  return workspaceProject;
216
222
  }
217
223
  export interface Snippet {
218
- name: string;
224
+ name: Platform.DevToolsPath.RawPathString;
219
225
  content: string;
220
226
  }
@@ -28,6 +28,8 @@
28
28
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
  */
30
30
 
31
+ // TODO(crbug.com/1253323): Casts to Branded Types will be removed from this file when migration to branded types is complete.
32
+
31
33
  import * as Common from '../../core/common/common.js';
32
34
  import * as Host from '../../core/host/host.js';
33
35
  import * as i18n from '../../core/i18n/i18n.js';
@@ -843,7 +845,7 @@ export class NavigatorView extends UI.Widget.VBox implements SDK.TargetManager.O
843
845
  }
844
846
 
845
847
  handleFolderContextMenu(event: Event, node: NavigatorTreeNode): void {
846
- const path = (node as NavigatorFolderTreeNode).folderPath || '';
848
+ const path = (node as NavigatorFolderTreeNode).folderPath || Platform.DevToolsPath.EmptyEncodedPathString;
847
849
  const project = (node as NavigatorFolderTreeNode).project || null;
848
850
 
849
851
  const contextMenu = new UI.ContextMenu.ContextMenu(event);
@@ -914,7 +916,7 @@ export class NavigatorView extends UI.Widget.VBox implements SDK.TargetManager.O
914
916
  if (uiSourceCodeToCopy) {
915
917
  content = (await uiSourceCodeToCopy.requestContent()).content || '';
916
918
  }
917
- const uiSourceCode = await project.createFile(path, null, content);
919
+ const uiSourceCode = await project.createFile(path as Platform.DevToolsPath.EncodedPathString, null, content);
918
920
  if (!uiSourceCode) {
919
921
  return;
920
922
  }
@@ -1413,7 +1415,9 @@ export class NavigatorUISourceCodeTreeNode extends NavigatorTreeNode {
1413
1415
  if (this.treeElement) {
1414
1416
  this.treeElement.title = newTitle;
1415
1417
  }
1416
- void this.uiSourceCodeInternal.rename(newTitle).then(renameCallback.bind(this));
1418
+ // necessary cast to RawPathString as alternative would be altering type of Config<T>
1419
+ void this.uiSourceCodeInternal.rename(newTitle as Platform.DevToolsPath.RawPathString)
1420
+ .then(renameCallback.bind(this));
1417
1421
  return;
1418
1422
  }
1419
1423
  afterEditing.call(this, true);
@@ -1453,7 +1457,7 @@ export class NavigatorUISourceCodeTreeNode extends NavigatorTreeNode {
1453
1457
 
1454
1458
  export class NavigatorFolderTreeNode extends NavigatorTreeNode {
1455
1459
  project: Workspace.Workspace.Project|null;
1456
- readonly folderPath: string;
1460
+ readonly folderPath: Platform.DevToolsPath.EncodedPathString;
1457
1461
  title: string;
1458
1462
  treeElement!: NavigatorFolderTreeElement|null;
1459
1463
  constructor(
@@ -1461,7 +1465,7 @@ export class NavigatorFolderTreeNode extends NavigatorTreeNode {
1461
1465
  folderPath: string, title: string) {
1462
1466
  super(navigatorView, id, type);
1463
1467
  this.project = project;
1464
- this.folderPath = folderPath;
1468
+ this.folderPath = folderPath as Platform.DevToolsPath.EncodedPathString;
1465
1469
  this.title = title;
1466
1470
  }
1467
1471
 
@@ -31,9 +31,9 @@
31
31
  import * as Common from '../../core/common/common.js';
32
32
  import * as Host from '../../core/host/host.js';
33
33
  import * as i18n from '../../core/i18n/i18n.js';
34
+ import * as Platform from '../../core/platform/platform.js';
34
35
  import * as SDK from '../../core/sdk/sdk.js';
35
36
  import * as Persistence from '../../models/persistence/persistence.js';
36
- import type * as Platform from '../../core/platform/platform.js';
37
37
  import * as Workspace from '../../models/workspace/workspace.js';
38
38
  import * as UI from '../../ui/legacy/legacy.js';
39
39
  import * as Snippets from '../snippets/snippets.js';
@@ -406,7 +406,7 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
406
406
  switch (actionId) {
407
407
  case 'sources.create-snippet':
408
408
  void Snippets.ScriptSnippetFileSystem.findSnippetsProject()
409
- .createFile('', null, '')
409
+ .createFile(Platform.DevToolsPath.EmptyEncodedPathString, null, '')
410
410
  .then(uiSourceCode => Common.Revealer.reveal(uiSourceCode));
411
411
  return true;
412
412
  case 'sources.add-folder-to-workspace':
@@ -16,8 +16,7 @@
16
16
  font-family: var(--monospace-font-family);
17
17
  font-size: var(--monospace-font-size);
18
18
  align-items: center;
19
- line-height: 18px;
20
- margin-top: 3px;
19
+ line-height: 24px;
21
20
  }
22
21
 
23
22
  .row devtools-button {
@@ -57,6 +56,18 @@
57
56
  border-radius: 2px;
58
57
  }
59
58
 
59
+ .row .inline-button {
60
+ opacity: 0%;
61
+ visibility: hidden;
62
+ transition: opacity 200ms;
63
+ }
64
+
65
+ .row:focus-within .inline-button,
66
+ .row:hover .inline-button {
67
+ opacity: 100%;
68
+ visibility: visible;
69
+ }
70
+
60
71
  .center-wrapper {
61
72
  height: 100%;
62
73
  display: flex;
@@ -79,3 +90,7 @@
79
90
  line-height: 1.5em;
80
91
  color: var(--color-text-secondary);
81
92
  }
93
+
94
+ .add-block {
95
+ margin-top: 3px;
96
+ }
@@ -5,6 +5,7 @@
5
5
  import * as i18n from '../../../core/i18n/i18n.js';
6
6
  import * as Persistence from '../../../models/persistence/persistence.js';
7
7
  import * as Workspace from '../../../models/workspace/workspace.js';
8
+ import * as Buttons from '../../../ui/components/buttons/buttons.js';
8
9
  import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
9
10
  import * as UI from '../../../ui/legacy/legacy.js';
10
11
  import * as LitHtml from '../../../ui/lit-html/lit-html.js';
@@ -12,6 +13,18 @@ import * as LitHtml from '../../../ui/lit-html/lit-html.js';
12
13
  import HeadersViewStyles from './HeadersView.css.js';
13
14
 
14
15
  const UIStrings = {
16
+ /**
17
+ *@description The title of a button that adds a field to input a header in the editor form.
18
+ */
19
+ addHeader: 'Add a header',
20
+ /**
21
+ *@description The title of a button that removes a field to input a header in the editor form.
22
+ */
23
+ removeHeader: 'Remove this header',
24
+ /**
25
+ *@description The title of a button that removes a section for defining header overrides in the editor form.
26
+ */
27
+ removeBlock: 'Remove this \'`ApplyTo`\'-section',
15
28
  /**
16
29
  *@description Error message for files which cannot not be parsed.
17
30
  *@example {.headers} PH1
@@ -23,10 +36,17 @@ const UIStrings = {
23
36
  */
24
37
  parsingErrorExplainer:
25
38
  'This is most likely due to a syntax error in \'\'{PH1}\'\'. Try opening this file in an external editor to fix the error or delete the file and re-create the override.',
39
+ /**
40
+ *@description Button text for a button which adds an additional header override.
41
+ */
42
+ addHeaderOverride: 'Add header override',
26
43
  };
27
44
  const str_ = i18n.i18n.registerUIStrings('panels/sources/components/HeadersView.ts', UIStrings);
28
45
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
29
46
 
47
+ const plusIconUrl = new URL('../../../Images/plus_icon.svg', import.meta.url).toString();
48
+ const minusIconUrl = new URL('../../../Images/minus_icon.svg', import.meta.url).toString();
49
+
30
50
  export class HeadersView extends UI.View.SimpleView {
31
51
  readonly #headersViewComponent = new HeadersViewComponent();
32
52
  #uiSourceCode: Workspace.UISourceCode.UISourceCode;
@@ -96,6 +116,10 @@ export class HeadersView extends UI.View.SimpleView {
96
116
  this.#setComponentData(this.#uiSourceCode.workingCopy());
97
117
  }
98
118
 
119
+ getComponent(): HeadersViewComponent {
120
+ return this.#headersViewComponent;
121
+ }
122
+
99
123
  dispose(): void {
100
124
  this.#uiSourceCode.removeEventListener(
101
125
  Workspace.UISourceCode.Events.WorkingCopyChanged, this.#onWorkingCopyChanged, this);
@@ -127,11 +151,13 @@ export class HeadersViewComponent extends HTMLElement {
127
151
  #headerOverrides: HeaderOverride[] = [];
128
152
  #uiSourceCode: Workspace.UISourceCode.UISourceCode|null = null;
129
153
  #parsingError = false;
154
+ #focusElement: {blockIndex: number, headerIndex?: number}|null = null;
130
155
 
131
156
  constructor() {
132
157
  super();
133
158
  this.#shadow.addEventListener('focusin', this.#onFocusIn.bind(this));
134
159
  this.#shadow.addEventListener('focusout', this.#onFocusOut.bind(this));
160
+ this.#shadow.addEventListener('click', this.#onClick.bind(this));
135
161
  this.#shadow.addEventListener('input', this.#onInput.bind(this));
136
162
  this.#shadow.addEventListener('keydown', this.#onKeyDown.bind(this));
137
163
  }
@@ -187,6 +213,43 @@ export class HeadersViewComponent extends HTMLElement {
187
213
  selection?.removeAllRanges();
188
214
  }
189
215
 
216
+ #generateNextHeaderName(headers: Header[]): string {
217
+ const takenNames = new Set<string>(headers.map(header => header.name));
218
+ let idx = 1;
219
+ while (takenNames.has('headerName' + idx)) {
220
+ idx++;
221
+ }
222
+ return 'headerName' + idx;
223
+ }
224
+
225
+ #onClick(e: Event): void {
226
+ const target = e.target as HTMLButtonElement;
227
+ const rowElement = target.closest('.row') as HTMLElement | null;
228
+ const blockIndex = Number(rowElement?.dataset.blockIndex || 0);
229
+ const headerIndex = Number(rowElement?.dataset.headerIndex || 0);
230
+ if (target.matches('.add-header')) {
231
+ this.#headerOverrides[blockIndex].headers.splice(
232
+ headerIndex + 1, 0,
233
+ {name: this.#generateNextHeaderName(this.#headerOverrides[blockIndex].headers), value: 'headerValue'});
234
+ this.#focusElement = {blockIndex, headerIndex: headerIndex + 1};
235
+ this.#onHeadersChanged();
236
+ } else if (target.matches('.remove-header')) {
237
+ this.#headerOverrides[blockIndex].headers.splice(headerIndex, 1);
238
+ if (this.#headerOverrides[blockIndex].headers.length === 0) {
239
+ this.#headerOverrides[blockIndex].headers.push(
240
+ {name: this.#generateNextHeaderName(this.#headerOverrides[blockIndex].headers), value: 'headerValue'});
241
+ }
242
+ this.#onHeadersChanged();
243
+ } else if (target.matches('.add-block')) {
244
+ this.#headerOverrides.push({applyTo: '*', headers: [{name: 'headerName', value: 'headerValue'}]});
245
+ this.#focusElement = {blockIndex: this.#headerOverrides.length - 1};
246
+ this.#onHeadersChanged();
247
+ } else if (target.matches('.remove-block')) {
248
+ this.#headerOverrides.splice(blockIndex, 1);
249
+ this.#onHeadersChanged();
250
+ }
251
+ }
252
+
190
253
  #onInput(e: Event): void {
191
254
  const target = e.target as HTMLButtonElement;
192
255
  const rowElement = target.closest('.row') as HTMLElement;
@@ -252,8 +315,25 @@ export class HeadersViewComponent extends HTMLElement {
252
315
  )}
253
316
  `,
254
317
  )}
318
+ <${Buttons.Button.Button.litTagName} .variant=${Buttons.Button.Variant.SECONDARY} class="add-block">
319
+ ${i18nString(UIStrings.addHeaderOverride)}
320
+ </${Buttons.Button.Button.litTagName}>
255
321
  `, this.#shadow, {host: this});
256
322
  // clang-format on
323
+
324
+ if (this.#focusElement) {
325
+ let focusElement: Element|null = null;
326
+ if (this.#focusElement.headerIndex) {
327
+ focusElement = this.#shadow.querySelector(`[data-block-index="${
328
+ this.#focusElement.blockIndex}"][data-header-index="${this.#focusElement.headerIndex}"] .header-name`);
329
+ } else {
330
+ focusElement = this.#shadow.querySelector(`[data-block-index="${this.#focusElement.blockIndex}"] .apply-to`);
331
+ }
332
+ if (focusElement) {
333
+ (focusElement as HTMLElement).focus();
334
+ }
335
+ this.#focusElement = null;
336
+ }
257
337
  }
258
338
 
259
339
  #renderApplyToRow(pattern: string, blockIndex: number): LitHtml.TemplateResult {
@@ -263,6 +343,13 @@ export class HeadersViewComponent extends HTMLElement {
263
343
  <div>${i18n.i18n.lockedString('Apply to')}</div>
264
344
  <div class="separator">:</div>
265
345
  ${this.#renderEditable(pattern, 'apply-to')}
346
+ <${Buttons.Button.Button.litTagName}
347
+ title=${i18nString(UIStrings.removeBlock)}
348
+ .size=${Buttons.Button.Size.SMALL}
349
+ .iconUrl=${minusIconUrl}
350
+ .variant=${Buttons.Button.Variant.ROUND}
351
+ class="remove-block inline-button"
352
+ ></${Buttons.Button.Button.litTagName}>
266
353
  </div>
267
354
  `;
268
355
  // clang-format on
@@ -275,6 +362,21 @@ export class HeadersViewComponent extends HTMLElement {
275
362
  ${this.#renderEditable(header.name, 'header-name red')}
276
363
  <div class="separator">:</div>
277
364
  ${this.#renderEditable(header.value, 'header-value')}
365
+ <${Buttons.Button.Button.litTagName}
366
+ title=${i18nString(UIStrings.addHeader)}
367
+ .size=${Buttons.Button.Size.SMALL}
368
+ .iconUrl=${plusIconUrl}
369
+ .variant=${Buttons.Button.Variant.ROUND}
370
+ class="add-header inline-button"
371
+ ></${Buttons.Button.Button.litTagName}>
372
+ <${Buttons.Button.Button.litTagName}
373
+ title=${i18nString(UIStrings.removeHeader)}
374
+ .size=${Buttons.Button.Size.SMALL}
375
+ .iconUrl=${minusIconUrl}
376
+ .variant=${Buttons.Button.Variant.ROUND}
377
+ class="remove-header inline-button"
378
+ ></${Buttons.Button.Button.litTagName}>
379
+ </div>
278
380
  `;
279
381
  // clang-format on
280
382
  }
@@ -636,7 +636,8 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
636
636
  }
637
637
 
638
638
  const now = new Date();
639
- const fileName = 'Profile-' + Platform.DateUtilities.toISO8601Compact(now) + '.json';
639
+ const fileName =
640
+ 'Profile-' + Platform.DateUtilities.toISO8601Compact(now) + '.json' as Platform.DevToolsPath.RawPathString;
640
641
  const stream = new Bindings.FileUtils.FileOutputStream();
641
642
 
642
643
  const accepted = await stream.open(fileName);