chrome-devtools-frontend 1.0.1582745 → 1.0.1585538

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 (127) hide show
  1. package/front_end/core/host/InspectorFrontendHostAPI.ts +1 -0
  2. package/front_end/core/host/InspectorFrontendHostStub.ts +2 -0
  3. package/front_end/core/root/Runtime.ts +12 -11
  4. package/front_end/core/sdk/CSSMatchedStyles.ts +3 -0
  5. package/front_end/core/sdk/CSSMetadata.ts +3 -0
  6. package/front_end/core/sdk/NetworkManager.ts +63 -115
  7. package/front_end/entrypoint_template.html +1 -5
  8. package/front_end/entrypoints/main/MainImpl.ts +1 -0
  9. package/front_end/generated/Deprecation.ts +21 -0
  10. package/front_end/generated/InspectorBackendCommands.ts +5 -3
  11. package/front_end/generated/SupportedCSSProperties.js +6 -2
  12. package/front_end/generated/protocol-mapping.d.ts +2 -1
  13. package/front_end/generated/protocol-proxy-api.d.ts +2 -1
  14. package/front_end/generated/protocol.ts +14 -0
  15. package/front_end/models/ai_assistance/AiConversation.ts +34 -2
  16. package/front_end/models/ai_assistance/BuiltInAi.ts +1 -2
  17. package/front_end/models/ai_assistance/agents/AiAgent.ts +3 -10
  18. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +1 -1
  19. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +17 -9
  20. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +0 -24
  21. package/front_end/models/ai_code_generation/AiCodeGeneration.ts +5 -0
  22. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +16 -8
  23. package/front_end/models/computed_style/ComputedStyleModel.ts +20 -15
  24. package/front_end/models/greendev/Prototypes.ts +1 -5
  25. package/front_end/models/issues_manager/CorsIssue.ts +2 -9
  26. package/front_end/models/issues_manager/descriptions/corsLocalNetworkAccessPermissionDenied.md +2 -2
  27. package/front_end/models/javascript_metadata/NativeFunctions.js +20 -0
  28. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +28 -39
  29. package/front_end/panels/ai_assistance/components/ChatInput.ts +73 -95
  30. package/front_end/panels/ai_assistance/components/ChatMessage.ts +4 -2
  31. package/front_end/panels/ai_assistance/components/ChatView.ts +0 -2
  32. package/front_end/panels/autofill/AutofillView.ts +4 -8
  33. package/front_end/panels/common/AiCodeGenerationTeaser.ts +37 -8
  34. package/front_end/panels/console/ConsoleView.ts +1 -2
  35. package/front_end/panels/console/ConsoleViewMessage.ts +18 -11
  36. package/front_end/panels/elements/ComputedStyleWidget.ts +80 -48
  37. package/front_end/panels/elements/ElementStatePaneWidget.ts +1 -1
  38. package/front_end/panels/elements/ElementsPanel.ts +18 -5
  39. package/front_end/panels/elements/ElementsTreeElement.ts +459 -435
  40. package/front_end/panels/elements/EventListenersWidget.ts +2 -4
  41. package/front_end/panels/elements/NodeStackTraceWidget.ts +1 -3
  42. package/front_end/panels/elements/PropertiesWidget.ts +1 -2
  43. package/front_end/panels/elements/StylePropertyTreeElement.ts +18 -2
  44. package/front_end/panels/elements/computedStyleWidget.css +25 -0
  45. package/front_end/panels/issues/AffectedResourcesView.ts +1 -2
  46. package/front_end/panels/network/NetworkDataGridNode.ts +0 -8
  47. package/front_end/panels/network/NetworkLogView.ts +64 -105
  48. package/front_end/panels/network/RequestConditionsDrawer.ts +168 -233
  49. package/front_end/panels/network/network-meta.ts +4 -27
  50. package/front_end/panels/settings/SettingsScreen.ts +12 -4
  51. package/front_end/panels/settings/WorkspaceSettingsTab.ts +1 -1
  52. package/front_end/panels/snippets/SnippetsQuickOpen.ts +6 -9
  53. package/front_end/panels/sources/CallStackSidebarPane.ts +6 -8
  54. package/front_end/panels/sources/DebuggerPlugin.ts +2 -1
  55. package/front_end/panels/sources/GoToLineQuickOpen.ts +0 -4
  56. package/front_end/panels/sources/OpenFileQuickOpen.ts +0 -4
  57. package/front_end/panels/sources/OutlineQuickOpen.ts +0 -4
  58. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +3 -1
  59. package/front_end/panels/sources/sources-meta.ts +9 -6
  60. package/front_end/panels/timeline/TimelineFlameChartView.ts +0 -12
  61. package/front_end/panels/timeline/TimelinePanel.ts +35 -15
  62. package/front_end/panels/timeline/components/Sidebar.ts +21 -0
  63. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +1 -20
  64. package/front_end/panels/timeline/components/insights/InsightRenderer.ts +0 -1
  65. package/front_end/third_party/chromium/README.chromium +1 -1
  66. package/front_end/third_party/puppeteer/README.chromium +2 -2
  67. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/BrowsingContext.d.ts +1 -1
  68. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/BrowsingContext.d.ts.map +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/BrowsingContext.js +1 -1
  70. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/BrowsingContext.js.map +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +2 -3
  73. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +2 -2
  75. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +2 -2
  76. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/encoding.d.ts.map +1 -1
  77. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/encoding.js +5 -3
  78. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/encoding.js.map +1 -1
  79. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  81. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +8 -6
  82. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/BrowsingContext.d.ts +1 -1
  83. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/BrowsingContext.d.ts.map +1 -1
  84. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/BrowsingContext.js +1 -1
  85. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/BrowsingContext.js.map +1 -1
  86. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  87. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +2 -3
  88. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
  89. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +2 -2
  90. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +2 -2
  91. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/encoding.d.ts.map +1 -1
  92. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/encoding.js +5 -3
  93. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/encoding.js.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  96. package/front_end/third_party/puppeteer/package/package.json +4 -4
  97. package/front_end/third_party/puppeteer/package/src/bidi/Page.ts +1 -1
  98. package/front_end/third_party/puppeteer/package/src/bidi/core/BrowsingContext.ts +2 -2
  99. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +4 -3
  100. package/front_end/third_party/puppeteer/package/src/revisions.ts +2 -2
  101. package/front_end/third_party/puppeteer/package/src/util/encoding.ts +5 -3
  102. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  103. package/front_end/ui/components/linkifier/LinkifierImpl.ts +3 -0
  104. package/front_end/ui/components/linkifier/LinkifierUtils.ts +3 -0
  105. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +8 -0
  106. package/front_end/ui/components/text_editor/AiCodeGenerationParser.ts +29 -7
  107. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +42 -18
  108. package/front_end/ui/components/text_editor/config.ts +6 -0
  109. package/front_end/ui/legacy/InspectorView.ts +41 -15
  110. package/front_end/ui/legacy/Toolbar.ts +12 -4
  111. package/front_end/ui/legacy/UIUtils.ts +56 -7
  112. package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +4 -3
  113. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +2 -1
  114. package/front_end/ui/legacy/components/quick_open/HelpQuickOpen.ts +8 -2
  115. package/front_end/ui/legacy/components/quick_open/QuickOpen.ts +8 -8
  116. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +12 -9
  117. package/front_end/ui/legacy/components/utils/Linkifier.ts +116 -87
  118. package/front_end/ui/legacy/legacy.ts +0 -2
  119. package/front_end/ui/visual_logging/KnownContextValues.ts +4 -0
  120. package/mcp/HostBindings.ts +3 -0
  121. package/package.json +1 -1
  122. package/front_end/models/issues_manager/descriptions/corsInsecurePrivateNetwork.md +0 -10
  123. package/front_end/models/issues_manager/descriptions/corsPreflightAllowPrivateNetworkError.md +0 -10
  124. package/front_end/models/issues_manager/descriptions/corsPrivateNetworkPermissionDenied.md +0 -10
  125. package/front_end/panels/elements/computedStyleSidebarPane.css +0 -18
  126. package/front_end/ui/legacy/Floaty.ts +0 -438
  127. package/front_end/ui/legacy/floaty.css +0 -77
