chrome-devtools-frontend 1.0.1702515 → 1.0.1704172

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 (84) hide show
  1. package/AUTHORS +1 -0
  2. package/front_end/core/common/ParsedURL.ts +59 -1
  3. package/front_end/core/platform/StringUtilities.ts +65 -0
  4. package/front_end/core/sdk/CPUThrottlingManager.ts +3 -3
  5. package/front_end/core/sdk/SourceMap.ts +3 -3
  6. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +4 -3
  7. package/front_end/core/sdk/SourceMapScopesInfo.ts +17 -5
  8. package/front_end/core/text_utils/Markdown.ts +73 -0
  9. package/front_end/core/text_utils/text_utils.ts +2 -0
  10. package/front_end/entrypoints/main/MainImpl.ts +3 -3
  11. package/front_end/generated/InspectorBackendCommands.ts +3 -1
  12. package/front_end/generated/protocol-mapping.d.ts +8 -0
  13. package/front_end/generated/protocol-proxy-api.d.ts +6 -0
  14. package/front_end/generated/protocol.ts +22 -0
  15. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  16. package/front_end/models/comment_manager/CD4ABridge.ts +5 -0
  17. package/front_end/models/har/HARFormat.ts +7 -2
  18. package/front_end/models/issues_manager/MarkdownIssueDescription.ts +4 -47
  19. package/front_end/models/persistence/AutomaticFileSystemWorkspaceBinding.ts +1 -1
  20. package/front_end/models/source_map_scopes/NamesResolver.ts +13 -5
  21. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingListView.ts +33 -1
  22. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +4 -12
  23. package/front_end/panels/application/ApplicationPanelSidebar.ts +58 -60
  24. package/front_end/panels/application/BackForwardCacheTreeElement.ts +1 -1
  25. package/front_end/panels/application/FrameDetailsView.ts +64 -82
  26. package/front_end/panels/application/OpenedWindowDetailsView.ts +20 -21
  27. package/front_end/panels/application/PreloadingTreeElement.ts +3 -3
  28. package/front_end/panels/common/CommentsOverlayWidget.ts +145 -72
  29. package/front_end/panels/common/CommentsStatusBarPill.ts +40 -7
  30. package/front_end/panels/common/commentsOverlay.css +34 -23
  31. package/front_end/panels/common/commentsStatusBarPill.css +13 -0
  32. package/front_end/panels/elements/DOMTreeWidget.ts +29 -10
  33. package/front_end/panels/issues/IssueView.ts +5 -1
  34. package/front_end/panels/network/RequestPayloadView.ts +7 -2
  35. package/front_end/panels/network/ShowMoreDetailsWidget.ts +12 -9
  36. package/front_end/panels/sources/DebuggerPlugin.ts +49 -16
  37. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  38. package/front_end/panels/sources/SourcesView.ts +119 -148
  39. package/front_end/panels/sources/TabbedEditorContainer.ts +263 -207
  40. package/front_end/panels/sources/components/HeadersView.ts +193 -123
  41. package/front_end/third_party/chromium/README.chromium +1 -1
  42. package/front_end/third_party/puppeteer/README.chromium +2 -2
  43. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +5 -0
  44. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +24 -32
  45. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts +1 -1
  46. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.d.ts.map +1 -1
  47. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js +15 -1
  48. package/front_end/third_party/puppeteer/package/lib/puppeteer/api/ElementHandle.js.map +1 -1
  49. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js +8 -1
  50. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Accessibility.js.map +1 -1
  51. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +0 -8
  52. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
  53. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +0 -24
  54. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
  55. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts +12 -0
  56. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.d.ts.map +1 -1
  57. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js +79 -17
  58. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/BrowserLauncher.js.map +1 -1
  59. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js +1 -1
  60. package/front_end/third_party/puppeteer/package/lib/puppeteer/node-env-setup.js.map +1 -1
  61. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  62. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  63. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  64. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  65. package/front_end/third_party/puppeteer/package/lib/types.d.ts +5 -0
  66. package/front_end/third_party/puppeteer/package/package.json +4 -4
  67. package/front_end/third_party/puppeteer/package/src/api/ElementHandle.ts +15 -2
  68. package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +10 -1
  69. package/front_end/third_party/puppeteer/package/src/common/Debug.ts +0 -27
  70. package/front_end/third_party/puppeteer/package/src/node/BrowserLauncher.ts +96 -17
  71. package/front_end/third_party/puppeteer/package/src/node-env-setup.ts +1 -1
  72. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  73. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  74. package/front_end/ui/comments/CommentAnchorResolver.ts +60 -22
  75. package/front_end/ui/comments/CommentOverlayManager.ts +78 -25
  76. package/front_end/ui/components/markdown_view/MarkdownPlaceholderLitRenderer.ts +7 -65
  77. package/front_end/ui/legacy/TabbedPane.ts +51 -40
  78. package/front_end/ui/legacy/components/data_grid/DataGridExporter.ts +7 -13
  79. package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +51 -57
  80. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +342 -373
  81. package/front_end/ui/legacy/components/source_frame/ImageView.ts +101 -42
  82. package/front_end/ui/legacy/components/utils/Linkifier.ts +1 -2
  83. package/front_end/ui/visual_logging/KnownContextValues.ts +8 -0
  84. package/package.json +2 -1
package/AUTHORS CHANGED
@@ -99,6 +99,7 @@ Ryuhei Shima <shimaryuhei@gmail.com>
99
99
  Samuel Maddock <samuelmaddock@electronjs.org>
100
100
  Satvic Dhawan <satvicdhawan14@gmail.com>
101
101
  Sebastian Markbåge <sebastian@calyptus.eu>
102
+ Semih Dogan <semih702do@gmail.com>
102
103
  Serg Kryvonos <sergeikrivonos@gmail.com>
