chrome-devtools-frontend 1.0.1581708 → 1.0.1583146

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 (73) hide show
  1. package/front_end/core/root/Runtime.ts +0 -5
  2. package/front_end/core/sdk/NetworkManager.ts +63 -115
  3. package/front_end/core/sdk/RemoteObject.ts +7 -1
  4. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +9 -24
  5. package/front_end/entrypoints/greendev_floaty/floaty.css +1 -1
  6. package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +1 -1
  7. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  8. package/front_end/generated/SupportedCSSProperties.js +2 -0
  9. package/front_end/generated/protocol.ts +0 -6
  10. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +6 -6
  11. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +17 -9
  12. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +2 -6
  13. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +66 -2
  14. package/front_end/models/greendev/Prototypes.ts +1 -10
  15. package/front_end/models/issues_manager/ConnectionAllowlistIssue.ts +75 -0
  16. package/front_end/models/issues_manager/FederatedAuthRequestIssue.ts +0 -30
  17. package/front_end/models/issues_manager/IssuesManager.ts +5 -0
  18. package/front_end/models/issues_manager/descriptions/connectionAllowlistInvalidAllowlistItemType.md +12 -0
  19. package/front_end/models/issues_manager/descriptions/connectionAllowlistInvalidHeader.md +12 -0
  20. package/front_end/models/issues_manager/descriptions/connectionAllowlistInvalidUrlPattern.md +8 -0
  21. package/front_end/models/issues_manager/descriptions/connectionAllowlistItemNotInnerList.md +12 -0
  22. package/front_end/models/issues_manager/descriptions/connectionAllowlistMoreThanOneList.md +7 -0
  23. package/front_end/models/issues_manager/descriptions/connectionAllowlistReportingEndpointNotToken.md +10 -0
  24. package/front_end/models/issues_manager/issues_manager.ts +2 -0
  25. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +22 -4
  26. package/front_end/panels/ai_assistance/components/ChatInput.ts +7 -3
  27. package/front_end/panels/ai_assistance/components/ChatMessage.ts +4 -2
  28. package/front_end/panels/application/preloading/PreloadingView.ts +8 -1
  29. package/front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts +4 -1
  30. package/front_end/panels/application/preloading/components/PreloadingGrid.ts +2 -1
  31. package/front_end/panels/application/preloading/components/PreloadingString.ts +12 -3
  32. package/front_end/panels/application/preloading/helper/PreloadingForward.ts +14 -0
  33. package/front_end/panels/autofill/AutofillView.ts +4 -8
  34. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +37 -3
  35. package/front_end/panels/changes/ChangesSidebar.ts +2 -6
  36. package/front_end/panels/common/AiCodeGenerationTeaser.ts +27 -8
  37. package/front_end/panels/console/ConsoleSidebar.ts +3 -11
  38. package/front_end/panels/elements/ComputedStyleWidget.ts +41 -29
  39. package/front_end/panels/elements/ElementStatePaneWidget.ts +1 -1
  40. package/front_end/panels/elements/ElementsTreeElement.ts +487 -426
  41. package/front_end/panels/elements/EventListenersWidget.ts +2 -4
  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 +30 -0
  45. package/front_end/panels/lighthouse/LighthouseStartView.ts +3 -5
  46. package/front_end/panels/lighthouse/lighthouseStartView.css +6 -0
  47. package/front_end/panels/network/NetworkLogView.ts +67 -108
  48. package/front_end/panels/network/RequestConditionsDrawer.ts +40 -131
  49. package/front_end/panels/network/RequestInitiatorView.ts +19 -8
  50. package/front_end/panels/network/network-meta.ts +4 -27
  51. package/front_end/panels/settings/AISettingsTab.ts +1 -5
  52. package/front_end/panels/settings/SettingsScreen.ts +0 -51
  53. package/front_end/panels/settings/WorkspaceSettingsTab.ts +1 -1
  54. package/front_end/panels/sources/CallStackSidebarPane.ts +2 -2
  55. package/front_end/panels/timeline/AnimationsTrackAppender.ts +4 -1
  56. package/front_end/panels/timeline/InteractionsTrackAppender.ts +1 -1
  57. package/front_end/panels/timeline/TimelineUIUtils.ts +13 -16
  58. package/front_end/third_party/chromium/README.chromium +1 -1
  59. package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +5 -1
  60. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +8 -0
  61. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +8 -0
  62. package/front_end/ui/components/text_editor/config.ts +6 -0
  63. package/front_end/ui/legacy/Toolbar.ts +16 -8
  64. package/front_end/ui/legacy/Treeoutline.ts +4 -4
  65. package/front_end/ui/legacy/UIUtils.ts +35 -4
  66. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +6 -11
  67. package/front_end/ui/legacy/components/utils/Linkifier.ts +4 -7
  68. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  69. package/package.json +1 -1
  70. package/front_end/models/issues_manager/descriptions/federatedAuthRequestClientMetadataHttpNotFound.md +0 -1
  71. package/front_end/models/issues_manager/descriptions/federatedAuthRequestClientMetadataInvalidResponse.md +0 -1
  72. package/front_end/models/issues_manager/descriptions/federatedAuthRequestClientMetadataNoResponse.md +0 -1
  73. package/front_end/panels/elements/computedStyleSidebarPane.css +0 -18