@@ -73,6 +73,7 @@ import {ImagePreviewPopover} from './ImagePreviewPopover.js';
73
73
  import {getRegisteredDecorators, type MarkerDecorator, type MarkerDecoratorRegistration} from './MarkerDecorator.js';
74
74
 
75
75
  const {html, nothing, render, Directives: {ref}} = Lit;
76
+ const {animateOn} = UI.UIUtils;
76
77
 
77
78
  const UIStrings = {
78
79
  /**
@@ -379,6 +380,15 @@ export function isOpeningTag(context: TagTypeContext): context is OpeningTagCont
379
380
  }
380
381
 
381
382
  export interface ViewInput {
383
+ node: SDK.DOMModel.DOMNode|null;
384
+ isClosingTag: boolean;
385
+ expanded: boolean;
386
+ isExpandable: boolean;
387
+ isXMLMimeType: boolean;
388
+ updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null;
389
+ onHighlightSearchResults: () => void;
390
+ onExpand: () => void;
391
+
382
392
  containerAdornerActive: boolean;
383
393
  flexAdornerActive: boolean;
384
394
  gridAdornerActive: boolean;
@@ -398,7 +408,6 @@ export interface ViewInput {
398
408
  showViewSourceAdorner: boolean;
399
409
  showScrollAdorner: boolean;
400
410
  showScrollSnapAdorner: boolean;
401
- nodeInfo?: DocumentFragment;
402
411
  topLayerIndex: number;
403
412
  scrollSnapAdornerActive: boolean;
404
413
 
@@ -436,7 +445,7 @@ export interface ViewOutput {
436
445
 
437
446
  export function adornerRef(): DirectiveResult<typeof Lit.Directives.RefDirective> {
438
447
  let adorner: Adorners.Adorner.Adorner|undefined;
439
- return ref((el?: Element) => {
448
+ return ref(el => {
440
449
  if (adorner) {
441
450
  ElementsPanel.instance().deregisterAdorner(adorner);
442
451
  }
@@ -457,6 +466,8 @@ export interface Decoration {
457
466
  color: string;
458
467
  }
459
468
 
469
+ const DOM_UPDATE_ANIMATION_CLASS_NAME = 'dom-update-highlight';
470
+
460
471
  function handleAdornerKeydown(cb: (event: Event) => void): (event: KeyboardEvent) => void {
461
472
  return (event: KeyboardEvent) => {
462
473
  if (event.code === 'Enter' || event.code === 'Space') {
@@ -467,6 +478,378 @@ function handleAdornerKeydown(cb: (event: Event) => void): (event: KeyboardEvent
467
478
  };
468
479
  }
469
480
 
481
+ function renderTitle(
482
+ node: SDK.DOMModel.DOMNode,
483
+ isClosingTag: boolean,
484
+ expanded: boolean,
485
+ isExpandable: boolean,
486
+ isXMLMimeType: boolean,
487
+ updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null,
488
+ onUpdateSearchHighlight: () => void,
489
+ onExpand: () => void,
490
+ ): Lit.LitTemplate {
491
+ switch (node.nodeType()) {
492
+ case Node.ATTRIBUTE_NODE:
493
+ return renderAttribute({name: node.name as string, value: node.value as string}, updateRecord, true, node);
494
+
495
+ case Node.ELEMENT_NODE: {
496
+ if (node.pseudoType()) {
497
+ let pseudoElementName = node.nodeName();
498
+ const pseudoIdentifier = node.pseudoIdentifier();
499
+ if (pseudoIdentifier) {
500
+ pseudoElementName += `(${pseudoIdentifier})`;
501
+ }
502
+ return html`<span class="webkit-html-pseudo-element">${pseudoElementName}</span>\u200B`;
503
+ }
504
+
505
+ const tagName = node.nodeNameInCorrectCase();
506
+ if (isClosingTag) {
507
+ return renderTag(node, tagName, true, expanded, true, updateRecord);
508
+ }
509
+
510
+ const openingTag = renderTag(node, tagName, false, expanded, false, updateRecord);
511
+
512
+ if (isExpandable) {
513
+ if (!expanded) {
514
+ return html`${openingTag}<devtools-elements-tree-expand-button .data=${
515
+ {clickHandler: onExpand} as
516
+ ElementsComponents.ElementsTreeExpandButton
517
+ .ElementsTreeExpandButtonData}></devtools-elements-tree-expand-button><span style="font-size: 0;"
518
+ >…</span>\u200B${renderTag(node, tagName, true, expanded, false, updateRecord)}`;
519
+ }
520
+ return openingTag;
521
+ }
522
+
523
+ if (ElementsTreeElement.canShowInlineText(node)) {
524
+ const firstChild = node.firstChild;
525
+ if (!firstChild) {
526
+ throw new Error('ElementsTreeElement._nodeTitleInfo expects node.firstChild to be defined.');
527
+ }
528
+ const result = convertUnicodeCharsToHTMLEntities(firstChild.nodeValue());
529
+ const textContent = Platform.StringUtilities.collapseWhitespace(result.text);
530
+
531
+ const renderTextNode = ref(el => {
532
+ if (el) {
533
+ el.textContent = textContent;
534
+ Highlighting.highlightRangesWithStyleClass(el, result.entityRanges, 'webkit-html-entity-value');
535
+ }
536
+ });
537
+
538
+ return html`${openingTag}<span class="webkit-html-text-node" jslog=${
539
+ VisualLogging.value('text-node').track({change: true, dblclick: true})} ${
540
+ animateOn(
541
+ Boolean((updateRecord?.hasChangedChildren() || updateRecord?.isCharDataModified())),
542
+ DOM_UPDATE_ANIMATION_CLASS_NAME)} ${renderTextNode}></span>\u200B${
543
+ renderTag(node, tagName, true, expanded, false, updateRecord)}`;
544
+ }
545
+
546
+ if (isXMLMimeType || !ForbiddenClosingTagElements.has(tagName)) {
547
+ return html`${openingTag}${renderTag(node, tagName, true, expanded, false, updateRecord)}`;
548
+ }
549
+ return openingTag;
550
+ }
551
+
552
+ case Node.TEXT_NODE: {
553
+ if (node.parentNode && node.parentNode.nodeName().toLowerCase() === 'script') {
554
+ const text = node.nodeValue();
555
+ const highlightNode = ref(el => {
556
+ if (el) {
557
+ el.textContent = text.replace(/^[\n\r]+|\s+$/g, '');
558
+ void CodeHighlighter.CodeHighlighter.highlightNode(el, 'text/javascript').then(onUpdateSearchHighlight);
559
+ }
560
+ });
561
+ return html`<span class="webkit-html-text-node webkit-html-js-node" jslog=${
562
+ VisualLogging.value('script-text-node').track({change: true, dblclick: true})} ${highlightNode}></span>`;
563
+ }
564
+ if (node.parentNode && node.parentNode.nodeName().toLowerCase() === 'style') {
565
+ const text = node.nodeValue();
566
+ const highlightNode = ref(el => {
567
+ if (el) {
568
+ el.textContent = text.replace(/^[\n\r]+|\s+$/g, '');
569
+ void CodeHighlighter.CodeHighlighter.highlightNode(el, 'text/css').then(onUpdateSearchHighlight);
570
+ }
571
+ });
572
+ return html`<span class="webkit-html-text-node webkit-html-css-node" jslog=${
573
+ VisualLogging.value('css-text-node').track({change: true, dblclick: true})} ${highlightNode}></span>`;
574
+ }
575
+
576
+ const result = convertUnicodeCharsToHTMLEntities(node.nodeValue());
577
+ const textContent = Platform.StringUtilities.collapseWhitespace(result.text);
578
+ const renderTextNode = ref(el => {
579
+ if (el) {
580
+ el.textContent = textContent;
581
+ Highlighting.highlightRangesWithStyleClass(el, result.entityRanges, 'webkit-html-entity-value');
582
+ }
583
+ });
584
+ return html`"<span class="webkit-html-text-node" jslog=${VisualLogging.value('text-node').track({
585
+ change: true,
586
+ dblclick: true
587
+ })} ${animateOn(Boolean(updateRecord?.isCharDataModified()), DOM_UPDATE_ANIMATION_CLASS_NAME)} ${
588
+ renderTextNode}></span>"`;
589
+ }
590
+
591
+ case Node.COMMENT_NODE: {
592
+ return html`<span class="webkit-html-comment">&lt;!--${node.nodeValue()}--&gt;</span>`;
593
+ }
594
+
595
+ case Node.DOCUMENT_TYPE_NODE: {
596
+ let doctype = '<!DOCTYPE ' + node.nodeName();
597
+ if (node.publicId) {
598
+ doctype += ' PUBLIC "' + node.publicId + '"';
599
+ if (node.systemId) {
600
+ doctype += ' "' + node.systemId + '"';
601
+ }
602
+ } else if (node.systemId) {
603
+ doctype += ' SYSTEM "' + node.systemId + '"';
604
+ }
605
+ if (node.internalSubset) {
606
+ doctype += ' [' + node.internalSubset + ']';
607
+ }
608
+ doctype += '>';
609
+ return html`<span class="webkit-html-doctype">${doctype}</span>`;
610
+ }
611
+
612
+ case Node.CDATA_SECTION_NODE: {
613
+ return html`<span class="webkit-html-text-node">&lt;![CDATA[${node.nodeValue()}]]&gt;</span>`;
614
+ }
615
+
616
+ case Node.DOCUMENT_NODE: {
617
+ const text = (node as SDK.DOMModel.DOMDocument).documentURL;
618
+ return html`<span>#document (<span>${Components.Linkifier.Linkifier.renderLinkifiedUrl(text, {
619
+ text,
620
+ preventClick: true,
621
+ showColumnNumber: false,
622
+ inlineFrameIndex: 0,
623
+ })}</span>)</span>`;
624
+ }
625
+
626
+ case Node.DOCUMENT_FRAGMENT_NODE: {
627
+ return html`<span class="webkit-html-fragment">${
628
+ Platform.StringUtilities.collapseWhitespace(node.nodeNameInCorrectCase())}</span>`;
629
+ }
630
+
631
+ default: {
632
+ return html`${Platform.StringUtilities.collapseWhitespace(node.nodeNameInCorrectCase())}`;
633
+ }
634
+ }
635
+ }
636
+
637
+ function renderAttribute(
638
+ attr: {name: string, value?: string}, updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null,
639
+ isDiff: boolean, node: SDK.DOMModel.DOMNode): Lit.LitTemplate {
640
+ const name = attr.name;
641
+ const value = attr.value || '';
642
+ const forceValue = isDiff;
643
+ const closingPunctuationRegex = /[\/;:\)\]\}]/g;
644
+ let highlightIndex = 0;
645
+ let highlightCount = 0;
646
+ let additionalHighlightOffset = 0;
647
+
648
+ function setValueWithEntities(element: Element, value: string): void {
649
+ const result = convertUnicodeCharsToHTMLEntities(value);
650
+ highlightCount = result.entityRanges.length;
651
+ const newValue = result.text.replace(closingPunctuationRegex, (match, replaceOffset) => {
652
+ while (highlightIndex < highlightCount && result.entityRanges[highlightIndex].offset < replaceOffset) {
653
+ result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
654
+ ++highlightIndex;
655
+ }
656
+ additionalHighlightOffset += 1;
657
+ return match + '\u200B';
658
+ });
659
+
660
+ while (highlightIndex < highlightCount) {
661
+ result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
662
+ ++highlightIndex;
663
+ }
664
+ element.setTextContentTruncatedIfNeeded(newValue);
665
+ Highlighting.highlightRangesWithStyleClass(element, result.entityRanges, 'webkit-html-entity-value');
666
+ }
667
+
668
+ const hasText = (forceValue || value.length > 0);
669
+ const jslog = VisualLogging.value(name === 'style' ? 'style-attribute' : 'attribute').track({
670
+ change: true,
671
+ dblclick: true,
672
+ });
673
+
674
+ function linkifyValue(value: string): Element {
675
+ const rewrittenHref = node ? node.resolveURL(value) : null;
676
+ if (rewrittenHref === null) {
677
+ const span = document.createElement('span');
678
+ setValueWithEntities(span, value);
679
+ return span;
680
+ }
681
+ value = value.replace(closingPunctuationRegex, '$&\u200B');
682
+ if (value.startsWith('data:')) {
683
+ value = Platform.StringUtilities.trimMiddle(value, 60);
684
+ }
685
+ const link = node && node.nodeName().toLowerCase() === 'a' ?
686
+ Link.create(rewrittenHref, value, undefined, 'image-url') :
687
+ Components.Linkifier.Linkifier.linkifyURL(rewrittenHref, {
688
+ text: value,
689
+ preventClick: true,
690
+ showColumnNumber: false,
691
+ inlineFrameIndex: 0,
692
+ });
693
+ return ImagePreviewPopover.setImageUrl(link, rewrittenHref);
694
+ }
695
+
696
+ function linkifySrcset(value: string): DocumentFragment {
697
+ // Splitting normally on commas or spaces will break on valid srcsets "foo 1x,bar 2x" and "data:,foo 1x".
698
+ const fragment = document.createDocumentFragment();
699
+ let i = 0;
700
+ while (value.length) {
701
+ if (i++ > 0) {
702
+ UI.UIUtils.createTextChild(fragment, ' ');
703
+ }
704
+ value = value.trim();
705
+ let url = '';
706
+ let descriptor = '';
707
+ const indexOfSpace = value.search(/\s/);
708
+ if (indexOfSpace === -1) {
709
+ url = value;
710
+ } else if (indexOfSpace > 0 && value[indexOfSpace - 1] === ',') {
711
+ url = value.substring(0, indexOfSpace);
712
+ } else {
713
+ url = value.substring(0, indexOfSpace);
714
+ const indexOfComma = value.indexOf(',', indexOfSpace);
715
+ if (indexOfComma !== -1) {
716
+ descriptor = value.substring(indexOfSpace, indexOfComma + 1);
717
+ } else {
718
+ descriptor = value.substring(indexOfSpace);
719
+ }
720
+ }
721
+
722
+ if (url) {
723
+ if (url.endsWith(',')) {
724
+ fragment.appendChild(linkifyValue(url.substring(0, url.length - 1)));
725
+ UI.UIUtils.createTextChild(fragment, ',');
726
+ } else {
727
+ fragment.appendChild(linkifyValue(url));
728
+ }
729
+ }
730
+ if (descriptor) {
731
+ UI.UIUtils.createTextChild(fragment, descriptor);
732
+ }
733
+ value = value.substring(url.length + descriptor.length);
734
+ }
735
+ return fragment;
736
+ }
737
+
738
+ const nodeName = node ? node.nodeName().toLowerCase() : '';
739
+ const setAttrValue = ref(el => {
740
+ if (!el) {
741
+ return;
742
+ }
743
+ const valueElement = el as HTMLElement;
744
+ valueElement.removeChildren();
745
+ if (nodeName && (name === 'src' || name === 'href') && value) {
746
+ valueElement.appendChild(linkifyValue(value));
747
+ } else if ((nodeName === 'img' || nodeName === 'source') && name === 'srcset') {
748
+ valueElement.appendChild(linkifySrcset(value));
749
+ } else if (nodeName === 'image' && (name === 'xlink:href' || name === 'href')) {
750
+ valueElement.appendChild(linkifySrcset(value));
751
+ } else {
752
+ setValueWithEntities(valueElement, value);
753
+ }
754
+ });
755
+
756
+ const relationRef =
757
+ (relation: Protocol.DOM.GetElementByRelationRequestRelation, tooltip: string): ReturnType<typeof ref> =>
758
+ ref((el): void => {
759
+ if (!el) {
760
+ return;
761
+ }
762
+ void (async(): Promise<void> => {
763
+ const relatedElementId = await node.domModel().getElementByRelation(node.id, relation);
764
+ const relatedElement = node.domModel().nodeForId(relatedElementId);
765
+ if (!relatedElement) {
766
+ return;
767
+ }
768
+ const link = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(relatedElement, {
769
+ preventKeyboardFocus: true,
770
+ tooltip,
771
+ textContent: el.textContent || undefined,
772
+ isDynamicLink: true,
773
+ });
774
+ el.removeChildren();
775
+ el.append(link);
776
+ })();
777
+ });
778
+
779
+ let relationRefDirective: ReturnType<typeof relationRef> = ref(() => {});
780
+ if (!value) {
781
+ if (name === 'popovertarget') {
782
+ relationRefDirective = relationRef(
783
+ Protocol.DOM.GetElementByRelationRequestRelation.PopoverTarget, i18nString(UIStrings.showPopoverTarget));
784
+ } else if (name === 'interesttarget') {
785
+ relationRefDirective = relationRef(
786
+ Protocol.DOM.GetElementByRelationRequestRelation.InterestTarget, i18nString(UIStrings.showInterestTarget));
787
+ } else if (name === 'commandfor') {
788
+ relationRefDirective = relationRef(
789
+ Protocol.DOM.GetElementByRelationRequestRelation.CommandFor, i18nString(UIStrings.showCommandForTarget));
790
+ }
791
+ }
792
+
793
+ let valueRelationRefDirective: ReturnType<typeof relationRef> = ref(() => {});
794
+ if (value) {
795
+ if (name === 'popovertarget') {
796
+ valueRelationRefDirective = relationRef(
797
+ Protocol.DOM.GetElementByRelationRequestRelation.PopoverTarget, i18nString(UIStrings.showPopoverTarget));
798
+ } else if (name === 'interesttarget') {
799
+ valueRelationRefDirective = relationRef(
800
+ Protocol.DOM.GetElementByRelationRequestRelation.InterestTarget, i18nString(UIStrings.showInterestTarget));
801
+ } else if (name === 'commandfor') {
802
+ valueRelationRefDirective = relationRef(
803
+ Protocol.DOM.GetElementByRelationRequestRelation.CommandFor, i18nString(UIStrings.showCommandForTarget));
804
+ }
805
+ }
806
+
807
+ return html`<span class="webkit-html-attribute" jslog=${jslog}><span class="webkit-html-attribute-name"
808
+ ${animateOn(Boolean(updateRecord?.isAttributeModified(name) && !hasText), DOM_UPDATE_ANIMATION_CLASS_NAME)} ${
809
+ relationRefDirective}>${name}</span>${
810
+ hasText ? html`=\u200B"<span class="webkit-html-attribute-value" ${
811
+ animateOn(
812
+ Boolean(updateRecord?.isAttributeModified(name) && hasText),
813
+ DOM_UPDATE_ANIMATION_CLASS_NAME)} ${setAttrValue} ${valueRelationRefDirective}></span>"` :
814
+ nothing}</span>`;
815
+ }
816
+
817
+ function renderTag(
818
+ node: SDK.DOMModel.DOMNode, tagName: string, isClosingTag: boolean, expanded: boolean,
819
+ isDistinctTreeElement: boolean,
820
+ updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null): Lit.LitTemplate {
821
+ const classMap = {
822
+ 'webkit-html-tag': true,
823
+ close: isClosingTag && isDistinctTreeElement,
824
+ };
825
+
826
+ let hasUpdates = false;
827
+ const attributes = !isClosingTag && node.hasAttributes() ? node.attributes() : [];
828
+
829
+ if (!isClosingTag && updateRecord) {
830
+ hasUpdates = updateRecord.hasRemovedAttributes() || updateRecord.hasRemovedChildren();
831
+ hasUpdates = hasUpdates || (!expanded && updateRecord.hasChangedChildren());
832
+ }
833
+
834
+ // We are taking full text content of the tag, including attributes and children, to set the aria label.
835
+ // FIXME: we should compute the aria label ourselves if it is event needed.
836
+ const setAriaLabel = ref(el => {
837
+ if (el?.textContent) {
838
+ UI.ARIAUtils.setLabel(el, el.textContent);
839
+ }
840
+ });
841
+
842
+ const tagNameClass = isClosingTag ? 'webkit-html-close-tag-name' : 'webkit-html-tag-name';
843
+ const tagString = (isClosingTag ? '/' : '') + tagName;
844
+ const jslog = !isClosingTag ? VisualLogging.value('tag-name').track({change: true, dblclick: true}) : '';
845
+
846
+ return html`<span
847
+ class=${Lit.Directives.classMap(classMap)} ${setAriaLabel}
848
+ >&lt;<span class=${tagNameClass} jslog=${jslog || nothing} ${
849
+ animateOn(hasUpdates, DOM_UPDATE_ANIMATION_CLASS_NAME)}>${tagString}</span>${
850
+ attributes.map(attr => html` ${renderAttribute(attr, updateRecord, false, node)}`)}&gt;</span>\u200B`;
851
+ }
852
+
470
853
  export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement): void => {
471
854
  const hasAdorners = input.showAdAdorner || input.showContainerAdorner || input.showFlexAdorner ||
472
855
  input.showGridAdorner || input.showGridLanesAdorner || input.showMediaAdorner || input.showPopoverAdorner ||
@@ -479,7 +862,16 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
479
862
  // clang-format off
480
863
  render(html`
481
864
  <div ${ref(el => { output.contentElement = el as HTMLElement; })}>
482
- ${input.nodeInfo ? html`<span class="highlight">${input.nodeInfo}</span>` : nothing}
865
+ ${input.node ? html`<span class="highlight">${renderTitle(
866
+ input.node,
867
+ input.isClosingTag,
868
+ input.expanded,
869
+ input.isExpandable,
870
+ input.isXMLMimeType,
871
+ input.updateRecord,
872
+ input.onHighlightSearchResults,
873
+ input.onExpand,
874
+ )}</span>` : nothing}
483
875
  ${input.isHovered || input.isSelected ? html`
484
876
  <div class="selection fill" style=${`margin-left: ${-input.indent}px`}></div>
485
877
  ` : nothing}
@@ -681,9 +1073,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
681
1073
  nodeInternal: SDK.DOMModel.DOMNode;
682
1074
  override treeOutline: ElementsTreeOutline|null;
683
1075
 
684
- // Handled by the view output for now.
685
- contentElement!: HTMLElement;
686
-
687
1076
  private searchQuery: string|null;
688
1077
  #expandedChildrenLimit: number;
689
1078
  private readonly decorationsThrottler: Common.Throttler.Throttler;
@@ -699,7 +1088,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
699
1088
  readonly tagTypeContext: TagTypeContext;
700
1089
 
701
1090
  #adornersThrottler = new Common.Throttler.Throttler(100);
702
- #nodeInfo?: DocumentFragment;
703
1091
  #containerAdornerActive = false;
704
1092
  #flexAdornerActive = false;
705
1093
  #gridAdornerActive = false;
@@ -713,6 +1101,12 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
713
1101
  #descendantDecorations: Decoration[] = [];
714
1102
  #decorationsTooltip = '';
715
1103
 
1104
+ #updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null = null;
1105
+
1106
+ // Used to add the content to TreeElement's title element.
1107
+ // Relied on by web tests.
1108
+ #contentElement?: HTMLElement;
1109
+
716
1110
  constructor(node: SDK.DOMModel.DOMNode, isClosingTag?: boolean) {
717
1111
  // The title will be updated in onattach.
718
1112
  super();
@@ -765,7 +1159,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
765
1159
 
766
1160
  static animateOnDOMUpdate(treeElement: ElementsTreeElement): void {
767
1161
  const tagName = treeElement.listItemElement.querySelector('.webkit-html-tag-name');
768
- UI.UIUtils.runCSSAnimationOnce(tagName || treeElement.listItemElement, 'dom-update-highlight');
1162
+ UI.UIUtils.runCSSAnimationOnce(tagName || treeElement.listItemElement, DOM_UPDATE_ANIMATION_CLASS_NAME);
769
1163
  }
770
1164
 
771
1165
  static visibleShadowRoots(node: SDK.DOMModel.DOMNode): SDK.DOMModel.DOMNode[] {
@@ -816,9 +1210,23 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
816
1210
  }
817
1211
  }
818
1212
 
819
- performUpdate(): void {
1213
+ // ClearNode param is used to clean DOM after in-place editing..
1214
+ performUpdate(clearNode = false): void {
1215
+ if (this.editing) {
1216
+ return;
1217
+ }
1218
+ const output: ViewOutput = {};
820
1219
  DEFAULT_VIEW(
821
1220
  {
1221
+ node: !clearNode ? this.nodeInternal : null,
1222
+ isClosingTag: this.isClosingTag(),
1223
+ expanded: this.expanded,
1224
+ isExpandable: this.isExpandable(),
1225
+ isXMLMimeType: Boolean(this.treeOutline?.isXMLMimeType),
1226
+ updateRecord: this.#updateRecord,
1227
+ onHighlightSearchResults: () => this.#highlightSearchResults(),
1228
+ onExpand: () => this.expand(),
1229
+
822
1230
  containerAdornerActive: this.#containerAdornerActive,
823
1231
  showAdAdorner: this.nodeInternal.isAdFrameNode(),
824
1232
  showContainerAdorner: Boolean(this.#layout?.containerType) && !this.isClosingTag(),
@@ -847,7 +1255,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
847
1255
  showSlotAdorner: Boolean(this.nodeInternal.assignedSlot) && !this.isClosingTag(),
848
1256
  showStartingStyleAdorner: this.nodeInternal.affectedByStartingStyles() && !this.isClosingTag(),
849
1257
  startingStyleAdornerActive: this.#startingStyleAdornerActive,
850
- nodeInfo: this.#nodeInfo,
851
1258
  onStartingStyleAdornerClick: (event: Event) => this.#onStartingStyleAdornerClick(event),
852
1259
  onSlotAdornerClick: () => {
853
1260
  if (this.nodeInternal.assignedSlot) {
@@ -888,7 +1295,12 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
888
1295
  }
889
1296
  },
890
1297
  },
891
- this, this.listItemElement);
1298
+ output, this.listItemElement);
1299
+
1300
+ this.#contentElement = output.contentElement;
1301
+ if (this.#updateRecord) {
1302
+ this.#updateRecord = null;
1303
+ }
892
1304
  }
893
1305
 
894
1306
  #onContainerAdornerClick(event: Event): void {
@@ -978,7 +1390,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
978
1390
  }
979
1391
  }
980
1392
  }
981
- UI.UIUtils.runCSSAnimationOnce(animationElement, 'dom-update-highlight');
1393
+ UI.UIUtils.runCSSAnimationOnce(animationElement, DOM_UPDATE_ANIMATION_CLASS_NAME);
982
1394
  }
983
1395
 
984
1396
  isClosingTag(): boolean {
@@ -1096,11 +1508,16 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1096
1508
  this.#expandedChildrenLimit = expandedChildrenLimit;
1097
1509
  }
1098
1510
 
1511
+ onTopLayerIndexChanged(): void {
1512
+ this.performUpdate();
1513
+ }
1514
+
1099
1515
  override onbind(): void {
1100
1516
  this.performUpdate();
1101
1517
  if (this.treeOutline && !this.isClosingTag()) {
1102
1518
  this.treeOutline.treeElementByNode.set(this.nodeInternal, this);
1103
- this.nodeInternal.addEventListener(SDK.DOMModel.DOMNodeEvents.TOP_LAYER_INDEX_CHANGED, this.performUpdate, this);
1519
+ this.nodeInternal.addEventListener(
1520
+ SDK.DOMModel.DOMNodeEvents.TOP_LAYER_INDEX_CHANGED, this.onTopLayerIndexChanged, this);
1104
1521
  this.nodeInternal.addEventListener(
1105
1522
  SDK.DOMModel.DOMNodeEvents.SCROLLABLE_FLAG_UPDATED, this.#onScrollableFlagUpdated, this);
1106
1523
  this.nodeInternal.addEventListener(
@@ -1127,6 +1544,14 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1127
1544
  // element is bound again.
1128
1545
  DEFAULT_VIEW(
1129
1546
  {
1547
+ node: null,
1548
+ isClosingTag: false,
1549
+ expanded: false,
1550
+ isExpandable: false,
1551
+ isXMLMimeType: false,
1552
+ updateRecord: null,
1553
+ onHighlightSearchResults: () => {},
1554
+ onExpand: () => {},
1130
1555
  containerAdornerActive: false,
1131
1556
  showAdAdorner: false,
1132
1557
  showContainerAdorner: false,
@@ -1148,7 +1573,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1148
1573
  showSlotAdorner: false,
1149
1574
  showStartingStyleAdorner: false,
1150
1575
  startingStyleAdornerActive: false,
1151
- nodeInfo: this.#nodeInfo,
1152
1576
  onStartingStyleAdornerClick: () => {},
1153
1577
  onSlotAdornerClick: () => {},
1154
1578
  topLayerIndex: -1,
@@ -1170,12 +1594,13 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1170
1594
  decorationsTooltip: '',
1171
1595
  indent: 0,
1172
1596
  },
1173
- this, this.listItemElement);
1597
+ {}, this.listItemElement);
1174
1598
 
1175
1599
  if (this.treeOutline && this.treeOutline.treeElementByNode.get(this.nodeInternal) === this) {
1176
1600
  this.treeOutline.treeElementByNode.delete(this.nodeInternal);
1177
1601
  }
1178
- this.nodeInternal.removeEventListener(SDK.DOMModel.DOMNodeEvents.TOP_LAYER_INDEX_CHANGED, this.performUpdate, this);
1602
+ this.nodeInternal.removeEventListener(
1603
+ SDK.DOMModel.DOMNodeEvents.TOP_LAYER_INDEX_CHANGED, this.onTopLayerIndexChanged, this);
1179
1604
  this.nodeInternal.removeEventListener(
1180
1605
  SDK.DOMModel.DOMNodeEvents.SCROLLABLE_FLAG_UPDATED, this.#onScrollableFlagUpdated, this);
1181
1606
  this.nodeInternal.removeEventListener(
@@ -1271,13 +1696,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1271
1696
  if (this.editing) {
1272
1697
  return false;
1273
1698
  }
1274
- const handledByFloaty = UI.Floaty.onFloatyClick({
1275
- type: UI.Floaty.FloatyContextTypes.ELEMENT_NODE_ID,
1276
- data: {nodeId: this.nodeInternal.id},
1277
- });
1278
- if (handledByFloaty) {
1279
- return false;
1280
- }
1281
1699
  return super.select(omitFocus, selectedByUser);
1282
1700
  }
1283
1701
 
@@ -1743,7 +2161,10 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1743
2161
  // Cannot just convert the textual html into an element without
1744
2162
  // a parent node. Use a temporary span container for the HTML.
1745
2163
  const container = document.createElement('span');
1746
- const attr = this.buildAttributeDOM(container, ' ', '', null);
2164
+
2165
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
2166
+ Lit.render(renderAttribute({name: ' ', value: ''}, null, false, this.nodeInternal), container);
2167
+ const attr = container.firstElementChild as HTMLElement;
1747
2168
  attr.style.marginLeft = '2px'; // overrides the .editing margin rule
1748
2169
  attr.style.marginRight = '2px'; // overrides the .editing margin rule
1749
2170
  attr.setAttribute('jslog', `${VisualLogging.value('new-attribute').track({change: true, resize: true})}`);
@@ -1789,7 +2210,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1789
2210
 
1790
2211
  // Make sure elementForSelection is not a child of attributeValueElement.
1791
2212
  elementForSelection =
1792
- attributeValueElement.isAncestor(elementForSelection) ? attributeValueElement : elementForSelection;
2213
+ attributeValueElement?.isAncestor(elementForSelection) ? attributeValueElement : elementForSelection;
1793
2214
 
1794
2215
  function removeZeroWidthSpaceRecursive(node: Node): void {
1795
2216
  if (node.nodeType === Node.TEXT_NODE) {
@@ -1825,7 +2246,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1825
2246
  return '';
1826
2247
  }
1827
2248
 
1828
- if (!Common.ParsedURL.ParsedURL.fromString(attributeValueElement.textContent || '')) {
2249
+ if (!Common.ParsedURL.ParsedURL.fromString(attributeValueElement?.textContent || '')) {
1829
2250
  config.setPostKeydownFinishHandler(postKeyDownFinishHandler);
1830
2251
  }
1831
2252
 
@@ -1962,7 +2383,9 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1962
2383
  // Hide header items.
1963
2384
  let child: (ChildNode|null) = this.listItemElement.firstChild;
1964
2385
  while (child) {
1965
- (child as HTMLElement).style.display = 'none';
2386
+ if (child instanceof HTMLElement) {
2387
+ child.style.display = 'none';
2388
+ }
1966
2389
  child = child.nextSibling;
1967
2390
  }
1968
2391
  // Hide children item.
@@ -2054,7 +2477,9 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2054
2477
  // Unhide header items.
2055
2478
  let child: (ChildNode|null) = this.listItemElement.firstChild;
2056
2479
  while (child) {
2057
- (child as HTMLElement).style.removeProperty('display');
2480
+ if (child instanceof HTMLElement) {
2481
+ child.style.removeProperty('display');
2482
+ }
2058
2483
  child = child.nextSibling;
2059
2484
  }
2060
2485
 
@@ -2242,8 +2667,8 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2242
2667
  if (this.editing) {
2243
2668
  return;
2244
2669
  }
2245
-
2246
- this.#nodeInfo = this.nodeTitleInfo(updateRecord || null);
2670
+ this.performUpdate(/* clearNode= */ true);
2671
+ this.#updateRecord = updateRecord ?? null;
2247
2672
  if (this.nodeInternal.nodeType() === Node.DOCUMENT_FRAGMENT_NODE && this.nodeInternal.isInShadowTree() &&
2248
2673
  this.nodeInternal.shadowRootType()) {
2249
2674
  this.childrenListElement.classList.add('shadow-root');
@@ -2261,8 +2686,10 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2261
2686
  }