103
104
  Sergio Avalos <sergio.avalos@gmail.com>
104
105
  Shelley Vohr <shelley.vohr@gmail.com>
@@ -46,6 +46,60 @@ export function schemeIs(url: Platform.DevToolsPath.UrlString|URL, scheme: strin
46
46
  }
47
47
  }
48
48
 
49
+ /**
50
+ * Schemes safe for unprivileged web contexts (matching ChildProcessSecurityPolicyImpl::RegisterDefaultSchemes).
51
+ */
52
+ const WEB_SAFE_SCHEMES = new Set([
53
+ 'http:',
54
+ 'https:',
55
+ 'ws:',
56
+ 'wss:',
57
+ 'data:',
58
+ ]);
59
+
60
+ /**
61
+ * Returns true if the URL uses an unprivileged web-safe scheme (or is a `blob:` URL wrapping one).
62
+ */
63
+ export function hasWebSafeScheme(url: Platform.DevToolsPath.UrlString|URL|string): boolean {
64
+ try {
65
+ const parsed = new URL(url);
66
+ if (parsed.protocol === 'blob:') {
67
+ return hasWebSafeScheme(parsed.pathname);
68
+ }
69
+ return WEB_SAFE_SCHEMES.has(parsed.protocol);
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Privileged browser and local schemes that must not be opened via external/new-tab links by default.
77
+ */
78
+ const PRIVILEGED_SCHEMES = new Set([
79
+ 'chrome:',
80
+ 'chrome-extension:',
81
+ 'chrome-search:',
82
+ 'chrome-untrusted:',
83
+ 'devtools:',
84
+ 'file:',
85
+ 'isolated-app:',
86
+ ]);
87
+
88
+ /**
89
+ * Returns true if the URL uses a privileged scheme (or is a `blob:` URL wrapping one).
90
+ */
91
+ export function isPrivilegedScheme(url: Platform.DevToolsPath.UrlString|URL|string): boolean {
92
+ try {
93
+ const parsed = new URL(url);
94
+ if (parsed.protocol === 'blob:') {
95
+ return isPrivilegedScheme(parsed.pathname);
96
+ }
97
+ return PRIVILEGED_SCHEMES.has(parsed.protocol);
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+
49
103
  /**
50
104
  * File paths in DevTools that are represented either as unencoded absolute or relative paths, or encoded paths, or URLs.
51
105
  * @example
@@ -179,7 +233,11 @@ export class ParsedURL {
179
233
  preEncodedPath = 'file:///' + preEncodedPath;
180
234
  }
181
235
  }
182
- return new URL(preEncodedPath).toString() as Platform.DevToolsPath.UrlString;
236
+ let url = new URL(preEncodedPath).toString();
237
+ while (url.endsWith('/') && url.length > 'file:///'.length) {
238
+ url = url.substring(0, url.length - 1);
239
+ }
240
+ return url as Platform.DevToolsPath.UrlString;
183
241
  }
184
242
 
185
243
  static relativePathToUrlString(
@@ -740,3 +740,68 @@ export const concatBase64 = function(lhs: string, rhs: string): string {
740
740
  const lhsToDecode = lhs.substring(lhs.length - 4);
741
741
  return lhsLeaveAsIs + globalThis.btoa(globalThis.atob(lhsToDecode) + globalThis.atob(rhs));
742
742
  };
743
+
744
+ /**
745
+ * Characters that cause spreadsheet viewers (Excel, Google Sheets, LibreOffice
746
+ * Calc) to evaluate a cell as a formula rather than rendering it as plain text
747
+ * when they appear at the start of a cell.
748
+ */
749
+ const CSV_FORMULA_TRIGGERS = new Set(['=', '+', '-', '@', '\t', '\r']);
750
+
751
+ /**
752
+ * Matches plain numeric literals (e.g. `-42`, `+3.14`, `-1e-3`).
753
+ * Even though negative/signed numbers start with `-` or `+`, spreadsheets parse
754
+ * them as numbers rather than formulas, so we exempt them from `'` prefixing to
755
+ * keep numeric columns usable.
756
+ */
757
+ const CSV_PLAIN_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
758
+
759
+ /**
760
+ * Returns true if a spreadsheet viewer could interpret `value` as a formula
761
+ * (for example `=SUM(A1:A2)`, `"- Loading..."`, or `"@alice"`).
762
+ */
763
+ function isCsvFormula(value: string): boolean {
764
+ // Check both the raw string and the leading-whitespace-trimmed string, since
765
+ // some spreadsheet parsers ignore leading spaces before `=`, `+`, `-`, or `@`.
766
+ const trimmed = value.trimStart();
767
+ if (!CSV_FORMULA_TRIGGERS.has(value[0]) && !CSV_FORMULA_TRIGGERS.has(trimmed[0])) {
768
+ return false;
769
+ }
770
+ return !CSV_PLAIN_NUMBER.test(trimmed.trimEnd());
771
+ }
772
+
773
+ /**
774
+ * Formats and escapes `value` so it can be safely written as a single CSV cell.
775
+ *
776
+ * This handles two separate layers of escaping:
777
+ * 1. Spreadsheet formula escaping (CWE-1236 & viewer fidelity):
778
+ * RFC 4180 double-quoting (`"=1+1"` or `"- Loading..."`) only groups text
779
+ * into a single CSV column; spreadsheet apps strip the outer `"` and still
780
+ * evaluate cells starting with `=`, `+`, `-`, or `@` as formulas (often
781
+ * resulting in `#NAME?` errors or unintended formula execution). Prefixing
782
+ * the value with a single quote (`'`) tells spreadsheet apps to treat the
783
+ * cell as literal text (and they hide the leading `'` when displaying it).
784
+ * - Example without commas: `=SUM(A1:A2)` -> `'=SUM(A1:A2)`
785
+ * 2. RFC 4180 structural CSV quoting:
786
+ * If the cell also contains commas (`,`), double quotes (`"`), or newlines,
787
+ * it is wrapped in `"..."` (with inner `"` doubled to `""`) so CSV parsers
788
+ * do not split the cell across columns or rows.
789
+ * - Example with commas: `=SUM(1,2)` -> `"'=SUM(1,2)"`
790
+ * - Example starting with a literal quote: `"=SUM(1,2)"` -> `"""=SUM(1,2)"""`
791
+ * (not treated as a formula since the first character is `"`, not `=`).
792
+ */
793
+ export const escapeCsvCell = function(value: string): string {
794
+ // Step 1: Prefix formula-like text with `'` so spreadsheets render it as literal text.
795
+ let escaped = isCsvFormula(value) ? `'${value}` : value;
796
+
797
+ // Step 2: Apply standard RFC 4180 double-quoting if the cell contains quotes,
798
+ // commas, or line breaks so the CSV structure stays intact.
799
+ if (escaped.includes('"')) {
800
+ escaped = escaped.replace(/"/g, '""');
801
+ return `"${escaped}"`;
802
+ }
803
+ if (escaped.includes(',') || escaped.includes('\n') || escaped.includes('\r')) {
804
+ return `"${escaped}"`;
805
+ }
806
+ return escaped;
807
+ };
@@ -282,9 +282,9 @@ export class CPUThrottlingManager extends Common.ObjectWrapper.ObjectWrapper<Eve
282
282
  }
283
283
 
284
284
  async updateHostDefaultCPUPerformanceTier(): Promise<void> {
285
- if (this.#manualCPUPerformanceOverride !== undefined) {
286
- // We do not want to update the host default tier when it is manually overridden
287
- // via the sensors panel (in which case, `navigator.cpuPerformance` would return the override).
285
+ if (this.#isCPUPerformanceOverrideActive()) {
286
+ // We do not want to update the host default tier when it is overridden
287
+ // (in which case, `navigator.cpuPerformance` would return the override).
288
288
  return;
289
289
  }
290
290
  const target = this.#targetManager.primaryPageTarget();
@@ -12,7 +12,7 @@ import {scopeTreeForScript} from './ScopeTreeCache.js';
12
12
  import type {Script} from './Script.js';
13
13
  import {buildOriginalScopes, decodePastaRanges, type NamedFunctionRange} from './SourceMapFunctionRanges.js';
14
14
  import {decodeRangeMappings} from './SourceMapRangeMappings.js';
15
- import {SourceMapScopesInfo, type TranslatedFrame} from './SourceMapScopesInfo.js';
15
+ import {scriptRelativePosition, SourceMapScopesInfo, type TranslatedFrame} from './SourceMapScopesInfo.js';
16
16
 
17
17
  /**
18
18
  * Type of the base source map JSON object, which contains the sources and the mappings at the very least, plus
@@ -850,8 +850,8 @@ export class SourceMap {
850
850
  return null;
851
851
  }
852
852
 
853
- return this.#scopesInfo.resolveMappedVariablesAtPosition(location.lineNumber, location.columnNumber,
854
- ignoreInnerBlockScopes);
853
+ const {line, column} = scriptRelativePosition(location);
854
+ return this.#scopesInfo.resolveMappedVariablesAtPosition(line, column, ignoreInnerBlockScopes);
855
855
  }
856
856
 
857
857
  findOriginalFunctionName(position: ScopesCodec.Position): string|null {
@@ -8,7 +8,7 @@ import * as i18n from '../i18n/i18n.js';
8
8
 
9
9
  import type {CallFrame, LocationRange, ScopeChainEntry} from './DebuggerModel.js';
10
10
  import {type GetPropertiesResult, type RemoteObject, RemoteObjectImpl, RemoteObjectProperty} from './RemoteObject.js';
11
- import {findExpression} from './SourceMapScopesInfo.js';
11
+ import {findExpression, scriptRelativePosition} from './SourceMapScopesInfo.js';
12
12
 
13
13
  const UIStrings = {
14
14
  /**
@@ -263,8 +263,9 @@ class SourceMapScopeRemoteObject extends RemoteObjectImpl {
263
263
 
264
264
  /** @returns null if the variable is unavailable at the current paused location */
265
265
  #findExpression(index: number): string|null {
266
- const pausedPosition = this.#callFrame.location();
267
- return findExpression(this.#range, index, pausedPosition?.lineNumber, pausedPosition?.columnNumber);
266
+ const pausedLocation = this.#callFrame.location();
267
+ const pausedPosition = pausedLocation ? scriptRelativePosition(pausedLocation) : undefined;
268
+ return findExpression(this.#range, index, pausedPosition?.line, pausedPosition?.column);
268
269
  }
269
270
 
270
271
  static #unavailableProperty(name: string): RemoteObjectProperty {
@@ -8,7 +8,7 @@ import type * as ScopesCodec from '../../third_party/source-map-scopes-codec/sou
8
8
  import type * as Platform from '../platform/platform.js';
9
9
  import type * as TextUtils from '../text_utils/text_utils.js';
10
10
 
11
- import type {CallFrame, Scope, ScopeChainEntry} from './DebuggerModel.js';
11
+ import type {CallFrame, Location, Scope, ScopeChainEntry} from './DebuggerModel.js';
12
12
  import type {SourceMap} from './SourceMap.js';
13
13
  import {SourceMapScopeChainEntry} from './SourceMapScopeChainEntry.js';
14
14
 
@@ -389,8 +389,8 @@ export class SourceMapScopesInfo {
389
389
 
390
390
  /** Similar to #findGeneratedRangeChain, but takes inlineFrameIndex of virtual call frames into account */
391
391
  #findGeneratedRangeChainForFrame(callFrame: CallFrame): ScopesCodec.GeneratedRange[] {
392
- const rangeChain =
393
- this.#findGeneratedRangeChain(callFrame.location().lineNumber, callFrame.location().columnNumber);
392
+ const {line, column} = scriptRelativePosition(callFrame.location());
393
+ const rangeChain = this.#findGeneratedRangeChain(line, column);
394
394
  if (callFrame.inlineFrameIndex === 0) {
395
395
  return rangeChain;
396
396
  }
@@ -611,6 +611,18 @@ export function comparePositions(a: ScopesCodec.Position, b: ScopesCodec.Positio
611
611
  return a.column - b.column;
612
612
  }
613
613
 
614
+ /**
615
+ * Converts a raw V8 {@link location} into a generated position relative to the start of its script.
616
+ *
617
+ * Positions in source maps (mappings and generated ranges) are relative to the start of the script,
618
+ * while V8 reports locations in inline `<script>`s (without `//# sourceURL`) relative to the start of
619
+ * the surrounding document.
620
+ */
621
+ export function scriptRelativePosition(location: Location): ScopesCodec.Position {
622
+ const {lineNumber, columnNumber} = location.script()?.rawLocationToRelativeLocation(location) ?? location;
623
+ return {line: lineNumber, column: columnNumber};
624
+ }
625
+
614
626
  function positionRange(callFrame: CallFrame, scope: Scope): {start: ScopesCodec.Position, end: ScopesCodec.Position}|
615
627
  null {
616
628
  const range = scope.range();
@@ -619,8 +631,8 @@ function positionRange(callFrame: CallFrame, scope: Scope): {start: ScopesCodec.
619
631
  return null;
620
632
  }
621
633
  return {
622
- start: {line: range.start.lineNumber, column: range.start.columnNumber},
623
- end: {line: range.end.lineNumber, column: range.end.columnNumber},
634
+ start: scriptRelativePosition(range.start),
635
+ end: scriptRelativePosition(range.end),
624
636
  };
625
637
  }
626
638
 
@@ -0,0 +1,73 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import * as Marked from '../../third_party/marked/marked.js';
6
+
7
+ export interface PlaceholderToken extends Marked.Marked.Tokens.Generic {
8
+ type: 'placeholder';
9
+ raw: string;
10
+ key: string;
11
+ }
12
+
13
+ export const VALID_PLACEHOLDER_MATCH_PATTERN: RegExp = /\{(PLACEHOLDER_[a-zA-Z][a-zA-Z0-9_]*)\}/g;
14
+ const VALID_PLACEHOLDER_NAME_PATTERN = /^PLACEHOLDER_[a-zA-Z][a-zA-Z0-9_]*$/;
15
+
16
+ const placeholderExtension: Marked.Marked.TokenizerExtension = {
17
+ name: 'placeholder',
18
+ level: 'inline',
19
+ start(src: string): number |
20
+ undefined {
21
+ return src.match(/\{PLACEHOLDER_[a-zA-Z][a-zA-Z0-9_]*\}/)?.index;
22
+ },
23
+ tokenizer(src: string): PlaceholderToken |
24
+ undefined {
25
+ const match = /^\{(PLACEHOLDER_[a-zA-Z][a-zA-Z0-9_]*)\}/.exec(src);
26
+ if (match) {
27
+ return {
28
+ type: 'placeholder',
29
+ raw: match[0],
30
+ key: match[1],
31
+ };
32
+ }
33
+ return undefined;
34
+ },
35
+ };
36
+
37
+ export function validateSubstitutions(rawMarkdown: string, substitutions?: Map<string, string>): void {
38
+ if (!substitutions) {
39
+ return;
40
+ }
41
+ const unusedPlaceholders = new Set(substitutions.keys());
42
+ for (const key of unusedPlaceholders) {
43
+ if (!VALID_PLACEHOLDER_NAME_PATTERN.test(key)) {
44
+ throw new Error(`Invalid placeholder '${key}' provided in the substitutions map.`);
45
+ }
46
+ }
47
+ for (const [, placeholder] of rawMarkdown.matchAll(VALID_PLACEHOLDER_MATCH_PATTERN)) {
48
+ if (!substitutions.has(placeholder)) {
49
+ throw new Error(`No replacement provided for placeholder '${placeholder}'.`);
50
+ }
51
+ unusedPlaceholders.delete(placeholder);
52
+ }
53
+ if (unusedPlaceholders.size > 0) {
54
+ throw new Error(`Unused replacements provided: ${[...unusedPlaceholders]}`);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Tokenizes markdown into a Marked AST while preserving `{PLACEHOLDER_*}` tokens
60
+ * as `PlaceholderToken` AST nodes instead of substituting strings before lexing.
61
+ */
62
+ export function tokenizeWithPlaceholders(markdown: string|Marked.Marked.Token[],
63
+ substitutions?: Map<string, string>): Marked.Marked.Token[] {
64
+ const rawMarkdown = typeof markdown === 'string' ? markdown : markdown.map(token => token.raw).join('');
65
+ validateSubstitutions(rawMarkdown, substitutions);
66
+ if (typeof markdown !== 'string' && !rawMarkdown.includes('{PLACEHOLDER_')) {
67
+ return markdown;
68
+ }
69
+ const markedInstance = new Marked.Marked.Marked({
70
+ extensions: [placeholderExtension],
71
+ });
72
+ return markedInstance.lexer(rawMarkdown);
73
+ }
@@ -5,6 +5,7 @@
5
5
  import * as CodeMirrorUtils from './CodeMirrorUtils.js';
6
6
  import * as ContentData from './ContentData.js';
7
7
  import * as ContentProvider from './ContentProvider.js';
8
+ import * as Markdown from './Markdown.js';
8
9
  import * as StaticContentProvider from './StaticContentProvider.js';
9
10
  import * as StreamingContentData from './StreamingContentData.js';
10
11
  import * as Text from './Text.js';
@@ -17,6 +18,7 @@ export {
17
18
  CodeMirrorUtils,
18
19
  ContentData,
19
20
  ContentProvider,
21
+ Markdown,
20
22
  StaticContentProvider,
21
23
  StreamingContentData,
22
24
  Text,
@@ -121,9 +121,9 @@ const UIStrings = {
121
121
  */
122
122
  aiModelDownloaded: 'AI model downloaded',
123
123
  /**
124
- * @description Title of the menu item in the customize and control menu leading to the DevTools MCP repository.
124
+ * @description Title of the menu item in the customize and control menu leading to the Chrome DevTools for agents repository.
125
125
  */
126
- getDevToolsMcp: 'Get `DevTools MCP`',
126
+ getChromeDevToolsForAgents: 'Give your agent access to `DevTools`',
127
127
  } as const;
128
128
  const str_ = i18n.i18n.registerUIStrings('entrypoints/main/MainImpl.ts', UIStrings);
129
129
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -893,7 +893,7 @@ export class MainMenuItem implements UI.Toolbar.Provider {
893
893
 
894
894
  contextMenu.defaultSection().appendAction('freestyler.main-menu', undefined, /* optional */ true);
895
895
 
896
- contextMenu.defaultSection().appendItem(i18nString(UIStrings.getDevToolsMcp), () => {
896
+ contextMenu.defaultSection().appendItem(i18nString(UIStrings.getChromeDevToolsForAgents), () => {
897
897
  UIHelpers.openInNewTab('https://github.com/ChromeDevTools/chrome-devtools-mcp');
898
898
  }, {
899
899
  additionalElement: UI.UIUtils.maybeCreateNewBadge('get-devtools-mcp'),
@@ -1183,7 +1183,7 @@ inspectorBackend.registerType("Page.FrameResource", [{"name": "url", "type": "st
1183
1183
  inspectorBackend.registerType("Page.FrameResourceTree", [{"name": "frame", "type": "object", "optional": false, "description": "Frame information for this tree item.", "typeRef": "Page.Frame"}, {"name": "childFrames", "type": "array", "optional": true, "description": "Child frames.", "typeRef": "Page.FrameResourceTree"}, {"name": "resources", "type": "array", "optional": false, "description": "Information about frame resources.", "typeRef": "Page.FrameResource"}]);
1184
1184
  inspectorBackend.registerType("Page.FrameTree", [{"name": "frame", "type": "object", "optional": false, "description": "Frame information for this tree item.", "typeRef": "Page.Frame"}, {"name": "childFrames", "type": "array", "optional": true, "description": "Child frames.", "typeRef": "Page.FrameTree"}]);
1185
1185
  inspectorBackend.registerType("Page.NavigationEntry", [{"name": "id", "type": "number", "optional": false, "description": "Unique id of the navigation history entry.", "typeRef": null}, {"name": "url", "type": "string", "optional": false, "description": "URL of the navigation history entry.", "typeRef": null}, {"name": "userTypedURL", "type": "string", "optional": false, "description": "URL that the user typed in the url bar.", "typeRef": null}, {"name": "title", "type": "string", "optional": false, "description": "Title of the navigation history entry.", "typeRef": null}, {"name": "transitionType", "type": "string", "optional": false, "description": "Transition type.", "typeRef": "Page.TransitionType"}]);
1186
- inspectorBackend.registerType("Page.ScreencastFrameMetadata", [{"name": "offsetTop", "type": "number", "optional": false, "description": "Top offset in DIP.", "typeRef": null}, {"name": "pageScaleFactor", "type": "number", "optional": false, "description": "Page scale factor.", "typeRef": null}, {"name": "deviceWidth", "type": "number", "optional": false, "description": "Device screen width in DIP.", "typeRef": null}, {"name": "deviceHeight", "type": "number", "optional": false, "description": "Device screen height in DIP.", "typeRef": null}, {"name": "scrollOffsetX", "type": "number", "optional": false, "description": "Position of horizontal scroll in CSS pixels.", "typeRef": null}, {"name": "scrollOffsetY", "type": "number", "optional": false, "description": "Position of vertical scroll in CSS pixels.", "typeRef": null}, {"name": "timestamp", "type": "number", "optional": true, "description": "Frame swap timestamp.", "typeRef": "Network.TimeSinceEpoch"}]);
1186
+ inspectorBackend.registerType("Page.ScreencastFrameMetadata", [{"name": "offsetTop", "type": "number", "optional": false, "description": "Top offset in DIP.", "typeRef": null}, {"name": "pageScaleFactor", "type": "number", "optional": false, "description": "Page scale factor.", "typeRef": null}, {"name": "deviceWidth", "type": "number", "optional": false, "description": "Device screen width in DIP.", "typeRef": null}, {"name": "deviceHeight", "type": "number", "optional": false, "description": "Device screen height in DIP.", "typeRef": null}, {"name": "scrollOffsetX", "type": "number", "optional": false, "description": "Position of horizontal scroll in CSS pixels.", "typeRef": null}, {"name": "scrollOffsetY", "type": "number", "optional": false, "description": "Position of vertical scroll in CSS pixels.", "typeRef": null}, {"name": "timestamp", "type": "number", "optional": true, "description": "Frame swap timestamp.", "typeRef": "Network.TimeSinceEpoch"}, {"name": "monotonicTimestamp", "type": "number", "optional": true, "description": "Frame swap timestamp as monotonic time.", "typeRef": "Network.MonotonicTime"}]);
1187
1187
  inspectorBackend.registerType("Page.AppManifestError", [{"name": "message", "type": "string", "optional": false, "description": "Error message.", "typeRef": null}, {"name": "critical", "type": "number", "optional": false, "description": "If critical, this is a non-recoverable parse error.", "typeRef": null}, {"name": "line", "type": "number", "optional": false, "description": "Error line.", "typeRef": null}, {"name": "column", "type": "number", "optional": false, "description": "Error column.", "typeRef": null}]);
1188
1188
  inspectorBackend.registerType("Page.AppManifestParsedProperties", [{"name": "scope", "type": "string", "optional": false, "description": "Computed scope value", "typeRef": null}]);
1189
1189
  inspectorBackend.registerType("Page.LayoutViewport", [{"name": "pageX", "type": "number", "optional": false, "description": "Horizontal offset relative to the document (CSS pixels).", "typeRef": null}, {"name": "pageY", "type": "number", "optional": false, "description": "Vertical offset relative to the document (CSS pixels).", "typeRef": null}, {"name": "clientWidth", "type": "number", "optional": false, "description": "Width (CSS pixels), excludes scrollbar if present.", "typeRef": null}, {"name": "clientHeight", "type": "number", "optional": false, "description": "Height (CSS pixels), excludes scrollbar if present.", "typeRef": null}]);
@@ -1362,6 +1362,7 @@ inspectorBackend.registerCommand("Storage.untrackIndexedDBForStorageKey", [{"nam
1362
1362
  inspectorBackend.registerCommand("Storage.getTrustTokens", [], ["tokens"], "Returns the number of stored Trust Tokens per issuer for the current browsing context.");
1363
1363
  inspectorBackend.registerCommand("Storage.clearTrustTokens", [{"name": "issuerOrigin", "type": "string", "optional": false, "description": "", "typeRef": null}], ["didDeleteTokens"], "Removes all Trust Tokens issued by the provided issuerOrigin. Leaves other stored data, including the issuer's Redemption Records, intact.");
1364
1364
  inspectorBackend.registerCommand("Storage.getPrivateVerificationTokens", [], ["tokens"], "Returns all stored Private Verification Tokens for the current browsing context.");
1365
+ inspectorBackend.registerCommand("Storage.getPrivateVerificationTokensIssuerConfigs", [], ["configs"], "Returns the configured Private Verification Tokens issuers and their redeemer origins.");
1365
1366
  inspectorBackend.registerCommand("Storage.clearPrivateVerificationTokens", [{"name": "issuerOrigin", "type": "string", "optional": false, "description": "", "typeRef": null}], [], "Removes all Private Verification Tokens issued by the provided issuerOrigin.");
1366
1367
  inspectorBackend.registerCommand("Storage.deletePrivateVerificationToken", [{"name": "tokenId", "type": "string", "optional": false, "description": "", "typeRef": null}], [], "Removes a specific Private Verification Token by its ID.");
1367
1368
  inspectorBackend.registerCommand("Storage.setPrivateVerificationTokensTracking", [{"name": "enable", "type": "boolean", "optional": false, "description": "", "typeRef": null}], [], "Set tracking for Private Verification Tokens.");
@@ -1371,6 +1372,7 @@ inspectorBackend.registerCommand("Storage.runBounceTrackingMitigations", [], ["d
1371
1372
  inspectorBackend.registerType("Storage.UsageForType", [{"name": "storageType", "type": "string", "optional": false, "description": "Name of storage type.", "typeRef": "Storage.StorageType"}, {"name": "usage", "type": "number", "optional": false, "description": "Storage usage (bytes).", "typeRef": null}]);
1372
1373
  inspectorBackend.registerType("Storage.TrustTokens", [{"name": "issuerOrigin", "type": "string", "optional": false, "description": "", "typeRef": null}, {"name": "count", "type": "number", "optional": false, "description": "", "typeRef": null}]);
1373
1374
  inspectorBackend.registerType("Storage.PrivateVerificationToken", [{"name": "id", "type": "string", "optional": false, "description": "Unique identifier of the token in the database.", "typeRef": null}, {"name": "issuerOrigin", "type": "string", "optional": false, "description": "Origin of the token issuer.", "typeRef": null}, {"name": "keyId", "type": "number", "optional": false, "description": "Public key ID used to issue the token.", "typeRef": null}, {"name": "expiration", "type": "number", "optional": false, "description": "Expiration timestamp in seconds since the epoch.", "typeRef": "Network.TimeSinceEpoch"}, {"name": "creationTime", "type": "number", "optional": false, "description": "Token creation timestamp in seconds since the epoch.", "typeRef": "Network.TimeSinceEpoch"}, {"name": "version", "type": "number", "optional": false, "description": "Token protocol version.", "typeRef": null}, {"name": "token", "type": "string", "optional": false, "description": "Base64-encoded serialized token.", "typeRef": null}]);
1375
+ inspectorBackend.registerType("Storage.PrivateVerificationTokensIssuerConfig", [{"name": "issuerOrigin", "type": "string", "optional": false, "description": "Origin of the token issuer.", "typeRef": null}, {"name": "redeemerOrigins", "type": "array", "optional": false, "description": "Origins authorized to redeem tokens from this issuer.", "typeRef": "string"}]);
1374
1376
  inspectorBackend.registerType("Storage.StorageBucket", [{"name": "storageKey", "type": "string", "optional": false, "description": "", "typeRef": "Storage.SerializedStorageKey"}, {"name": "name", "type": "string", "optional": true, "description": "If not specified, it is the default bucket of the storageKey.", "typeRef": null}]);
1375
1377
  inspectorBackend.registerType("Storage.StorageBucketInfo", [{"name": "bucket", "type": "object", "optional": false, "description": "", "typeRef": "Storage.StorageBucket"}, {"name": "id", "type": "string", "optional": false, "description": "", "typeRef": null}, {"name": "expiration", "type": "number", "optional": false, "description": "", "typeRef": "Network.TimeSinceEpoch"}, {"name": "quota", "type": "number", "optional": false, "description": "Storage quota (bytes).", "typeRef": null}, {"name": "persistent", "type": "boolean", "optional": false, "description": "", "typeRef": null}, {"name": "durability", "type": "string", "optional": false, "description": "", "typeRef": "Storage.StorageBucketsDurability"}]);
1376
1378
 
@@ -4954,6 +4954,14 @@ export namespace ProtocolMapping {
4954
4954
  paramsType: [];
4955
4955
  returnType: Protocol.Storage.GetPrivateVerificationTokensResponse;
4956
4956
  };
4957
+ /**
4958
+ * Returns the configured Private Verification Tokens issuers and their redeemer
4959
+ * origins.
4960
+ */
4961
+ 'Storage.getPrivateVerificationTokensIssuerConfigs': {
4962
+ paramsType: [];
4963
+ returnType: Protocol.Storage.GetPrivateVerificationTokensIssuerConfigsResponse;
4964
+ };
4957
4965
  /**
4958
4966
  * Removes all Private Verification Tokens issued by the provided issuerOrigin.
4959
4967
  */
@@ -4331,6 +4331,12 @@ declare namespace ProtocolProxyApi {
4331
4331
  */
4332
4332
  invoke_getPrivateVerificationTokens(): Promise<Protocol.Storage.GetPrivateVerificationTokensResponse>;
4333
4333
 
4334
+ /**
4335
+ * Returns the configured Private Verification Tokens issuers and their redeemer
4336
+ * origins.
4337
+ */
4338
+ invoke_getPrivateVerificationTokensIssuerConfigs(): Promise<Protocol.Storage.GetPrivateVerificationTokensIssuerConfigsResponse>;
4339
+
4334
4340
  /**
4335
4341
  * Removes all Private Verification Tokens issued by the provided issuerOrigin.
4336
4342
  */
@@ -15133,6 +15133,10 @@ export namespace Page {
15133
15133
  * Frame swap timestamp.
15134
15134
  */
15135
15135
  timestamp?: Network.TimeSinceEpoch;
15136
+ /**
15137
+ * Frame swap timestamp as monotonic time.
15138
+ */
15139
+ monotonicTimestamp?: Network.MonotonicTime;
15136
15140
  }
15137
15141
 
15138
15142
  /**
@@ -18504,6 +18508,20 @@ export namespace Storage {
18504
18508
  token: string;
18505
18509
  }
18506
18510
 
18511
+ /**
18512
+ * Configuration for a Private Verification Tokens issuer.
18513
+ */
18514
+ export interface PrivateVerificationTokensIssuerConfig {
18515
+ /**
18516
+ * Origin of the token issuer.
18517
+ */
18518
+ issuerOrigin: string;
18519
+ /**
18520
+ * Origins authorized to redeem tokens from this issuer.
18521
+ */
18522
+ redeemerOrigins: string[];
18523
+ }
18524
+
18507
18525
  export const enum StorageBucketsDurability {
18508
18526
  Relaxed = 'relaxed',
18509
18527
  Strict = 'strict',
@@ -18717,6 +18735,10 @@ export namespace Storage {
18717
18735
  tokens: PrivateVerificationToken[];
18718
18736
  }
18719
18737
 
18738
+ export interface GetPrivateVerificationTokensIssuerConfigsResponse extends ProtocolResponseWithError {
18739
+ configs: PrivateVerificationTokensIssuerConfig[];
18740
+ }
18741
+
18720
18742
  export interface ClearPrivateVerificationTokensRequest {
18721
18743
  issuerOrigin: string;
18722
18744
  }
@@ -348,7 +348,7 @@ export class CompilerScriptMapping implements DebuggerSourceMapping {
348
348
  return false;
349
349
  }
350
350
  const {sourceMap, script} = sourceMapAndScript;
351
- const {lineNumber, columnNumber} = script.relativeLocationToRawLocation(frame);
351
+ const {lineNumber, columnNumber} = script.rawLocationToRelativeLocation(frame);
352
352
 
353
353
  if (!sourceMap.isOutlinedFrame(lineNumber, columnNumber)) {
354
354
  const frames = sourceMap.translateCallSite(lineNumber, columnNumber);
@@ -87,8 +87,13 @@ export class CD4ABridge extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
87
87
 
88
88
  #formatCommentText(thread: CommentManager.CommentThread): string {
89
89
  const rawText = thread.comments[0]?.text ?? '';
90
+
90
91
  const details: string[] = [];
91
92
 
93
+ if (thread.anchor.vePath) {
94
+ details.push(`- DevTools VEPath: ${thread.anchor.vePath}`);
95
+ }
96
+
92
97
  if (thread.anchor.textSignature) {
93
98
  details.push(`- DevTools element: ${thread.anchor.textSignature}`);
94
99
  }
@@ -2,6 +2,7 @@
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
6
  import type * as Platform from '../../core/platform/platform.js';
6
7
  import * as SDK from '../../core/sdk/sdk.js';
7
8
  import type * as Protocol from '../../generated/protocol.js';
@@ -420,7 +421,9 @@ export class HARInitiator extends HARBase {
420
421
  super(data);
421
422
  this.type = (HARBase.optionalString(data['type']) ?? SDK.NetworkRequest.InitiatorType.OTHER) as
422
423
  Protocol.Network.InitiatorType;
423
- this.url = HARBase.optionalString(data['url']);
424
+ const url = HARBase.optionalString(data['url']);
425
+ // Ignore privileged/non-web schemes in imported HARs.
426
+ this.url = url && Common.ParsedURL.hasWebSafeScheme(url) ? url : undefined;
424
427
  this.lineNumber = HARBase.optionalNumber(data['lineNumber']);
425
428
  this.requestId = HARBase.optionalString(data['requestId']) as Protocol.Network.RequestId;
426
429
  if (data['stack']) {
@@ -477,7 +480,9 @@ export class HARCallFrame extends HARBase {
477
480
 
478
481
  this.functionName = HARBase.optionalString(data['functionName']) ?? '';
479
482
  this.scriptId = (HARBase.optionalString(data['scriptId']) ?? '') as Protocol.Runtime.ScriptId;
480
- this.url = HARBase.optionalString(data['url']) ?? '';
483
+ const url = HARBase.optionalString(data['url']) ?? '';
484
+ // Ignore privileged/non-web schemes in imported HARs.
485
+ this.url = Common.ParsedURL.hasWebSafeScheme(url) ? url : '';
481
486
  this.lineNumber = HARBase.optionalNumber(data['lineNumber']) ?? -1;
482
487
  this.columnNumber = HARBase.optionalNumber(data['columnNumber']) ?? -1;
483
488
  }
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as Platform from '../../core/platform/platform.js';
6
+ import * as TextUtils from '../../core/text_utils/text_utils.js';
6
7
  import * as Marked from '../../third_party/marked/marked.js';
7
8
 
8
9
  /**
@@ -75,9 +76,8 @@ export async function getMarkdownFileContent(filename: string): Promise<string>
75
76
 
76
77
  export async function createIssueDescriptionFromMarkdown(description: MarkdownIssueDescription):
77
78
  Promise<IssueDescription> {
78
- const rawMarkdown = await getMarkdownFileContent(description.file);
79
- const rawMarkdownWithPlaceholdersReplaced = substitutePlaceholders(rawMarkdown, description.substitutions);
80
- return createIssueDescriptionFromRawMarkdown(rawMarkdownWithPlaceholdersReplaced, description);
79
+ const markdown = await getMarkdownFileContent(description.file);
80
+ return createIssueDescriptionFromRawMarkdown(markdown, description);
81
81
  }
82
82
 
83
83
  /**
@@ -85,7 +85,7 @@ export async function createIssueDescriptionFromMarkdown(description: MarkdownIs
85
85
  */
86
86
  export function createIssueDescriptionFromRawMarkdown(
87
87
  markdown: string, description: MarkdownIssueDescription): IssueDescription {
88
- const markdownAst = Marked.Marked.lexer(markdown);
88
+ const markdownAst = TextUtils.Markdown.tokenizeWithPlaceholders(markdown, description.substitutions);
89
89
  const markdownTitle = findTitleFromMarkdownAst(markdownAst);
90
90
  if (!markdownTitle) {
91
91
  throw new Error('Markdown issue descriptions must start with a heading');
@@ -99,49 +99,6 @@ export function createIssueDescriptionFromRawMarkdown(
99
99
  };
100
100
  }
101
101
 
102
- const validPlaceholderMatchPattern = /\{(PLACEHOLDER_[a-zA-Z][a-zA-Z0-9]*)\}/g;
103
- const validPlaceholderNamePattern = /PLACEHOLDER_[a-zA-Z][a-zA-Z0-9]*/;
104
-
105
- /**
106
- * Replaces placeholders in markdown text with a string provided by the
107
- * `substitutions` map. To keep mental overhead to a minimum, the same
108
- * syntax is used as for l10n placeholders. Please note that the
109
- * placeholders require a mandatory 'PLACEHOLDER_' prefix.
110
- *
111
- * Example:
112
- * const str = "This is markdown with `code` and two placeholders, namely {PLACEHOLDER_PH1} and {PLACEHOLDER_PH2}".
113
- * const result = substitutePlaceholders(str, new Map([['PLACEHOLDER_PH1', 'foo'], ['PLACEHOLDER_PH2', 'bar']]));
114
- *
115
- * Exported only for unit testing.
116
- */
117
- export function substitutePlaceholders(markdown: string, substitutions?: Map<string, string>): string {
118
- const unusedPlaceholders = new Set(substitutions ? substitutions.keys() : []);
119
- validatePlaceholders(unusedPlaceholders);
120
-
121
- const result = markdown.replace(validPlaceholderMatchPattern, (_, placeholder) => {
122
- const replacement = substitutions ? substitutions.get(placeholder) : undefined;
123
- if (replacement === undefined) {
124
- throw new Error(`No replacement provided for placeholder '${placeholder}'.`);
125
- }
126
- unusedPlaceholders.delete(placeholder);
127
- return replacement;
128
- });
129
-
130
- if (unusedPlaceholders.size > 0) {
131
- throw new Error(`Unused replacements provided: ${[...unusedPlaceholders]}`);
132
- }
133
-
134
- return result;
135
- }
136
-
137
- /** Ensure that all provided placeholders match the naming pattern. **/
138
- function validatePlaceholders(placeholders: Set<string>): void {
139
- const invalidPlaceholders = [...placeholders].filter(placeholder => !validPlaceholderNamePattern.test(placeholder));
140
- if (invalidPlaceholders.length > 0) {
141
- throw new Error(`Invalid placeholders provided in the substitutions map: ${invalidPlaceholders}`);
142
- }
143
- }
144
-
145
102
  export function findTitleFromMarkdownAst(markdownAst: Marked.Marked.Token[]): string|null {
146
103
  if (markdownAst.length === 0 || markdownAst[0].type !== 'heading' || markdownAst[0].depth !== 1) {
147
104
  return null;
@@ -64,7 +64,7 @@ export class FileSystem implements Workspace.Workspace.Project {
64
64
  }
65
65
 
66
66
  displayName(): string {
67
- const {root} = this.automaticFileSystem;
67
+ const root = this.automaticFileSystem.root.replace(Host.Platform.isWin() ? /[/\\]+$/ : /\/+$/, '');
68
68
  let slash = root.lastIndexOf('/');
69
69
  if (slash === -1 && Host.Platform.isWin()) {
70
70
  slash = root.lastIndexOf('\\');