chrome-devtools-frontend 1.0.1700561 → 1.0.1701636

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 (53) hide show
  1. package/front_end/core/root/Runtime.ts +5 -0
  2. package/front_end/core/sdk/CSSPropertyParserMatchers.ts +16 -11
  3. package/front_end/core/sdk/NetworkManager.ts +7 -0
  4. package/front_end/core/sdk/SecurityOrigin.ts +1 -0
  5. package/front_end/core/sdk/SourceMap.ts +12 -1
  6. package/front_end/core/sdk/SourceMapCache.ts +12 -0
  7. package/front_end/core/sdk/SourceMapManager.ts +17 -8
  8. package/front_end/core/sdk/SourceMapScopeChainEntry.ts +2 -19
  9. package/front_end/core/sdk/SourceMapScopesInfo.ts +21 -0
  10. package/front_end/entrypoints/formatter_worker/ScopeParser.ts +7 -3
  11. package/front_end/entrypoints/formatter_worker/Substitute.ts +90 -26
  12. package/front_end/entrypoints/formatter_worker/formatter_worker-entrypoint.ts +1 -1
  13. package/front_end/entrypoints/main/ExecutionContextSelector.ts +17 -4
  14. package/front_end/generated/InspectorBackendCommands.ts +1 -3
  15. package/front_end/generated/protocol-mapping.d.ts +0 -8
  16. package/front_end/generated/protocol-proxy-api.d.ts +0 -6
  17. package/front_end/generated/protocol.ts +4 -22
  18. package/front_end/models/ai_assistance/debug.ts +10 -2
  19. package/front_end/models/comment_manager/CD4ABridge.ts +2 -2
  20. package/front_end/models/comment_manager/CommentManager.ts +3 -0
  21. package/front_end/models/formatter/FormatterWorkerPool.ts +2 -2
  22. package/front_end/models/javascript_metadata/NativeFunctions.ts +7 -2
  23. package/front_end/models/source_map_scopes/NamesResolver.ts +42 -39
  24. package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +70 -1
  25. package/front_end/panels/ai_assistance/components/ChatMessage.ts +1 -1
  26. package/front_end/panels/common/CommentsOverlayWidget.ts +29 -2
  27. package/front_end/panels/common/ExtensionServer.ts +18 -8
  28. package/front_end/panels/console/ConsolePrompt.ts +1 -1
  29. package/front_end/panels/console/ConsoleViewMessage.ts +1 -1
  30. package/front_end/panels/elements/ClassesPaneWidget.ts +2 -1
  31. package/front_end/panels/elements/DOMTreeContextMenu.ts +1 -1
  32. package/front_end/panels/elements/{ElementsTreeOutline.ts → DOMTreeWidget.ts} +34 -24
  33. package/front_end/panels/elements/ElementsPanel.ts +1 -1
  34. package/front_end/panels/elements/ElementsTreeElement.ts +1 -1
  35. package/front_end/panels/elements/ShortcutTreeElement.ts +1 -1
  36. package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
  37. package/front_end/panels/elements/StylesSidebarPane.ts +4 -6
  38. package/front_end/panels/elements/TopLayerContainer.ts +1 -1
  39. package/front_end/panels/elements/elements.ts +4 -3
  40. package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +1 -1
  41. package/front_end/panels/profiler/HeapProfileView.ts +8 -6
  42. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +8 -1
  43. package/front_end/panels/profiler/HeapSnapshotView.ts +6 -4
  44. package/front_end/panels/security/SecurityPanel.ts +87 -99
  45. package/front_end/panels/sensors/SensorsView.ts +87 -0
  46. package/front_end/panels/sensors/sensors-meta.ts +69 -1
  47. package/front_end/panels/settings/keybindsSettingsTab.css +1 -1
  48. package/front_end/third_party/chromium/README.chromium +1 -1
  49. package/front_end/ui/legacy/Treeoutline.ts +2 -1
  50. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +1 -1
  51. package/front_end/ui/legacy/treeoutline.css +2 -2
  52. package/front_end/ui/visual_logging/KnownContextValues.ts +7 -0
  53. package/package.json +1 -1
@@ -18532,24 +18532,6 @@ export namespace Storage {
18532
18532
  durability: StorageBucketsDurability;
18533
18533
  }