2262
2687
  }
2263
2688
  this.performUpdate();
2264
- // fixme: make it clear that `this.title = x` is a setter with significant side effects
2265
- this.title = this.contentElement;
2689
+ if (this.#contentElement) {
2690
+ // fixme: we probably do not need a title element in the new tree outline.
2691
+ this.title = this.#contentElement;
2692
+ }
2266
2693
  this.updateDecorations();
2267
2694
 
2268
2695
  // If there is an issue with this node, make sure to update it.
@@ -2367,409 +2794,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2367
2794
  }
2368
2795
  }
2369
2796
 
2370
- private buildAttributeDOM(
2371
- parentElement: Element|DocumentFragment, name: string, value: string,
2372
- updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null, forceValue?: boolean,
2373
- node?: SDK.DOMModel.DOMNode): HTMLElement {
2374
- const closingPunctuationRegex = /[\/;:\)\]\}]/g;
2375
- let highlightIndex = 0;
2376
- let highlightCount = 0;
2377
- let additionalHighlightOffset = 0;
2378
-
2379
- function setValueWithEntities(this: ElementsTreeElement, element: Element, value: string): void {
2380
- const result = convertUnicodeCharsToHTMLEntities(value);
2381
- highlightCount = result.entityRanges.length;
2382
- value = result.text.replace(closingPunctuationRegex, (match, replaceOffset) => {
2383
- while (highlightIndex < highlightCount && result.entityRanges[highlightIndex].offset < replaceOffset) {
2384
- result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
2385
- ++highlightIndex;
2386
- }
2387
- additionalHighlightOffset += 1;
2388
- return match + '\u200B';
2389
- });
2390
-
2391
- while (highlightIndex < highlightCount) {
2392
- result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
2393
- ++highlightIndex;
2394
- }
2395
- element.setTextContentTruncatedIfNeeded(value);
2396
- Highlighting.highlightRangesWithStyleClass(element, result.entityRanges, 'webkit-html-entity-value');
2397
- }
2398
-
2399
- const hasText = (forceValue || value.length > 0);
2400
- const attrSpanElement = parentElement.createChild('span', 'webkit-html-attribute');
2401
- attrSpanElement.setAttribute(
2402
- 'jslog', `${VisualLogging.value(name === 'style' ? 'style-attribute' : 'attribute').track({
2403
- change: true,
2404
- dblclick: true,
2405
- })}`);
2406
-
2407
- const attrNameElement = attrSpanElement.createChild('span', 'webkit-html-attribute-name');
2408
- attrNameElement.textContent = name;
2409
-
2410
- if (hasText) {
2411
- UI.UIUtils.createTextChild(attrSpanElement, '=\u200B"');
2412
- }
2413
-
2414
- const attrValueElement = attrSpanElement.createChild('span', 'webkit-html-attribute-value');
2415
-
2416
- if (updateRecord?.isAttributeModified(name)) {
2417
- UI.UIUtils.runCSSAnimationOnce(hasText ? attrValueElement : attrNameElement, 'dom-update-highlight');
2418
- }
2419
-
2420
- function linkifyValue(this: ElementsTreeElement, value: string): Element {
2421
- const rewrittenHref = node ? node.resolveURL(value) : null;
2422
- if (rewrittenHref === null) {
2423
- const span = document.createElement('span');
2424
- setValueWithEntities.call(this, span, value);
2425
- return span;
2426
- }
2427
- value = value.replace(closingPunctuationRegex, '$&\u200B');
2428
- if (value.startsWith('data:')) {
2429
- value = Platform.StringUtilities.trimMiddle(value, 60);
2430
- }
2431
- const link = node && node.nodeName().toLowerCase() === 'a' ?
2432
- Link.create(rewrittenHref, value, undefined, 'image-url') :
2433
- Components.Linkifier.Linkifier.linkifyURL(rewrittenHref, {
2434
- text: value,
2435
- preventClick: true,
2436
- showColumnNumber: false,
2437
- inlineFrameIndex: 0,
2438
- });
2439
- return ImagePreviewPopover.setImageUrl(link, rewrittenHref);
2440
- }
2441
-
2442
- const nodeName = node ? node.nodeName().toLowerCase() : '';
2443
- // If the href/src attribute has a value, attempt to link it.
2444
- // There's no point trying to link it if the value is empty (e.g. <a href=''>).
2445
- if (nodeName && (name === 'src' || name === 'href') && value) {
2446
- attrValueElement.appendChild(linkifyValue.call(this, value));
2447
- } else if ((nodeName === 'img' || nodeName === 'source') && name === 'srcset') {
2448
- attrValueElement.appendChild(linkifySrcset.call(this, value));
2449
- } else if (nodeName === 'image' && (name === 'xlink:href' || name === 'href')) {
2450
- attrValueElement.appendChild(linkifySrcset.call(this, value));
2451
- } else {
2452
- setValueWithEntities.call(this, attrValueElement, value);
2453
- }
2454
-
2455
- switch (name) {
2456
- case 'popovertarget': {
2457
- const linkedPart = value ? attrValueElement : attrNameElement;
2458
- void this.linkifyElementByRelation(
2459
- linkedPart, Protocol.DOM.GetElementByRelationRequestRelation.PopoverTarget,
2460
- i18nString(UIStrings.showPopoverTarget));
2461
- break;
2462
- }
2463
- case 'interesttarget': {
2464
- const linkedPart = value ? attrValueElement : attrNameElement;
2465
- void this.linkifyElementByRelation(
2466
- linkedPart, Protocol.DOM.GetElementByRelationRequestRelation.InterestTarget,
2467
- i18nString(UIStrings.showInterestTarget));
2468
- break;
2469
- }
2470
- case 'commandfor': {
2471
- const linkedPart = value ? attrValueElement : attrNameElement;
2472
- void this.linkifyElementByRelation(
2473
- linkedPart, Protocol.DOM.GetElementByRelationRequestRelation.CommandFor,
2474
- i18nString(UIStrings.showCommandForTarget));
2475
- break;
2476
- }
2477
- }
2478
-
2479
- if (hasText) {
2480
- UI.UIUtils.createTextChild(attrSpanElement, '"');
2481
- }
2482
-
2483
- function linkifySrcset(this: ElementsTreeElement, value: string): DocumentFragment {
2484
- // Splitting normally on commas or spaces will break on valid srcsets "foo 1x,bar 2x" and "data:,foo 1x".
2485
- // 1) Let the index of the next space be `indexOfSpace`.
2486
- // 2a) If the character at `indexOfSpace - 1` is a comma, collect the preceding characters up to
2487
- // `indexOfSpace - 1` as a URL and repeat step 1).
2488
- // 2b) Else, collect the preceding characters as a URL.
2489
- // 3) Collect the characters from `indexOfSpace` up to the next comma as the size descriptor and repeat step 1).
2490
- // https://html.spec.whatwg.org/C/#parse-a-srcset-attribute
2491
- const fragment = document.createDocumentFragment();
2492
- let i = 0;
2493
- while (value.length) {
2494
- if (i++ > 0) {
2495
- UI.UIUtils.createTextChild(fragment, ' ');
2496
- }
2497
- value = value.trim();
2498
- // The url and descriptor may end with a separating comma.
2499
- let url = '';
2500
- let descriptor = '';
2501
- const indexOfSpace = value.search(/\s/);
2502
- if (indexOfSpace === -1) {
2503
- url = value;
2504
- } else if (indexOfSpace > 0 && value[indexOfSpace - 1] === ',') {
2505
- url = value.substring(0, indexOfSpace);
2506
- } else {
2507
- url = value.substring(0, indexOfSpace);
2508
- const indexOfComma = value.indexOf(',', indexOfSpace);
2509
- if (indexOfComma !== -1) {
2510
- descriptor = value.substring(indexOfSpace, indexOfComma + 1);
2511
- } else {
2512
- descriptor = value.substring(indexOfSpace);
2513
- }
2514
- }
2515
-
2516
- if (url) {
2517
- // Up to one trailing comma should be removed from `url`.
2518
- if (url.endsWith(',')) {
2519
- fragment.appendChild(linkifyValue.call(this, url.substring(0, url.length - 1)));
2520
- UI.UIUtils.createTextChild(fragment, ',');
2521
- } else {
2522
- fragment.appendChild(linkifyValue.call(this, url));
2523
- }
2524
- }
2525
- if (descriptor) {
2526
- UI.UIUtils.createTextChild(fragment, descriptor);
2527
- }
2528
- value = value.substring(url.length + descriptor.length);
2529
- }
2530
- return fragment;
2531
- }
2532
-
2533
- return attrSpanElement;
2534
- }
2535
-
2536
- private async linkifyElementByRelation(
2537
- linkContainer: Element, relation: Protocol.DOM.GetElementByRelationRequestRelation,
2538
- tooltip: string): Promise<void> {
2539
- const relatedElementId = await this.nodeInternal.domModel().getElementByRelation(this.nodeInternal.id, relation);
2540
- const relatedElement = this.nodeInternal.domModel().nodeForId(relatedElementId);
2541
- if (!relatedElement) {
2542
- return;
2543
- }
2544
- const link = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(relatedElement, {
2545
- preventKeyboardFocus: true,
2546
- tooltip,
2547
- textContent: linkContainer.textContent || undefined,
2548
- isDynamicLink: true,
2549
- });
2550
- linkContainer.removeChildren();
2551
- linkContainer.append(link);
2552
- }
2553
-
2554
- private buildPseudoElementDOM(parentElement: DocumentFragment, pseudoElementName: string): void {
2555
- const pseudoElement = parentElement.createChild('span', 'webkit-html-pseudo-element');
2556
- pseudoElement.textContent = pseudoElementName;
2557
- UI.UIUtils.createTextChild(parentElement, '\u200B');
2558
- }
2559
-
2560
- private buildTagDOM(
2561
- parentElement: DocumentFragment, tagName: string, isClosingTag: boolean, isDistinctTreeElement: boolean,
2562
- updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null): void {
2563
- const node = this.nodeInternal;
2564
- const classes = ['webkit-html-tag'];
2565
- if (isClosingTag && isDistinctTreeElement) {
2566
- classes.push('close');
2567
- }
2568
- const tagElement = parentElement.createChild('span', classes.join(' '));
2569
- UI.UIUtils.createTextChild(tagElement, '<');
2570
- const tagNameElement =
2571
- tagElement.createChild('span', isClosingTag ? 'webkit-html-close-tag-name' : 'webkit-html-tag-name');
2572
- if (!isClosingTag) {
2573
- tagNameElement.setAttribute('jslog', `${VisualLogging.value('tag-name').track({change: true, dblclick: true})}`);
2574
- }
2575
- tagNameElement.textContent = (isClosingTag ? '/' : '') + tagName;
2576
- if (!isClosingTag) {
2577
- if (node.hasAttributes()) {
2578
- const attributes = node.attributes();
2579
- for (let i = 0; i < attributes.length; ++i) {
2580
- const attr = attributes[i];
2581
- UI.UIUtils.createTextChild(tagElement, ' ');
2582
- this.buildAttributeDOM(tagElement, attr.name, attr.value, updateRecord, false, node);
2583
- }
2584
- }
2585
- if (updateRecord) {
2586
- let hasUpdates: boolean = updateRecord.hasRemovedAttributes() || updateRecord.hasRemovedChildren();
2587
- hasUpdates = hasUpdates || (!this.expanded && updateRecord.hasChangedChildren());
2588
- if (hasUpdates) {
2589
- UI.UIUtils.runCSSAnimationOnce(tagNameElement, 'dom-update-highlight');
2590
- }
2591
- }
2592
- }
2593
-
2594
- UI.UIUtils.createTextChild(tagElement, '>');
2595
- UI.UIUtils.createTextChild(parentElement, '\u200B');
2596
- if (tagElement.textContent) {
2597
- UI.ARIAUtils.setLabel(tagElement, tagElement.textContent);
2598
- }
2599
- }
2600
-
2601
- private nodeTitleInfo(updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null): DocumentFragment {
2602
- const node = this.nodeInternal;
2603
- const titleDOM = document.createDocumentFragment();
2604
- const updateSearchHighlight = (): void => {
2605
- this.#highlightSearchResults();
2606
- };
2607
-
2608
- switch (node.nodeType()) {
2609
- case Node.ATTRIBUTE_NODE:
2610
- this.buildAttributeDOM(titleDOM, (node.name as string), (node.value as string), updateRecord, true);
2611
- break;
2612
-
2613
- case Node.ELEMENT_NODE: {
2614
- if (node.pseudoType()) {
2615
- let pseudoElementName = node.nodeName();
2616
- const pseudoIdentifier = node.pseudoIdentifier();
2617
- if (pseudoIdentifier) {
2618
- pseudoElementName += `(${pseudoIdentifier})`;
2619
- }
2620
- this.buildPseudoElementDOM(titleDOM, pseudoElementName);
2621
- break;
2622
- }
2623
-
2624
- const tagName = node.nodeNameInCorrectCase();
2625
- if (this.isClosingTag()) {
2626
- this.buildTagDOM(titleDOM, tagName, true, true, updateRecord);
2627
- break;
2628
- }
2629
-
2630
- this.buildTagDOM(titleDOM, tagName, false, false, updateRecord);
2631
-
2632
- if (this.isExpandable()) {
2633
- if (!this.expanded) {
2634
- const expandButton = new ElementsComponents.ElementsTreeExpandButton.ElementsTreeExpandButton();
2635
- expandButton.data = {
2636
- clickHandler: () => this.expand(),
2637
- };
2638
- titleDOM.appendChild(expandButton);
2639
-
2640
- // This hidden span with … is for blink layout tests.
2641
- // The method dumpElementsTree(front_end/legacy_test_runner/elements_test_runner/ElementsTestRunner.js)
2642
- // dumps … to identify expandable element.
2643
- const hidden = document.createElement('span');
2644
- hidden.textContent = '…';
2645
- hidden.style.fontSize = '0';
2646
- titleDOM.appendChild(hidden);
2647
-
2648
- UI.UIUtils.createTextChild(titleDOM, '\u200B');
2649
- this.buildTagDOM(titleDOM, tagName, true, false, updateRecord);
2650
- }
2651
- break;
2652
- }
2653
-
2654
- if (ElementsTreeElement.canShowInlineText(node)) {
2655
- const textNodeElement = titleDOM.createChild('span', 'webkit-html-text-node');
2656
- textNodeElement.setAttribute(
2657
- 'jslog', `${VisualLogging.value('text-node').track({change: true, dblclick: true})}`);
2658
- const firstChild = node.firstChild;
2659
- if (!firstChild) {
2660
- throw new Error('ElementsTreeElement._nodeTitleInfo expects node.firstChild to be defined.');
2661
- }
2662
- const result = convertUnicodeCharsToHTMLEntities(firstChild.nodeValue());
2663
- textNodeElement.textContent = Platform.StringUtilities.collapseWhitespace(result.text);
2664
- Highlighting.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, 'webkit-html-entity-value');
2665
- UI.UIUtils.createTextChild(titleDOM, '\u200B');
2666
- this.buildTagDOM(titleDOM, tagName, true, false, updateRecord);
2667
- if (updateRecord?.hasChangedChildren()) {
2668
- UI.UIUtils.runCSSAnimationOnce(textNodeElement, 'dom-update-highlight');
2669
- }
2670
- if (updateRecord?.isCharDataModified()) {
2671
- UI.UIUtils.runCSSAnimationOnce(textNodeElement, 'dom-update-highlight');
2672
- }
2673
- break;
2674
- }
2675
-
2676
- if (this.treeOutline?.isXMLMimeType || !ForbiddenClosingTagElements.has(tagName)) {
2677
- this.buildTagDOM(titleDOM, tagName, true, false, updateRecord);
2678
- }
2679
- break;
2680
- }
2681
-
2682
- case Node.TEXT_NODE:
2683
- if (node.parentNode && node.parentNode.nodeName().toLowerCase() === 'script') {
2684
- const newNode = titleDOM.createChild('span', 'webkit-html-text-node webkit-html-js-node');
2685
- newNode.setAttribute(
2686
- 'jslog', `${VisualLogging.value('script-text-node').track({change: true, dblclick: true})}`);
2687
- const text = node.nodeValue();
2688
- newNode.textContent = text.replace(/^[\n\r]+|\s+$/g, '');
2689
- void CodeHighlighter.CodeHighlighter.highlightNode(newNode, 'text/javascript').then(updateSearchHighlight);
2690
- } else if (node.parentNode && node.parentNode.nodeName().toLowerCase() === 'style') {
2691
- const newNode = titleDOM.createChild('span', 'webkit-html-text-node webkit-html-css-node');
2692
- newNode.setAttribute(
2693
- 'jslog', `${VisualLogging.value('css-text-node').track({change: true, dblclick: true})}`);
2694
- const text = node.nodeValue();
2695
- newNode.textContent = text.replace(/^[\n\r]+|\s+$/g, '');
2696
- void CodeHighlighter.CodeHighlighter.highlightNode(newNode, 'text/css').then(updateSearchHighlight);
2697
- } else {
2698
- UI.UIUtils.createTextChild(titleDOM, '"');
2699
- const textNodeElement = titleDOM.createChild('span', 'webkit-html-text-node');
2700
- textNodeElement.setAttribute(
2701
- 'jslog', `${VisualLogging.value('text-node').track({change: true, dblclick: true})}`);
2702
- const result = convertUnicodeCharsToHTMLEntities(node.nodeValue());
2703
- textNodeElement.textContent = Platform.StringUtilities.collapseWhitespace(result.text);
2704
- Highlighting.highlightRangesWithStyleClass(textNodeElement, result.entityRanges, 'webkit-html-entity-value');
2705
- UI.UIUtils.createTextChild(titleDOM, '"');
2706
- if (updateRecord?.isCharDataModified()) {
2707
- UI.UIUtils.runCSSAnimationOnce(textNodeElement, 'dom-update-highlight');
2708
- }
2709
- }
2710
- break;
2711
-
2712
- case Node.COMMENT_NODE: {
2713
- const commentElement = titleDOM.createChild('span', 'webkit-html-comment');
2714
- UI.UIUtils.createTextChild(commentElement, '<!--' + node.nodeValue() + '-->');
2715
- break;
2716
- }
2717
-
2718
- case Node.DOCUMENT_TYPE_NODE: {
2719
- const docTypeElement = titleDOM.createChild('span', 'webkit-html-doctype');
2720
- UI.UIUtils.createTextChild(docTypeElement, '<!DOCTYPE ' + node.nodeName());
2721
- if (node.publicId) {
2722
- UI.UIUtils.createTextChild(docTypeElement, ' PUBLIC "' + node.publicId + '"');
2723
- if (node.systemId) {
2724
- UI.UIUtils.createTextChild(docTypeElement, ' "' + node.systemId + '"');
2725
- }
2726
- } else if (node.systemId) {
2727
- UI.UIUtils.createTextChild(docTypeElement, ' SYSTEM "' + node.systemId + '"');
2728
- }
2729
-
2730
- if (node.internalSubset) {
2731
- UI.UIUtils.createTextChild(docTypeElement, ' [' + node.internalSubset + ']');
2732
- }
2733
-
2734
- UI.UIUtils.createTextChild(docTypeElement, '>');
2735
- break;
2736
- }
2737
-
2738
- case Node.CDATA_SECTION_NODE: {
2739
- const cdataElement = titleDOM.createChild('span', 'webkit-html-text-node');
2740
- UI.UIUtils.createTextChild(cdataElement, '<![CDATA[' + node.nodeValue() + ']]>');
2741
- break;
2742
- }
2743
-
2744
- case Node.DOCUMENT_NODE: {
2745
- const documentElement = titleDOM.createChild('span');
2746
- UI.UIUtils.createTextChild(documentElement, '#document (');
2747
- const text = (node as SDK.DOMModel.DOMDocument).documentURL;
2748
- documentElement.appendChild(Components.Linkifier.Linkifier.linkifyURL(text, {
2749
- text,
2750
- preventClick: true,
2751
- showColumnNumber: false,
2752
- inlineFrameIndex: 0,
2753
- }));
2754
- UI.UIUtils.createTextChild(documentElement, ')');
2755
- break;
2756
- }
2757
-
2758
- case Node.DOCUMENT_FRAGMENT_NODE: {
2759
- const fragmentElement = titleDOM.createChild('span', 'webkit-html-fragment');
2760
- fragmentElement.textContent = Platform.StringUtilities.collapseWhitespace(node.nodeNameInCorrectCase());
2761
- break;
2762
- }
2763
-
2764
- default: {
2765
- const nameWithSpaceCollapsed = Platform.StringUtilities.collapseWhitespace(node.nodeNameInCorrectCase());
2766
- UI.UIUtils.createTextChild(titleDOM, nameWithSpaceCollapsed);
2767
- }
2768
- }
2769
-
2770
- return titleDOM;
2771
- }
2772
-
2773
2797
  async remove(): Promise<void> {
2774
2798
  if (this.treeOutline?.isToggledToHidden(this.nodeInternal)) {
2775
2799
  // Unhide the node before removing. This avoids inconsistent state if the node is restored via undo.