@@ -379,6 +379,15 @@ export function isOpeningTag(context: TagTypeContext): context is OpeningTagCont
379
379
  }
380
380
 
381
381
  export interface ViewInput {
382
+ node: SDK.DOMModel.DOMNode|null;
383
+ isClosingTag: boolean;
384
+ expanded: boolean;
385
+ isExpandable: boolean;
386
+ isXMLMimeType: boolean;
387
+ updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null;
388
+ onHighlightSearchResults: () => void;
389
+ onExpand: () => void;
390
+
382
391
  containerAdornerActive: boolean;
383
392
  flexAdornerActive: boolean;
384
393
  gridAdornerActive: boolean;
@@ -398,7 +407,6 @@ export interface ViewInput {
398
407
  showViewSourceAdorner: boolean;
399
408
  showScrollAdorner: boolean;
400
409
  showScrollSnapAdorner: boolean;
401
- nodeInfo?: DocumentFragment;
402
410
  topLayerIndex: number;
403
411
  scrollSnapAdornerActive: boolean;
404
412
 
@@ -436,7 +444,7 @@ export interface ViewOutput {
436
444
 
437
445
  export function adornerRef(): DirectiveResult<typeof Lit.Directives.RefDirective> {
438
446
  let adorner: Adorners.Adorner.Adorner|undefined;
439
- return ref((el?: Element) => {
447
+ return ref(el => {
440
448
  if (adorner) {
441
449
  ElementsPanel.instance().deregisterAdorner(adorner);
442
450
  }
@@ -467,6 +475,411 @@ function handleAdornerKeydown(cb: (event: Event) => void): (event: KeyboardEvent
467
475
  };
468
476
  }
469
477
 
478
+ function renderTitle(
479
+ node: SDK.DOMModel.DOMNode,
480
+ isClosingTag: boolean,
481
+ expanded: boolean,
482
+ isExpandable: boolean,
483
+ isXMLMimeType: boolean,
484
+ updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null,
485
+ onUpdateSearchHighlight: () => void,
486
+ onExpand: () => void,
487
+ ): Lit.LitTemplate {
488
+ switch (node.nodeType()) {
489
+ case Node.ATTRIBUTE_NODE:
490
+ return renderAttribute({name: node.name as string, value: node.value as string}, updateRecord, true, node);
491
+
492
+ case Node.ELEMENT_NODE: {
493
+ if (node.pseudoType()) {
494
+ let pseudoElementName = node.nodeName();
495
+ const pseudoIdentifier = node.pseudoIdentifier();
496
+ if (pseudoIdentifier) {
497
+ pseudoElementName += `(${pseudoIdentifier})`;
498
+ }
499
+ return html`<span class="webkit-html-pseudo-element">${pseudoElementName}</span>\u200B`;
500
+ }
501
+
502
+ const tagName = node.nodeNameInCorrectCase();
503
+ if (isClosingTag) {
504
+ return renderTag(node, tagName, true, expanded, true, updateRecord);
505
+ }
506
+
507
+ const openingTag = renderTag(node, tagName, false, expanded, false, updateRecord);
508
+
509
+ if (isExpandable) {
510
+ if (!expanded) {
511
+ return html`${openingTag}<devtools-elements-tree-expand-button .data=${
512
+ {clickHandler: onExpand} as
513
+ ElementsComponents.ElementsTreeExpandButton
514
+ .ElementsTreeExpandButtonData}></devtools-elements-tree-expand-button><span style="font-size: 0;"
515
+ >…</span>\u200B${renderTag(node, tagName, true, expanded, false, updateRecord)}`;
516
+ }
517
+ return openingTag;
518
+ }
519
+
520
+ if (ElementsTreeElement.canShowInlineText(node)) {
521
+ const firstChild = node.firstChild;
522
+ if (!firstChild) {
523
+ throw new Error('ElementsTreeElement._nodeTitleInfo expects node.firstChild to be defined.');
524
+ }
525
+ const result = convertUnicodeCharsToHTMLEntities(firstChild.nodeValue());
526
+ const textContent = Platform.StringUtilities.collapseWhitespace(result.text);
527
+
528
+ const highlightAnimation = (updateRecord?.hasChangedChildren() || updateRecord?.isCharDataModified()) ?
529
+ ref(el => {
530
+ if (el) {
531
+ UI.UIUtils.runCSSAnimationOnce(el, 'dom-update-highlight');
532
+ }
533
+ }) :
534
+ nothing;
535
+
536
+ const renderTextNode = ref(el => {
537
+ if (el) {
538
+ el.textContent = textContent;
539
+ Highlighting.highlightRangesWithStyleClass(el, result.entityRanges, 'webkit-html-entity-value');
540
+ }
541
+ });
542
+
543
+ return html`${openingTag}<span class="webkit-html-text-node" jslog=${
544
+ VisualLogging.value('text-node').track({change: true, dblclick: true})} ${highlightAnimation} ${
545
+ renderTextNode}></span>\u200B${renderTag(node, tagName, true, expanded, false, updateRecord)}`;
546
+ }
547
+
548
+ if (isXMLMimeType || !ForbiddenClosingTagElements.has(tagName)) {
549
+ return html`${openingTag}${renderTag(node, tagName, true, expanded, false, updateRecord)}`;
550
+ }
551
+ return openingTag;
552
+ }
553
+
554
+ case Node.TEXT_NODE: {
555
+ if (node.parentNode && node.parentNode.nodeName().toLowerCase() === 'script') {
556
+ const text = node.nodeValue();
557
+ const highlightNode = ref(el => {
558
+ if (el) {
559
+ el.textContent = text.replace(/^[\n\r]+|\s+$/g, '');
560
+ void CodeHighlighter.CodeHighlighter.highlightNode(el, 'text/javascript').then(onUpdateSearchHighlight);
561
+ }
562
+ });
563
+ return html`<span class="webkit-html-text-node webkit-html-js-node" jslog=${
564
+ VisualLogging.value('script-text-node').track({change: true, dblclick: true})} ${highlightNode}></span>`;
565
+ }
566
+ if (node.parentNode && node.parentNode.nodeName().toLowerCase() === 'style') {
567
+ const text = node.nodeValue();
568
+ const highlightNode = ref(el => {
569
+ if (el) {
570
+ el.textContent = text.replace(/^[\n\r]+|\s+$/g, '');
571
+ void CodeHighlighter.CodeHighlighter.highlightNode(el, 'text/css').then(onUpdateSearchHighlight);
572
+ }
573
+ });
574
+ return html`<span class="webkit-html-text-node webkit-html-css-node" jslog=${
575
+ VisualLogging.value('css-text-node').track({change: true, dblclick: true})} ${highlightNode}></span>`;
576
+ }
577
+
578
+ const result = convertUnicodeCharsToHTMLEntities(node.nodeValue());
579
+ const textContent = Platform.StringUtilities.collapseWhitespace(result.text);
580
+ const highlightAnimation = updateRecord?.isCharDataModified() ? ref(el => {
581
+ if (el) {
582
+ UI.UIUtils.runCSSAnimationOnce(el, 'dom-update-highlight');
583
+ }
584
+ }) :
585
+ nothing;
586
+ const renderTextNode = ref(el => {
587
+ if (el) {
588
+ el.textContent = textContent;
589
+ Highlighting.highlightRangesWithStyleClass(el, result.entityRanges, 'webkit-html-entity-value');
590
+ }
591
+ });
592
+ return html`"<span class="webkit-html-text-node" jslog=${VisualLogging.value('text-node').track({
593
+ change: true,
594
+ dblclick: true
595
+ })} ${highlightAnimation} ${renderTextNode}></span>"`;
596
+ }
597
+
598
+ case Node.COMMENT_NODE: {
599
+ return html`<span class="webkit-html-comment">&lt;!--${node.nodeValue()}--&gt;</span>`;
600
+ }
601
+
602
+ case Node.DOCUMENT_TYPE_NODE: {
603
+ let doctype = '<!DOCTYPE ' + node.nodeName();
604
+ if (node.publicId) {
605
+ doctype += ' PUBLIC "' + node.publicId + '"';
606
+ if (node.systemId) {
607
+ doctype += ' "' + node.systemId + '"';
608
+ }
609
+ } else if (node.systemId) {
610
+ doctype += ' SYSTEM "' + node.systemId + '"';
611
+ }
612
+ if (node.internalSubset) {
613
+ doctype += ' [' + node.internalSubset + ']';
614
+ }
615
+ doctype += '>';
616
+ return html`<span class="webkit-html-doctype">${doctype}</span>`;
617
+ }
618
+
619
+ case Node.CDATA_SECTION_NODE: {
620
+ return html`<span class="webkit-html-text-node">&lt;![CDATA[${node.nodeValue()}]]&gt;</span>`;
621
+ }
622
+
623
+ case Node.DOCUMENT_NODE: {
624
+ const text = (node as SDK.DOMModel.DOMDocument).documentURL;
625
+ const linkify = ref(el => {
626
+ if (el) {
627
+ el.removeChildren();
628
+ el.appendChild(Components.Linkifier.Linkifier.linkifyURL(text, {
629
+ text,
630
+ preventClick: true,
631
+ showColumnNumber: false,
632
+ inlineFrameIndex: 0,
633
+ }));
634
+ }
635
+ });
636
+ return html`<span>#document (<span ${linkify}></span>)</span>`;
637
+ }
638
+
639
+ case Node.DOCUMENT_FRAGMENT_NODE: {
640
+ return html`<span class="webkit-html-fragment">${
641
+ Platform.StringUtilities.collapseWhitespace(node.nodeNameInCorrectCase())}</span>`;
642
+ }
643
+
644
+ default: {
645
+ return html`${Platform.StringUtilities.collapseWhitespace(node.nodeNameInCorrectCase())}`;
646
+ }
647
+ }
648
+ }
649
+
650
+ function renderAttribute(
651
+ attr: {name: string, value?: string}, updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null,
652
+ isDiff: boolean, node: SDK.DOMModel.DOMNode): Lit.LitTemplate {
653
+ const name = attr.name;
654
+ const value = attr.value || '';
655
+ const forceValue = isDiff;
656
+ const closingPunctuationRegex = /[\/;:\)\]\}]/g;
657
+ let highlightIndex = 0;
658
+ let highlightCount = 0;
659
+ let additionalHighlightOffset = 0;
660
+
661
+ function setValueWithEntities(element: Element, value: string): void {
662
+ const result = convertUnicodeCharsToHTMLEntities(value);
663
+ highlightCount = result.entityRanges.length;
664
+ const newValue = result.text.replace(closingPunctuationRegex, (match, replaceOffset) => {
665
+ while (highlightIndex < highlightCount && result.entityRanges[highlightIndex].offset < replaceOffset) {
666
+ result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
667
+ ++highlightIndex;
668
+ }
669
+ additionalHighlightOffset += 1;
670
+ return match + '\u200B';
671
+ });
672
+
673
+ while (highlightIndex < highlightCount) {
674
+ result.entityRanges[highlightIndex].offset += additionalHighlightOffset;
675
+ ++highlightIndex;
676
+ }
677
+ element.setTextContentTruncatedIfNeeded(newValue);
678
+ Highlighting.highlightRangesWithStyleClass(element, result.entityRanges, 'webkit-html-entity-value');
679
+ }
680
+
681
+ const hasText = (forceValue || value.length > 0);
682
+ const jslog = VisualLogging.value(name === 'style' ? 'style-attribute' : 'attribute').track({
683
+ change: true,
684
+ dblclick: true,
685
+ });
686
+
687
+ const highlightNameAnimation = updateRecord?.isAttributeModified(name) && !hasText ? ref(el => {
688
+ if (el) {
689
+ UI.UIUtils.runCSSAnimationOnce(el, 'dom-update-highlight');
690
+ }
691
+ }) :
692
+ nothing;
693
+
694
+ const highlightValueAnimation = updateRecord?.isAttributeModified(name) && hasText ? ref(el => {
695
+ if (el) {
696
+ UI.UIUtils.runCSSAnimationOnce(el, 'dom-update-highlight');
697
+ }
698
+ }) :
699
+ nothing;
700
+
701
+ function linkifyValue(value: string): Element {
702
+ const rewrittenHref = node ? node.resolveURL(value) : null;
703
+ if (rewrittenHref === null) {
704
+ const span = document.createElement('span');
705
+ setValueWithEntities(span, value);
706
+ return span;
707
+ }
708
+ value = value.replace(closingPunctuationRegex, '$&\u200B');
709
+ if (value.startsWith('data:')) {
710
+ value = Platform.StringUtilities.trimMiddle(value, 60);
711
+ }
712
+ const link = node && node.nodeName().toLowerCase() === 'a' ?
713
+ Link.create(rewrittenHref, value, undefined, 'image-url') :
714
+ Components.Linkifier.Linkifier.linkifyURL(rewrittenHref, {
715
+ text: value,
716
+ preventClick: true,
717
+ showColumnNumber: false,
718
+ inlineFrameIndex: 0,
719
+ });
720
+ return ImagePreviewPopover.setImageUrl(link, rewrittenHref);
721
+ }
722
+
723
+ function linkifySrcset(value: string): DocumentFragment {
724
+ // Splitting normally on commas or spaces will break on valid srcsets "foo 1x,bar 2x" and "data:,foo 1x".
725
+ const fragment = document.createDocumentFragment();
726
+ let i = 0;
727
+ while (value.length) {
728
+ if (i++ > 0) {
729
+ UI.UIUtils.createTextChild(fragment, ' ');
730
+ }
731
+ value = value.trim();
732
+ let url = '';
733
+ let descriptor = '';
734
+ const indexOfSpace = value.search(/\s/);
735
+ if (indexOfSpace === -1) {
736
+ url = value;
737
+ } else if (indexOfSpace > 0 && value[indexOfSpace - 1] === ',') {
738
+ url = value.substring(0, indexOfSpace);
739
+ } else {
740
+ url = value.substring(0, indexOfSpace);
741
+ const indexOfComma = value.indexOf(',', indexOfSpace);
742
+ if (indexOfComma !== -1) {
743
+ descriptor = value.substring(indexOfSpace, indexOfComma + 1);
744
+ } else {
745
+ descriptor = value.substring(indexOfSpace);
746
+ }
747
+ }
748
+
749
+ if (url) {
750
+ if (url.endsWith(',')) {
751
+ fragment.appendChild(linkifyValue(url.substring(0, url.length - 1)));
752
+ UI.UIUtils.createTextChild(fragment, ',');
753
+ } else {
754
+ fragment.appendChild(linkifyValue(url));
755
+ }
756
+ }
757
+ if (descriptor) {
758
+ UI.UIUtils.createTextChild(fragment, descriptor);
759
+ }
760
+ value = value.substring(url.length + descriptor.length);
761
+ }
762
+ return fragment;
763
+ }
764
+
765
+ const nodeName = node ? node.nodeName().toLowerCase() : '';
766
+ const setAttrValue = ref(el => {
767
+ if (!el) {
768
+ return;
769
+ }
770
+ const valueElement = el as HTMLElement;
771
+ valueElement.removeChildren();
772
+ if (nodeName && (name === 'src' || name === 'href') && value) {
773
+ valueElement.appendChild(linkifyValue(value));
774
+ } else if ((nodeName === 'img' || nodeName === 'source') && name === 'srcset') {
775
+ valueElement.appendChild(linkifySrcset(value));
776
+ } else if (nodeName === 'image' && (name === 'xlink:href' || name === 'href')) {
777
+ valueElement.appendChild(linkifySrcset(value));
778
+ } else {
779
+ setValueWithEntities(valueElement, value);
780
+ }
781
+ });
782
+
783
+ const relationRef =
784
+ (relation: Protocol.DOM.GetElementByRelationRequestRelation, tooltip: string): ReturnType<typeof ref> =>
785
+ ref((el): void => {
786
+ if (!el) {
787
+ return;
788
+ }
789
+ void (async(): Promise<void> => {
790
+ const relatedElementId = await node.domModel().getElementByRelation(node.id, relation);
791
+ const relatedElement = node.domModel().nodeForId(relatedElementId);
792
+ if (!relatedElement) {
793
+ return;
794
+ }
795
+ const link = PanelsCommon.DOMLinkifier.Linkifier.instance().linkify(relatedElement, {
796
+ preventKeyboardFocus: true,
797
+ tooltip,
798
+ textContent: el.textContent || undefined,
799
+ isDynamicLink: true,
800
+ });
801
+ el.removeChildren();
802
+ el.append(link);
803
+ })();
804
+ });
805
+
806
+ let relationRefDirective: ReturnType<typeof relationRef> = ref(() => {});
807
+ if (!value) {
808
+ if (name === 'popovertarget') {
809
+ relationRefDirective = relationRef(
810
+ Protocol.DOM.GetElementByRelationRequestRelation.PopoverTarget, i18nString(UIStrings.showPopoverTarget));
811
+ } else if (name === 'interesttarget') {
812
+ relationRefDirective = relationRef(
813
+ Protocol.DOM.GetElementByRelationRequestRelation.InterestTarget, i18nString(UIStrings.showInterestTarget));
814
+ } else if (name === 'commandfor') {
815
+ relationRefDirective = relationRef(
816
+ Protocol.DOM.GetElementByRelationRequestRelation.CommandFor, i18nString(UIStrings.showCommandForTarget));
817
+ }
818
+ }
819
+
820
+ let valueRelationRefDirective: ReturnType<typeof relationRef> = ref(() => {});
821
+ if (value) {
822
+ if (name === 'popovertarget') {
823
+ valueRelationRefDirective = relationRef(
824
+ Protocol.DOM.GetElementByRelationRequestRelation.PopoverTarget, i18nString(UIStrings.showPopoverTarget));
825
+ } else if (name === 'interesttarget') {
826
+ valueRelationRefDirective = relationRef(
827
+ Protocol.DOM.GetElementByRelationRequestRelation.InterestTarget, i18nString(UIStrings.showInterestTarget));
828
+ } else if (name === 'commandfor') {
829
+ valueRelationRefDirective = relationRef(
830
+ Protocol.DOM.GetElementByRelationRequestRelation.CommandFor, i18nString(UIStrings.showCommandForTarget));
831
+ }
832
+ }
833
+
834
+ return html`<span class="webkit-html-attribute" jslog=${jslog}><span class="webkit-html-attribute-name" ${
835
+ highlightNameAnimation} ${relationRefDirective}>${name}</span>${
836
+ hasText ? html`=\u200B"<span class="webkit-html-attribute-value" ${highlightValueAnimation} ${setAttrValue} ${
837
+ valueRelationRefDirective}></span>"` :
838
+ nothing}</span>`;
839
+ }
840
+
841
+ function renderTag(
842
+ node: SDK.DOMModel.DOMNode, tagName: string, isClosingTag: boolean, expanded: boolean,
843
+ isDistinctTreeElement: boolean,
844
+ updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null): Lit.LitTemplate {
845
+ const classMap = {
846
+ 'webkit-html-tag': true,
847
+ close: isClosingTag && isDistinctTreeElement,
848
+ };
849
+
850
+ let hasUpdates = false;
851
+ const attributes = !isClosingTag && node.hasAttributes() ? node.attributes() : [];
852
+
853
+ if (!isClosingTag && updateRecord) {
854
+ hasUpdates = updateRecord.hasRemovedAttributes() || updateRecord.hasRemovedChildren();
855
+ hasUpdates = hasUpdates || (!expanded && updateRecord.hasChangedChildren());
856
+ }
857
+
858
+ const highlightAnimation = hasUpdates ? ref(el => {
859
+ if (el) {
860
+ UI.UIUtils.runCSSAnimationOnce(el, 'dom-update-highlight');
861
+ }
862
+ }) :
863
+ nothing;
864
+
865
+ // We are taking full text content of the tag, including attributes and children, to set the aria label.
866
+ // FIXME: we should compute the aria label ourselves if it is event needed.
867
+ const setAriaLabel = ref(el => {
868
+ if (el?.textContent) {
869
+ UI.ARIAUtils.setLabel(el, el.textContent);
870
+ }
871
+ });
872
+
873
+ const tagNameClass = isClosingTag ? 'webkit-html-close-tag-name' : 'webkit-html-tag-name';
874
+ const tagString = (isClosingTag ? '/' : '') + tagName;
875
+ const jslog = !isClosingTag ? VisualLogging.value('tag-name').track({change: true, dblclick: true}) : '';
876
+
877
+ return html`<span
878
+ class=${Lit.Directives.classMap(classMap)} ${setAriaLabel}
879
+ >&lt;<span class=${tagNameClass} jslog=${jslog || nothing} ${highlightAnimation}>${tagString}</span>${
880
+ attributes.map(attr => html` ${renderAttribute(attr, updateRecord, false, node)}`)}&gt;</span>\u200B`;
881
+ }
882
+
470
883
  export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement): void => {
471
884
  const hasAdorners = input.showAdAdorner || input.showContainerAdorner || input.showFlexAdorner ||
472
885
  input.showGridAdorner || input.showGridLanesAdorner || input.showMediaAdorner || input.showPopoverAdorner ||
@@ -479,7 +892,16 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
479
892
  // clang-format off
480
893
  render(html`
481
894
  <div ${ref(el => { output.contentElement = el as HTMLElement; })}>
482
- ${input.nodeInfo ? html`<span class="highlight">${input.nodeInfo}</span>` : nothing}
895
+ ${input.node ? html`<span class="highlight">${renderTitle(
896
+ input.node,
897
+ input.isClosingTag,
898
+ input.expanded,
899
+ input.isExpandable,
900
+ input.isXMLMimeType,
901
+ input.updateRecord,
902
+ input.onHighlightSearchResults,
903
+ input.onExpand,
904
+ )}</span>` : nothing}
483
905
  ${input.isHovered || input.isSelected ? html`
484
906
  <div class="selection fill" style=${`margin-left: ${-input.indent}px`}></div>
485
907
  ` : nothing}
@@ -681,9 +1103,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
681
1103
  nodeInternal: SDK.DOMModel.DOMNode;
682
1104
  override treeOutline: ElementsTreeOutline|null;
683
1105
 
684
- // Handled by the view output for now.
685
- contentElement!: HTMLElement;
686
-
687
1106
  private searchQuery: string|null;
688
1107
  #expandedChildrenLimit: number;
689
1108
  private readonly decorationsThrottler: Common.Throttler.Throttler;
@@ -699,7 +1118,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
699
1118
  readonly tagTypeContext: TagTypeContext;
700
1119
 
701
1120
  #adornersThrottler = new Common.Throttler.Throttler(100);
702
- #nodeInfo?: DocumentFragment;
703
1121
  #containerAdornerActive = false;
704
1122
  #flexAdornerActive = false;
705
1123
  #gridAdornerActive = false;
@@ -713,6 +1131,12 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
713
1131
  #descendantDecorations: Decoration[] = [];
714
1132
  #decorationsTooltip = '';
715
1133
 
1134
+ #updateRecord: Elements.ElementUpdateRecord.ElementUpdateRecord|null = null;
1135
+
1136
+ // Used to add the content to TreeElement's title element.
1137
+ // Relied on by web tests.
1138
+ #contentElement?: HTMLElement;
1139
+
716
1140
  constructor(node: SDK.DOMModel.DOMNode, isClosingTag?: boolean) {
717
1141
  // The title will be updated in onattach.
718
1142
  super();
@@ -816,9 +1240,23 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
816
1240
  }
817
1241
  }
818
1242
 
819
- performUpdate(): void {
1243
+ // ClearNode param is used to clean DOM after in-place editing..
1244
+ performUpdate(clearNode = false): void {
1245
+ if (this.editing) {
1246
+ return;
1247
+ }
1248
+ const output: ViewOutput = {};
820
1249
  DEFAULT_VIEW(
821
1250
  {
1251
+ node: !clearNode ? this.nodeInternal : null,
1252
+ isClosingTag: this.isClosingTag(),
1253
+ expanded: this.expanded,
1254
+ isExpandable: this.isExpandable(),
1255
+ isXMLMimeType: Boolean(this.treeOutline?.isXMLMimeType),
1256
+ updateRecord: this.#updateRecord,
1257
+ onHighlightSearchResults: () => this.#highlightSearchResults(),
1258
+ onExpand: () => this.expand(),
1259
+
822
1260
  containerAdornerActive: this.#containerAdornerActive,
823
1261
  showAdAdorner: this.nodeInternal.isAdFrameNode(),
824
1262
  showContainerAdorner: Boolean(this.#layout?.containerType) && !this.isClosingTag(),
@@ -847,7 +1285,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
847
1285
  showSlotAdorner: Boolean(this.nodeInternal.assignedSlot) && !this.isClosingTag(),
848
1286
  showStartingStyleAdorner: this.nodeInternal.affectedByStartingStyles() && !this.isClosingTag(),
849
1287
  startingStyleAdornerActive: this.#startingStyleAdornerActive,
850
- nodeInfo: this.#nodeInfo,
851
1288
  onStartingStyleAdornerClick: (event: Event) => this.#onStartingStyleAdornerClick(event),
852
1289
  onSlotAdornerClick: () => {
853
1290
  if (this.nodeInternal.assignedSlot) {
@@ -888,7 +1325,12 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
888
1325
  }
889
1326
  },
890
1327
  },
891
- this, this.listItemElement);
1328
+ output, this.listItemElement);
1329
+
1330
+ this.#contentElement = output.contentElement;
1331
+ if (this.#updateRecord) {
1332
+ this.#updateRecord = null;
1333
+ }
892
1334
  }
893
1335
 
894
1336
  #onContainerAdornerClick(event: Event): void {
@@ -1096,11 +1538,16 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1096
1538
  this.#expandedChildrenLimit = expandedChildrenLimit;
1097
1539
  }
1098
1540
 
1541
+ onTopLayerIndexChanged(): void {
1542
+ this.performUpdate();
1543
+ }
1544
+
1099
1545
  override onbind(): void {
1100
1546
  this.performUpdate();
1101
1547
  if (this.treeOutline && !this.isClosingTag()) {
1102
1548
  this.treeOutline.treeElementByNode.set(this.nodeInternal, this);
1103
- this.nodeInternal.addEventListener(SDK.DOMModel.DOMNodeEvents.TOP_LAYER_INDEX_CHANGED, this.performUpdate, this);
1549
+ this.nodeInternal.addEventListener(
1550
+ SDK.DOMModel.DOMNodeEvents.TOP_LAYER_INDEX_CHANGED, this.onTopLayerIndexChanged, this);
1104
1551
  this.nodeInternal.addEventListener(
1105
1552
  SDK.DOMModel.DOMNodeEvents.SCROLLABLE_FLAG_UPDATED, this.#onScrollableFlagUpdated, this);
1106
1553
  this.nodeInternal.addEventListener(
@@ -1127,6 +1574,14 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1127
1574
  // element is bound again.
1128
1575
  DEFAULT_VIEW(
1129
1576
  {
1577
+ node: null,
1578
+ isClosingTag: false,
1579
+ expanded: false,
1580
+ isExpandable: false,
1581
+ isXMLMimeType: false,
1582
+ updateRecord: null,
1583
+ onHighlightSearchResults: () => {},
1584
+ onExpand: () => {},
1130
1585
  containerAdornerActive: false,
1131
1586
  showAdAdorner: false,
1132
1587
  showContainerAdorner: false,
@@ -1148,7 +1603,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1148
1603
  showSlotAdorner: false,
1149
1604
  showStartingStyleAdorner: false,
1150
1605
  startingStyleAdornerActive: false,
1151
- nodeInfo: this.#nodeInfo,
1152
1606
  onStartingStyleAdornerClick: () => {},
1153
1607
  onSlotAdornerClick: () => {},
1154
1608
  topLayerIndex: -1,
@@ -1170,12 +1624,13 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1170
1624
  decorationsTooltip: '',
1171
1625
  indent: 0,
1172
1626
  },
1173
- this, this.listItemElement);
1627
+ {}, this.listItemElement);
1174
1628
 
1175
1629
  if (this.treeOutline && this.treeOutline.treeElementByNode.get(this.nodeInternal) === this) {
1176
1630
  this.treeOutline.treeElementByNode.delete(this.nodeInternal);
1177
1631
  }
1178
- this.nodeInternal.removeEventListener(SDK.DOMModel.DOMNodeEvents.TOP_LAYER_INDEX_CHANGED, this.performUpdate, this);
1632
+ this.nodeInternal.removeEventListener(
1633
+ SDK.DOMModel.DOMNodeEvents.TOP_LAYER_INDEX_CHANGED, this.onTopLayerIndexChanged, this);
1179
1634
  this.nodeInternal.removeEventListener(
1180
1635
  SDK.DOMModel.DOMNodeEvents.SCROLLABLE_FLAG_UPDATED, this.#onScrollableFlagUpdated, this);
1181
1636
  this.nodeInternal.removeEventListener(
@@ -1743,7 +2198,10 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1743
2198
  // Cannot just convert the textual html into an element without
1744
2199
  // a parent node. Use a temporary span container for the HTML.
1745
2200
  const container = document.createElement('span');
1746
- const attr = this.buildAttributeDOM(container, ' ', '', null);
2201
+
2202
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
2203
+ Lit.render(renderAttribute({name: ' ', value: ''}, null, false, this.nodeInternal), container);
2204
+ const attr = container.firstElementChild as HTMLElement;
1747
2205
  attr.style.marginLeft = '2px'; // overrides the .editing margin rule
1748
2206
  attr.style.marginRight = '2px'; // overrides the .editing margin rule
1749
2207
  attr.setAttribute('jslog', `${VisualLogging.value('new-attribute').track({change: true, resize: true})}`);
@@ -1789,7 +2247,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1789
2247
 
1790
2248
  // Make sure elementForSelection is not a child of attributeValueElement.
1791
2249
  elementForSelection =
1792
- attributeValueElement.isAncestor(elementForSelection) ? attributeValueElement : elementForSelection;
2250
+ attributeValueElement?.isAncestor(elementForSelection) ? attributeValueElement : elementForSelection;
1793
2251
 
1794
2252
  function removeZeroWidthSpaceRecursive(node: Node): void {
1795
2253
  if (node.nodeType === Node.TEXT_NODE) {
@@ -1825,7 +2283,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1825
2283
  return '';
1826
2284
  }
1827
2285
 
1828
- if (!Common.ParsedURL.ParsedURL.fromString(attributeValueElement.textContent || '')) {
2286
+ if (!Common.ParsedURL.ParsedURL.fromString(attributeValueElement?.textContent || '')) {
1829
2287
  config.setPostKeydownFinishHandler(postKeyDownFinishHandler);
1830
2288
  }
1831
2289
 
@@ -1962,7 +2420,9 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
1962
2420
  // Hide header items.
1963
2421
  let child: (ChildNode|null) = this.listItemElement.firstChild;
1964
2422
  while (child) {
1965
- (child as HTMLElement).style.display = 'none';
2423
+ if (child instanceof HTMLElement) {
2424
+ child.style.display = 'none';
2425
+ }
1966
2426
  child = child.nextSibling;
1967
2427
  }
1968
2428
  // Hide children item.
@@ -2054,7 +2514,9 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2054
2514
  // Unhide header items.
2055
2515
  let child: (ChildNode|null) = this.listItemElement.firstChild;
2056
2516
  while (child) {
2057
- (child as HTMLElement).style.removeProperty('display');
2517
+ if (child instanceof HTMLElement) {
2518
+ child.style.removeProperty('display');
2519
+ }
2058
2520
  child = child.nextSibling;
2059
2521
  }
2060
2522
 
@@ -2242,8 +2704,8 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2242
2704
  if (this.editing) {
2243
2705
  return;
2244
2706
  }
2245
-
2246
- this.#nodeInfo = this.nodeTitleInfo(updateRecord || null);
2707
+ this.performUpdate(/* clearNode= */ true);
2708
+ this.#updateRecord = updateRecord ?? null;
2247
2709
  if (this.nodeInternal.nodeType() === Node.DOCUMENT_FRAGMENT_NODE && this.nodeInternal.isInShadowTree() &&
2248
2710
  this.nodeInternal.shadowRootType()) {
2249
2711
  this.childrenListElement.classList.add('shadow-root');
@@ -2261,8 +2723,10 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2261
2723
  }
2262
2724
  }
2263
2725
  this.performUpdate();
2264
- // fixme: make it clear that `this.title = x` is a setter with significant side effects
2265
- this.title = this.contentElement;
2726
+ if (this.#contentElement) {
2727
+ // fixme: we probably do not need a title element in the new tree outline.
2728
+ this.title = this.#contentElement;
2729
+ }
2266
2730
  this.updateDecorations();
2267
2731
 
2268
2732
  // If there is an issue with this node, make sure to update it.
@@ -2367,409 +2831,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
2367
2831
  }
2368
2832
  }
2369
2833
 
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
2834
  async remove(): Promise<void> {
2774
2835
  if (this.treeOutline?.isToggledToHidden(this.nodeInternal)) {
2775
2836
  // Unhide the node before removing. This avoids inconsistent state if the node is restored via undo.