18534
18534
 
18535
- /**
18536
- * A single Related Website Set object.
18537
- */
18538
- export interface RelatedWebsiteSet {
18539
- /**
18540
- * The primary site of this set, along with the ccTLDs if there is any.
18541
- */
18542
- primarySites: string[];
18543
- /**
18544
- * The associated sites of this set, along with the ccTLDs if there is any.
18545
- */
18546
- associatedSites: string[];
18547
- /**
18548
- * The service sites of this set, along with the ccTLDs if there is any.
18549
- */
18550
- serviceSites: string[];
18551
- }
18552
-
18553
18535
  export interface GetStorageKeyForFrameRequest {
18554
18536
  frameId: Page.FrameId;
18555
18537
  }
@@ -18763,10 +18745,6 @@ export namespace Storage {
18763
18745
  deletedSites: string[];
18764
18746
  }
18765
18747
 
18766
- export interface GetRelatedWebsiteSetsResponse extends ProtocolResponseWithError {
18767
- sets: RelatedWebsiteSet[];
18768
- }
18769
-
18770
18748
  /**
18771
18749
  * A cache's contents have been modified.
18772
18750
  */
@@ -20429,6 +20407,10 @@ export namespace WebMCP {
20429
20407
  * A hint indicating that executing the tool will result in consequential actions, ex: booking a flight, transferring money.
20430
20408
  */
20431
20409
  consequential?: boolean;
20410
+ /**
20411
+ * If true, indicates that the tool is intended for debugging and developer tooling rather than end-user interactions.
20412
+ */
20413
+ debugging?: boolean;
20432
20414
  /**
20433
20415
  * If the declarative tool was declared with the autosubmit attribute.
20434
20416
  */
@@ -7,11 +7,19 @@
7
7
  */
8
8
 
9
9
  export function isDebugMode(): boolean {
10
- return Boolean(localStorage.getItem('debugAiAssistancePanelEnabled'));
10
+ try {
11
+ return Boolean(localStorage.getItem('debugAiAssistancePanelEnabled'));
12
+ } catch {
13
+ return false;
14
+ }
11
15
  }
12
16
 
13
17
  export function isStructuredLogEnabled(): boolean {
14
- return Boolean(localStorage.getItem('aiAssistanceStructuredLogEnabled'));
18
+ try {
19
+ return Boolean(localStorage.getItem('aiAssistanceStructuredLogEnabled'));
20
+ } catch {
21
+ return false;
22
+ }
15
23
  }
16
24
 
17
25
  export function debugLog(...log: unknown[]): void {
@@ -72,7 +72,7 @@ export class CD4ABridge extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
72
72
  if (!this.#targetManager) {
73
73
  return undefined;
74
74
  }
75
- const target = this.#targetManager.targetById(nodeSignature.targetId) ?? this.#targetManager.primaryPageTarget();
75
+ const target = this.#targetManager.targetById(nodeSignature.targetId);
76
76
  const domModel = target?.model(SDK.DOMModel.DOMModel);
77
77
  if (!domModel) {
78
78
  return undefined;
@@ -156,7 +156,7 @@ export class CD4ABridge extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
156
156
  }
157
157
 
158
158
  if (target?.node && this.#targetManager) {
159
- const sdkTarget = this.#targetManager.targetById(target.node.targetId) ?? this.#targetManager.primaryPageTarget();
159
+ const sdkTarget = this.#targetManager.targetById(target.node.targetId);
160
160
  const domModel = sdkTarget?.model(SDK.DOMModel.DOMModel);
161
161
  if (domModel) {
162
162
  const cdpNodeId = target.node.backendNodeId as Protocol.DOM.BackendNodeId;
@@ -74,6 +74,9 @@ export class CommentManager extends Common.ObjectWrapper.ObjectWrapper<EventType
74
74
  }
75
75
 
76
76
  setCommentMode(active: boolean): void {
77
+ if (active && !this.#agentAttached) {
78
+ return;
79
+ }
77
80
  if (this.#commentMode === active) {
78
81
  return;
79
82
  }
@@ -134,8 +134,8 @@ export class FormatterWorkerPool {
134
134
  return this.runTask(FormatterActions.FormatterActions.FORMAT, parameters) as Promise<FormatterActions.FormatResult>;
135
135
  }
136
136
 
137
- javaScriptSubstitute(expression: string, mapping: Map<string, string|null>): Promise<string> {
138
- if (mapping.size === 0) {
137
+ javaScriptSubstitute(expression: string, mapping: Array<Map<string, string|null>>): Promise<string> {
138
+ if (mapping.every(m => m.size === 0)) {
139
139
  return Promise.resolve(expression);
140
140
  }
141
141
  return this.runTask(FormatterActions.FormatterActions.JAVASCRIPT_SUBSTITUTE, {content: expression, mapping})
@@ -1367,6 +1367,11 @@ export const NativeFunctions: readonly NativeFunctionValue[] = [
1367
1367
  signatures: [["message","?options"],["message","transfer"],["message","targetOrigin","?transfer"]],
1368
1368
  receivers: ["Window"]
1369
1369
  },
1370
+ {
1371
+ name: "postMessage",
1372
+ signatures: [["message","targetOrigin"],["message","?options"]],
1373
+ receivers: ["HTMLPersistentWidgetElement","PersistentWidgetOpener"]
1374
+ },
1370
1375
  {
1371
1376
  name: "deleteRule",
1372
1377
  signatures: [["index"]],
@@ -2050,7 +2055,7 @@ export const NativeFunctions: readonly NativeFunctionValue[] = [
2050
2055
  },
2051
2056
  {
2052
2057
  name: "write",
2053
- signatures: [["...text"],["text"],["text1","...text"]],
2058
+ signatures: [["...text"]],
2054
2059
  receivers: ["Document"]
2055
2060
  },
2056
2061
  {
@@ -2489,7 +2494,7 @@ export const NativeFunctions: readonly NativeFunctionValue[] = [
2489
2494
  },
2490
2495
  {
2491
2496
  name: "writeln",
2492
- signatures: [["...text"],["text"],["text1","...text"]]
2497
+ signatures: [["...text"]]
2493
2498
  },
2494
2499
  {
2495
2500
  name: "elementFromPoint",
@@ -15,7 +15,7 @@ interface CachedScopeMap {
15
15
  }
16
16
 
17
17
  const scopeToCachedIdentifiersMap = new WeakMap<Formatter.FormatterWorkerPool.ScopeTreeNode, CachedScopeMap>();
18
- const cachedMapByCallFrame = new WeakMap<SDK.DebuggerModel.CallFrame, Map<string, string|null>>();
18
+ const cachedMapByCallFrame = new WeakMap<SDK.DebuggerModel.CallFrame, Array<Map<string, string|null>>>();
19
19
 
20
20
  export async function getTextFor(contentProvider: TextUtils.ContentProvider.ContentProvider):
21
21
  Promise<TextUtils.Text.Text|null> {
@@ -401,56 +401,61 @@ export const resolveScopeChain =
401
401
  return scopes.map(scope => new ScopeWithSourceMappedVariables(scope, thisObject, debuggerWorkspaceBinding));
402
402
  };
403
403
 
404
+ function reverseScopeMapping(variableMapping: Map<string, string>): Map<string, string|null> {
405
+ const result = new Map<string, string|null>();
406
+ for (const [compiledName, originalName] of variableMapping) {
407
+ if (originalName && !result.has(originalName)) {
408
+ result.set(originalName, compiledName);
409
+ }
410
+ }
411
+ return result;
412
+ }
413
+
404
414
  /**
405
- * @returns A mapping from original name -> compiled name. If the orignal name is unavailable (e.g. because the compiled name was
406
- * shadowed) we set it to `null`.
415
+ * @returns An array of mappings (from inner-most to outer-most scope) of original name -> compiled name or binding expression.
407
416
  */
408
417
  export const allVariablesInCallFrame = async(
409
418
  callFrame: SDK.DebuggerModel.CallFrame,
410
419
  debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
411
- Promise<Map<string, string|null>> => {
420
+ Promise<Array<Map<string, string|null>>> => {
412
421
  if (!callFrame.debuggerModel.target()
413
422
  .targetManager()
414
423
  .settings.resolve(SDK.SDKSettings.jsSourceMapsEnabledSettingDescriptor)
415
424
  .get()) {
416
- return new Map<string, string|null>();
425
+ return [];
417
426
  }
418
- const cachedMap = cachedMapByCallFrame.get(callFrame);
419
- if (cachedMap) {
420
- return cachedMap;
427
+ const cached = cachedMapByCallFrame.get(callFrame);
428
+ if (cached) {
429
+ return cached;
430
+ }
431
+
432
+ if (Root.Runtime.hostConfig.devToolsSourceMapScopesInSourcesPanel?.enabled) {
433
+ const sourceMap = callFrame.script.sourceMap() ??
434
+ await callFrame.debuggerModel.sourceMapManager().sourceMapForClientPromise(callFrame.script);
435
+ const mappedVariables =
436
+ sourceMap?.resolveMappedVariablesAtPosition(callFrame.location(), callFrame.returnValue() !== null);
437
+ if (mappedVariables) {
438
+ cachedMapByCallFrame.set(callFrame, mappedVariables);
439
+ return mappedVariables;
440
+ }
421
441
  }
422
442
 
423
443
  const scopeChain = callFrame.scopeChain().filter(scope => !scope.empty());
424
444
  const nameMappings =
425
445
  await Promise.all(scopeChain.map(scope => resolveDebuggerScope(scope, debuggerWorkspaceBinding)));
426
- const reverseMapping = new Map<string, string|null>();
427
- const compiledNames = new Set<string>();
428
- for (const {variableMapping} of nameMappings) {
429
- for (const [compiledName, originalName] of variableMapping) {
430
- if (!originalName) {
431
- continue;
432
- }
433
- if (!reverseMapping.has(originalName)) {
434
- // An inner scope might have shadowed {compiledName}. Mark it as "unavailable" in that case.
435
- const compiledNameOrNull = compiledNames.has(compiledName) ? null : compiledName;
436
- reverseMapping.set(originalName, compiledNameOrNull);
437
- }
438
- compiledNames.add(compiledName);
439
- }
440
- }
446
+ const reverseMapping = nameMappings.map(({variableMapping}) => reverseScopeMapping(variableMapping));
441
447
  cachedMapByCallFrame.set(callFrame, reverseMapping);
442
448
  return reverseMapping;
443
449
  };
444
450
 
445
451
  /**
446
- * @returns A mapping from original name -> compiled name. If the orignal name is unavailable (e.g. because the compiled name was
447
- * shadowed) we set it to `null`.
452
+ * @returns An array of mappings (from inner-most to outer-most scope) of original name -> compiled name or binding expression.
448
453
  */
449
454
  export const allVariablesAtPosition =
450
455
  async(location: SDK.DebuggerModel.Location,
451
456
  debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding):
452
- Promise<Map<string, string|null>> => {
453
- const reverseMapping = new Map<string, string|null>();
457
+ Promise<Array<Map<string, string|null>>> => {
458
+ const reverseMapping: Array<Map<string, string|null>> = [];
454
459
  const script = location.script();
455
460
  if (!script) {
456
461
  return reverseMapping;
@@ -462,6 +467,15 @@ export const allVariablesAtPosition =
462
467
  return reverseMapping;
463
468
  }
464
469
 
470
+ if (Root.Runtime.hostConfig.devToolsSourceMapScopesInSourcesPanel?.enabled) {
471
+ const sourceMap =
472
+ script.sourceMap() ?? await script.debuggerModel.sourceMapManager().sourceMapForClientPromise(script);
473
+ const mappedVariables = sourceMap?.resolveMappedVariablesAtPosition(location);
474
+ if (mappedVariables) {
475
+ return mappedVariables;
476
+ }
477
+ }
478
+
465
479
  const scopeTreeAndText = await computeScopeTree(script);
466
480
  if (!scopeTreeAndText) {
467
481
  return reverseMapping;
@@ -470,21 +484,10 @@ export const allVariablesAtPosition =
470
484
  const {scopeTree, text} = scopeTreeAndText;
471
485
  const locationOffset = text.offsetFromPosition(location.lineNumber, location.columnNumber);
472
486
  const scopeChain = findScopeChain(scopeTree, {start: locationOffset, end: locationOffset});
473
- const compiledNames = new Set<string>();
474
487
 
475
488
  while (scopeChain.length > 0) {
476
489
  const {variableMapping} = await resolveScope(script, scopeChain, debuggerWorkspaceBinding);
477
- for (const [compiledName, originalName] of variableMapping) {
478
- if (!originalName) {
479
- continue;
480
- }
481
- if (!reverseMapping.has(originalName)) {
482
- // An inner scope might have shadowed {compiledName}. Mark it as "unavailable" in that case.
483
- const compiledNameOrNull = compiledNames.has(compiledName) ? null : compiledName;
484
- reverseMapping.set(originalName, compiledNameOrNull);
485
- }
486
- compiledNames.add(compiledName);
487
- }
490
+ reverseMapping.push(reverseScopeMapping(variableMapping));
488
491
  scopeChain.pop();
489
492
  }
490
493
  return reverseMapping;
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type * as Common from '../../core/common/common.js';
6
6
  import * as i18n from '../../core/i18n/i18n.js';
7
+ import * as Platform from '../../core/platform/platform.js';
7
8
  import * as SDK from '../../core/sdk/sdk.js';
8
9
  import type * as Protocol from '../../generated/protocol.js';
9
10
  import * as Buttons from '../../ui/components/buttons/buttons.js';
@@ -35,6 +36,10 @@ const UIStrings = {
35
36
  * @description Tooltip for the clear announcements button in the announcements tool.
36
37
  */
37
38
  clearAnnouncements: 'Clear announcements',
39
+ /**
40
+ * @description Tooltip for the export to CSV button in the announcements tool.
41
+ */
42
+ exportCsv: 'Export to CSV',
38
43
  /**
39
44
  * @description Label/title for the dropdown filter to select which announcement types to record.
40
45
  */
@@ -108,6 +113,8 @@ export interface ViewInput {
108
113
  isRecording: boolean;
109
114
  onToggleRecording: () => void;
110
115
  onClear: () => void;
116
+ onExportCsv: () => void;
117
+ canExport: boolean;
111
118
  recordTypeFilter: RecordTypeFilter;
112
119
  onRecordTypeFilterChange: (type: RecordTypeFilter) => void;
113
120
  textFilter: string;
@@ -144,6 +151,14 @@ export const DEFAULT_VIEW: View = (input, _output, target) => {
144
151
  .variant=${Buttons.Button.Variant.TOOLBAR}
145
152
  .jslogContext=${'accessibility.clear-announcements'}>
146
153
  </devtools-button>
154
+ <devtools-button
155
+ title=${i18nString(UIStrings.exportCsv)}
156
+ .iconName=${'download'}
157
+ .disabled=${!input.canExport}
158
+ @click=${input.onExportCsv}
159
+ .variant=${Buttons.Button.Variant.TOOLBAR}
160
+ .jslogContext=${'accessibility.export-csv'}>
161
+ </devtools-button>
147
162
  <div class="toolbar-divider" role="separator"></div>
148
163
  <select
149
164
  title=${i18nString(UIStrings.filterByType)}
@@ -729,6 +744,32 @@ export function validateAndSanitizeAnnouncement(payload: unknown): A11yAnnouncem
729
744
  };
730
745
  }
731
746
 
747
+ export function escapeCsvValue(val: string): string {
748
+ const escaped = val.replace(/"/g, '""');
749
+ if (escaped.includes(',') || escaped.includes('\n') || escaped.includes('\r') || escaped.includes('"')) {
750
+ return `"${escaped}"`;
751
+ }
752
+ return escaped;
753
+ }
754
+
755
+ export function buildCsvContent(announcements: readonly A11yAnnouncement[]): string {
756
+ const csvRows: string[] = [];
757
+ csvRows.push(['Time', 'API', 'Politeness', 'Message'].join(','));
758
+
759
+ for (const item of announcements) {
760
+ const timeString = new Date(item.time).toISOString();
761
+ const row = [
762
+ escapeCsvValue(timeString),
763
+ escapeCsvValue(item.api),
764
+ escapeCsvValue(item.politeness),
765
+ escapeCsvValue(item.message),
766
+ ];
767
+ csvRows.push(row.join(','));
768
+ }
769
+
770
+ return csvRows.join('\r\n');
771
+ }
772
+
732
773
  export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane implements SDK.TargetManager.Observer {
733
774
  #announcements: A11yAnnouncement[] = [];
734
775
  #filteredAnnouncements: readonly A11yAnnouncement[]|null = null;
@@ -984,6 +1025,29 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
984
1025
  this.requestUpdate();
985
1026
  }
986
1027
 
1028
+ #exportCsv(): void {
1029
+ const csvContent = this.#buildCsvContent();
1030
+ const blob = new Blob(['\ufeff', csvContent], {type: 'text/csv;charset=utf-8'});
1031
+ const url = URL.createObjectURL(blob);
1032
+ /* eslint-disable-next-line @devtools/no-imperative-dom-api */
1033
+ const link = document.createElement('a');
1034
+ link.setAttribute('href', url);
1035
+ link.setAttribute('download', `aria-live-announcements-${Platform.DateUtilities.toISO8601Compact(new Date())}.csv`);
1036
+ link.style.visibility = 'hidden';
1037
+ document.body.appendChild(link);
1038
+ link.click();
1039
+ document.body.removeChild(link);
1040
+ URL.revokeObjectURL(url);
1041
+ }
1042
+
1043
+ #buildCsvContent(): string {
1044
+ return buildCsvContent(this.filteredAnnouncements);
1045
+ }
1046
+
1047
+ exportCsvForTest(): string {
1048
+ return this.#buildCsvContent();
1049
+ }
1050
+
987
1051
  override performUpdate(): void {
988
1052
  const blockedTargets: BlockedTargetInfo[] = [];
989
1053
  for (const [target, reason] of this.#blockedTargets) {
@@ -991,6 +1055,7 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
991
1055
  blockedTargets.push({targetName, reason});
992
1056
  }
993
1057
 
1058
+ const filteredAnnouncements = this.filteredAnnouncements;
994
1059
  const input: ViewInput = {
995
1060
  isRecording: this.#isRecording,
996
1061
  onToggleRecording: () => {
@@ -1003,6 +1068,10 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
1003
1068
  onClear: () => {
1004
1069
  this.clearAnnouncements();
1005
1070
  },
1071
+ onExportCsv: () => {
1072
+ this.#exportCsv();
1073
+ },
1074
+ canExport: filteredAnnouncements.length > 0,
1006
1075
  recordTypeFilter: this.#recordTypeFilter,
1007
1076
  onRecordTypeFilterChange: (type: RecordTypeFilter) => {
1008
1077
  this.setRecordTypeFilter(type);
@@ -1012,7 +1081,7 @@ export class AccessibilityAnnouncementRecordingView extends AccessibilitySubPane
1012
1081
  this.setTextFilter(text);
1013
1082
  },
1014
1083
  blockedTargets,
1015
- announcements: this.filteredAnnouncements,
1084
+ announcements: filteredAnnouncements,
1016
1085
  };
1017
1086
  this.#view(input, undefined, this.contentElement);
1018
1087
  }
@@ -1636,7 +1636,7 @@ async function makeDomTreeWidget(widgetData: AiAssistanceModel.AiAgent.DomTreeAi
1636
1636
  // clang-format off
1637
1637
  const renderedWidget = html`
1638
1638
  ${networkRequest ? renderNetworkRequestPreview(networkRequest) : Lit.nothing}
1639
- <devtools-widget class="dom-tree-widget" ${widget(Elements.ElementsTreeOutline.DOMTreeWidget, {
1639
+ <devtools-widget class="dom-tree-widget" ${widget(Elements.DOMTreeWidget.DOMTreeWidget, {
1640
1640
  maxTreeDepth: 2,
1641
1641
  enableContextMenu: false,
1642
1642
  showComments: false,
@@ -43,6 +43,7 @@ const POPUP_MARGIN = 8;
43
43
  const PIN_HEIGHT = 30;
44
44
  const POPUP_WIDTH = 288;
45
45
  const POPUP_HEIGHT = 220;
46
+ const AUTO_CLOSE_DELAY_MS = 2000;
46
47
 
47
48
  const DEFAULT_VIEW: View = (input: ViewInput, _output: undefined, target: HTMLElement): void => {
48
49
  // clang-format off
@@ -119,6 +120,7 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
119
120
  readonly #commentManager: CommentManager.CommentManager.CommentManager;
120
121
  #commentOverlayManager: Comments.CommentOverlayManager.CommentOverlayManager;
121
122
  #activeThreadId: string|null = null;
123
+ #closeTimeoutId: number|null = null;
122
124
  #cachedTitle: Title = {text: ''};
123
125
  #cachedTitleAnchor: CommentManager.CommentManager.CommentAnchorSignature|null = null;
124
126
 
@@ -173,6 +175,7 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
173
175
  }
174
176
 
175
177
  override willHide(): void {
178
+ this.#clearCloseTimeout();
176
179
  this.#commentOverlayManager.stop();
177
180
  this.#commentOverlayManager.removeEventListener(
178
181
  Comments.CommentOverlayManager.Events.POSITIONS_UPDATED,
@@ -204,6 +207,13 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
204
207
  super.willHide();
205
208
  }
206
209
 
210
+ #clearCloseTimeout(): void {
211
+ if (this.#closeTimeoutId !== null) {
212
+ window.clearTimeout(this.#closeTimeoutId);
213
+ this.#closeTimeoutId = null;
214
+ }
215
+ }
216
+
207
217
  #onAgentAttachedChanged(
208
218
  event: Common.EventTarget.EventTargetEvent<
209
219
  CommentManager.CommentManager.EventTypes[CommentManager.CommentManager.Events.AGENT_ATTACHED_CHANGED]>,
@@ -329,7 +339,18 @@ export class CommentsOverlayWidget extends UI.Widget.Widget {
329
339
  activePin,
330
340
  title,
331
341
  onAddComment: (text: string) => {
332
- activeThread?.save(text);
342
+ if (!activeThread) {
343
+ return;
344
+ }
345
+ activeThread.save(text);
346
+ const threadId = activeThread.id;
347
+ this.#closeTimeoutId = window.setTimeout(() => {
348
+ this.#closeTimeoutId = null;
349
+ if (this.#activeThreadId === threadId) {
350
+ this.#activeThreadId = null;
351
+ this.requestUpdate();
352
+ }
353
+ }, AUTO_CLOSE_DELAY_MS);
333
354
  },
334
355
  };
335
356
  this.#view(viewInput, undefined, this.contentElement);
@@ -350,6 +371,9 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
350
371
 
351
372
  handleAction(_context: UI.Context.Context, actionId: string): boolean {
352
373
  if (actionId === 'comments.toggle-comment-mode') {
374
+ if (!this.#commentManager.isAgentAttached()) {
375
+ return false;
376
+ }
353
377
  if (!widgetInstance) {
354
378
  widgetInstance = new CommentsOverlayWidget(
355
379
  undefined,
@@ -383,11 +407,14 @@ export class ButtonProvider implements UI.Toolbar.Provider {
383
407
  Root.DevToolsContext.globalInstance().get(
384
408
  CommentManager.CommentManager.CommentManager,
385
409
  );
386
- this.#button = UI.Toolbar.Toolbar.createActionButton('comments.toggle-comment-mode');
410
+ const action = UI.ActionRegistry.ActionRegistry.instance().getAction('comments.toggle-comment-mode');
411
+ action.setEnabled(this.#commentManager.isAgentAttached());
412
+ this.#button = UI.Toolbar.Toolbar.createActionButton(action);
387
413
  this.#button.setVisible(this.#commentManager.isAgentAttached());
388
414
  this.#commentManager.addEventListener(
389
415
  CommentManager.CommentManager.Events.AGENT_ATTACHED_CHANGED,
390
416
  event => {
417
+ action.setEnabled(event.data);
391
418
  this.#button.setVisible(event.data);
392
419
  },
393
420
  );
@@ -34,6 +34,7 @@ const kForbiddenSchemes = [
34
34
  'chrome-error:',
35
35
  'chrome-search:',
36
36
  'devtools:',
37
+ 'isolated-app:',
37
38
  ];
38
39
 
39
40
  declare global {
@@ -44,6 +45,18 @@ declare global {
44
45
 
45
46
  let extensionServerInstance: ExtensionServer|null;
46
47
 
48
+ function parseCanonicalURL(url: Platform.DevToolsPath.UrlString): URL|null {
49
+ try {
50
+ let parsedURL = new URL(url);
51
+ while (parsedURL.protocol === 'blob:' || parsedURL.protocol === 'filesystem:') {
52
+ parsedURL = new URL(parsedURL.href.slice(parsedURL.protocol.length));
53
+ }
54
+ return parsedURL;
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
47
60
  export class HostsPolicy {
48
61
  static create(policy?: Host.InspectorFrontendHostAPI.ExtensionHostsPolicy): HostsPolicy|null {
49
62
  const runtimeAllowedHosts = [];
@@ -98,12 +111,11 @@ class RegisteredExtension {
98
111
  return false;
99
112
  }
100
113
 
101
- let parsedURL;
102
- try {
103
- parsedURL = new URL(inspectedURL);
104
- } catch {
114
+ const parsedURL = parseCanonicalURL(inspectedURL);
115
+ if (!parsedURL) {
105
116
  return false;
106
117
  }
118
+ inspectedURL = parsedURL.href as Platform.DevToolsPath.UrlString;
107
119
 
108
120
  if (parsedURL.protocol === 'chrome-extension:') {
109
121
  if (parsedURL.origin !== this.origin) {
@@ -1827,12 +1839,10 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
1827
1839
  }
1828
1840
 
1829
1841
  static canInspectURL(url: Platform.DevToolsPath.UrlString): boolean {
1830
- let parsedURL;
1831
1842
  // This is only to work around invalid URLs we're occasionally getting from some tests.
1832
1843
  // TODO(caseq): make sure tests supply valid URLs or we specifically handle invalid ones.
1833
- try {
1834
- parsedURL = new URL(url);
1835
- } catch {
1844
+ const parsedURL = parseCanonicalURL(url);
1845
+ if (!parsedURL) {
1836
1846
  return false;
1837
1847
  }
1838
1848
 
@@ -459,7 +459,7 @@ export class ConsolePrompt extends ConsolePromptBase {
459
459
  ?.evaluateCommandInConsole(executionContext, message, expression, useCommandLineAPI);
460
460
  }
461
461
 
462
- private async substituteNames(expression: string, mapping: Map<string, string|null>): Promise<string> {
462
+ private async substituteNames(expression: string, mapping: Array<Map<string, string|null>>): Promise<string> {
463
463
  try {
464
464
  return await Formatter.FormatterWorkerPool.formatterWorkerPool().javaScriptSubstitute(expression, mapping);
465
465
  } catch {
@@ -1030,7 +1030,7 @@ export class ConsoleViewMessage implements ConsoleViewportElement {
1030
1030
  result.appendChild(this.formatParameterAsObject(remoteObject, false));
1031
1031
  return;
1032
1032
  }
1033
- const treeOutline = new Elements.ElementsTreeOutline.ElementsTreeOutline(
1033
+ const treeOutline = new Elements.DOMTreeWidget.ElementsTreeOutline(
1034
1034
  /* omitRootDOMNode: */ false, /* selectEnabled: */ true, /* hideGutter: */ true);
1035
1035
  treeOutline.rootDOMNode = node;
1036
1036
  treeOutline.deindentSingleNode();
@@ -359,7 +359,8 @@ export class ClassNamePrompt extends UI.TextPrompt.TextPrompt {
359
359
 
360
360
  let completions: string[] = await this.classNamesPromise;
361
361
  const classesMap = this.nodeClasses((selectedNode));
362
- completions = completions.filter(value => !classesMap.get(value));
362
+ const existingClasses = new Set(expression.split(/[,\s]/).map(className => className.trim()).filter(Boolean));
363
+ completions = completions.filter(value => !classesMap.get(value) && !existingClasses.has(value));
363
364
 
364
365
  if (prefix[0] === '.') {
365
366
  completions = completions.map(value => '.' + value);
@@ -9,9 +9,9 @@ import * as UI from '../../ui/legacy/legacy.js';
9
9
  import * as Emulation from '../emulation/emulation.js';
10
10
 
11
11
  import {canGetJSPath} from './DOMPath.js';
12
+ import type {DOMTreeWidget} from './DOMTreeWidget.js';
12
13
  import {ElementsPanel} from './ElementsPanel.js';
13
14
  import {ElementsTreeElement} from './ElementsTreeElement.js';
14
- import type {DOMTreeWidget} from './ElementsTreeOutline.js';
15
15
 
16
16
  const UIStrings = {
17
17
  /**