ElementsTreeElement.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. export default class ElementsTreeElement extends UI.TreeElement{constructor(node,elementCloseTag){super();this._node=node;this._gutterContainer=this.listItemElement.createChild('div','gutter-container');this._gutterContainer.addEventListener('click',this._showContextMenu.bind(this));const gutterMenuIcon=UI.Icon.create('largeicon-menu','gutter-menu-icon');this._gutterContainer.appendChild(gutterMenuIcon);this._decorationsElement=this._gutterContainer.createChild('div','hidden');this._elementCloseTag=elementCloseTag;if(this._node.nodeType()===Node.ELEMENT_NODE&&!elementCloseTag){this._canAddAttributes=true;}
  2. this._searchQuery=null;this._expandedChildrenLimit=InitialChildrenLimit;this._decorationsThrottler=new Common.Throttler(100);this._htmlEditElement;}
  3. static animateOnDOMUpdate(treeElement){const tagName=treeElement.listItemElement.querySelector('.webkit-html-tag-name');UI.runCSSAnimationOnce(tagName||treeElement.listItemElement,'dom-update-highlight');}
  4. static visibleShadowRoots(node){let roots=node.shadowRoots();if(roots.length&&!Common.moduleSetting('showUAShadowDOM').get()){roots=roots.filter(filter);}
  5. function filter(root){return root.shadowRootType()!==SDK.DOMNode.ShadowRootTypes.UserAgent;}
  6. return roots;}
  7. static canShowInlineText(node){if(node.contentDocument()||node.importedDocument()||node.templateContent()||ElementsTreeElement.visibleShadowRoots(node).length||node.hasPseudoElements()){return false;}
  8. if(node.nodeType()!==Node.ELEMENT_NODE){return false;}
  9. if(!node.firstChild||node.firstChild!==node.lastChild||node.firstChild.nodeType()!==Node.TEXT_NODE){return false;}
  10. const textChild=node.firstChild;const maxInlineTextChildLength=80;if(textChild.nodeValue().length<maxInlineTextChildLength){return true;}
  11. return false;}
  12. static populateForcedPseudoStateItems(contextMenu,node){const pseudoClasses=['active','hover','focus','visited','focus-within'];try{document.querySelector(':focus-visible');pseudoClasses.push('focus-visible');}catch(e){}
  13. const forcedPseudoState=node.domModel().cssModel().pseudoState(node);const stateMenu=contextMenu.debugSection().appendSubMenuItem(Common.UIString('Force state'));for(let i=0;i<pseudoClasses.length;++i){const pseudoClassForced=forcedPseudoState.indexOf(pseudoClasses[i])>=0;stateMenu.defaultSection().appendCheckboxItem(':'+pseudoClasses[i],setPseudoStateCallback.bind(null,pseudoClasses[i],!pseudoClassForced),pseudoClassForced,false);}
  14. function setPseudoStateCallback(pseudoState,enabled){node.domModel().cssModel().forcePseudoState(node,pseudoState,enabled);}}
  15. isClosingTag(){return!!this._elementCloseTag;}
  16. node(){return this._node;}
  17. isEditing(){return!!this._editing;}
  18. highlightSearchResults(searchQuery){if(this._searchQuery!==searchQuery){this._hideSearchHighlight();}
  19. this._searchQuery=searchQuery;this._searchHighlightsVisible=true;this.updateTitle(null,true);}
  20. hideSearchHighlights(){delete this._searchHighlightsVisible;this._hideSearchHighlight();}
  21. _hideSearchHighlight(){if(!this._highlightResult){return;}
  22. function updateEntryHide(entry){switch(entry.type){case'added':entry.node.remove();break;case'changed':entry.node.textContent=entry.oldText;break;}}
  23. for(let i=(this._highlightResult.length-1);i>=0;--i){updateEntryHide(this._highlightResult[i]);}
  24. delete this._highlightResult;}
  25. setInClipboard(inClipboard){if(this._inClipboard===inClipboard){return;}
  26. this._inClipboard=inClipboard;this.listItemElement.classList.toggle('in-clipboard',inClipboard);}
  27. get hovered(){return this._hovered;}
  28. set hovered(x){if(this._hovered===x){return;}
  29. this._hovered=x;if(this.listItemElement){if(x){this._createSelection();this.listItemElement.classList.add('hovered');}else{this.listItemElement.classList.remove('hovered');}}}
  30. expandedChildrenLimit(){return this._expandedChildrenLimit;}
  31. setExpandedChildrenLimit(expandedChildrenLimit){this._expandedChildrenLimit=expandedChildrenLimit;}
  32. _createSelection(){const listItemElement=this.listItemElement;if(!listItemElement){return;}
  33. if(!this.selectionElement){this.selectionElement=createElement('div');this.selectionElement.className='selection fill';this.selectionElement.style.setProperty('margin-left',(-this._computeLeftIndent())+'px');listItemElement.insertBefore(this.selectionElement,listItemElement.firstChild);}}
  34. _createHint(){if(this.listItemElement&&!this._hintElement){this._hintElement=this.listItemElement.createChild('span','selected-hint');const selectedElementCommand='$0';this._hintElement.title=ls`Use ${selectedElementCommand} in the console to refer to this element.`;UI.ARIAUtils.markAsHidden(this._hintElement);}}
  35. onbind(){if(!this._elementCloseTag){this._node[this.treeOutline.treeElementSymbol()]=this;}}
  36. onunbind(){if(this._node[this.treeOutline.treeElementSymbol()]===this){this._node[this.treeOutline.treeElementSymbol()]=null;}}
  37. onattach(){if(this._hovered){this._createSelection();this.listItemElement.classList.add('hovered');}
  38. this.updateTitle();this.listItemElement.draggable=true;}
  39. async onpopulate(){return this.treeOutline.populateTreeElement(this);}
  40. async expandRecursively(){await this._node.getSubtree(-1,true);await super.expandRecursively(Number.MAX_VALUE);}
  41. onexpand(){if(this._elementCloseTag){return;}
  42. this.updateTitle();}
  43. oncollapse(){if(this._elementCloseTag){return;}
  44. this.updateTitle();}
  45. select(omitFocus,selectedByUser){if(this._editing){return false;}
  46. return super.select(omitFocus,selectedByUser);}
  47. onselect(selectedByUser){this.treeOutline.suppressRevealAndSelect=true;this.treeOutline.selectDOMNode(this._node,selectedByUser);if(selectedByUser){this._node.highlight();Host.userMetrics.actionTaken(Host.UserMetrics.Action.ChangeInspectedNodeInElementsPanel);}
  48. this._createSelection();this._createHint();this.treeOutline.suppressRevealAndSelect=false;return true;}
  49. ondelete(){const startTagTreeElement=this.treeOutline.findTreeElement(this._node);startTagTreeElement?startTagTreeElement.remove():this.remove();return true;}
  50. onenter(){if(this._editing){return false;}
  51. this._startEditing();return true;}
  52. selectOnMouseDown(event){super.selectOnMouseDown(event);if(this._editing){return;}
  53. if(event.detail>=2){event.preventDefault();}}
  54. ondblclick(event){if(this._editing||this._elementCloseTag){return false;}
  55. if(this._startEditingTarget((event.target))){return false;}
  56. if(this.isExpandable()&&!this.expanded){this.expand();}
  57. return false;}
  58. hasEditableNode(){return!this._node.isShadowRoot()&&!this._node.ancestorUserAgentShadowRoot();}
  59. _insertInLastAttributePosition(tag,node){if(tag.getElementsByClassName('webkit-html-attribute').length>0){tag.insertBefore(node,tag.lastChild);}else{const nodeName=tag.textContent.match(/^<(.*?)>$/)[1];tag.textContent='';tag.createTextChild('<'+nodeName);tag.appendChild(node);tag.createTextChild('>');}}
  60. _startEditingTarget(eventTarget){if(this.treeOutline.selectedDOMNode()!==this._node){return false;}
  61. if(this._node.nodeType()!==Node.ELEMENT_NODE&&this._node.nodeType()!==Node.TEXT_NODE){return false;}
  62. const textNode=eventTarget.enclosingNodeOrSelfWithClass('webkit-html-text-node');if(textNode){return this._startEditingTextNode(textNode);}
  63. const attribute=eventTarget.enclosingNodeOrSelfWithClass('webkit-html-attribute');if(attribute){return this._startEditingAttribute(attribute,eventTarget);}
  64. const tagName=eventTarget.enclosingNodeOrSelfWithClass('webkit-html-tag-name');if(tagName){return this._startEditingTagName(tagName);}
  65. const newAttribute=eventTarget.enclosingNodeOrSelfWithClass('add-attribute');if(newAttribute){return this._addNewAttribute();}
  66. return false;}
  67. _showContextMenu(event){this.treeOutline.showContextMenu(this,event);}
  68. populateTagContextMenu(contextMenu,event){const treeElement=this._elementCloseTag?this.treeOutline.findTreeElement(this._node):this;contextMenu.editSection().appendItem(Common.UIString('Add attribute'),treeElement._addNewAttribute.bind(treeElement));const attribute=event.target.enclosingNodeOrSelfWithClass('webkit-html-attribute');const newAttribute=event.target.enclosingNodeOrSelfWithClass('add-attribute');if(attribute&&!newAttribute){contextMenu.editSection().appendItem(Common.UIString('Edit attribute'),this._startEditingAttribute.bind(this,attribute,event.target));}
  69. this.populateNodeContextMenu(contextMenu);ElementsTreeElement.populateForcedPseudoStateItems(contextMenu,treeElement.node());this.populateScrollIntoView(contextMenu);contextMenu.viewSection().appendItem(Common.UIString('Focus'),async()=>{await this._node.focus();});}
  70. populateScrollIntoView(contextMenu){contextMenu.viewSection().appendItem(Common.UIString('Scroll into view'),()=>this._node.scrollIntoView());}
  71. populateTextContextMenu(contextMenu,textNode){if(!this._editing){contextMenu.editSection().appendItem(Common.UIString('Edit text'),this._startEditingTextNode.bind(this,textNode));}
  72. this.populateNodeContextMenu(contextMenu);}
  73. populateNodeContextMenu(contextMenu){const isEditable=this.hasEditableNode();if(isEditable&&!this._editing){contextMenu.editSection().appendItem(Common.UIString('Edit as HTML'),this._editAsHTML.bind(this));}
  74. const isShadowRoot=this._node.isShadowRoot();const copyMenu=contextMenu.clipboardSection().appendSubMenuItem(Common.UIString('Copy'));const createShortcut=UI.KeyboardShortcut.shortcutToString.bind(null);const modifier=UI.KeyboardShortcut.Modifiers.CtrlOrMeta;const treeOutline=this.treeOutline;let menuItem;let section;if(!isShadowRoot){section=copyMenu.section();menuItem=section.appendItem(Common.UIString('Copy outerHTML'),treeOutline.performCopyOrCut.bind(treeOutline,false,this._node));menuItem.setShortcut(createShortcut('V',modifier));}
  75. if(this._node.nodeType()===Node.ELEMENT_NODE){section.appendItem(Common.UIString('Copy selector'),this._copyCSSPath.bind(this));section.appendItem(Common.UIString('Copy JS path'),this._copyJSPath.bind(this),!Elements.DOMPath.canGetJSPath(this._node));section.appendItem(ls`Copy styles`,this._copyStyles.bind(this));}
  76. if(!isShadowRoot){section.appendItem(Common.UIString('Copy XPath'),this._copyXPath.bind(this));section.appendItem(ls`Copy full XPath`,this._copyFullXPath.bind(this));}
  77. if(!isShadowRoot){menuItem=copyMenu.clipboardSection().appendItem(Common.UIString('Cut element'),treeOutline.performCopyOrCut.bind(treeOutline,true,this._node),!this.hasEditableNode());menuItem.setShortcut(createShortcut('X',modifier));menuItem=copyMenu.clipboardSection().appendItem(Common.UIString('Copy element'),treeOutline.performCopyOrCut.bind(treeOutline,false,this._node));menuItem.setShortcut(createShortcut('C',modifier));menuItem=copyMenu.clipboardSection().appendItem(Common.UIString('Paste element'),treeOutline.pasteNode.bind(treeOutline,this._node),!treeOutline.canPaste(this._node));menuItem.setShortcut(createShortcut('V',modifier));}
  78. menuItem=contextMenu.debugSection().appendCheckboxItem(Common.UIString('Hide element'),treeOutline.toggleHideElement.bind(treeOutline,this._node),treeOutline.isToggledToHidden(this._node));menuItem.setShortcut(UI.shortcutRegistry.shortcutTitleForAction('elements.hide-element'));if(isEditable){contextMenu.editSection().appendItem(Common.UIString('Delete element'),this.remove.bind(this));}
  79. contextMenu.viewSection().appendItem(ls`Expand recursively`,this.expandRecursively.bind(this));contextMenu.viewSection().appendItem(ls`Collapse children`,this.collapseChildren.bind(this));}
  80. _startEditing(){if(this.treeOutline.selectedDOMNode()!==this._node){return;}
  81. const listItem=this.listItemElement;if(this._canAddAttributes){const attribute=listItem.getElementsByClassName('webkit-html-attribute')[0];if(attribute){return this._startEditingAttribute(attribute,attribute.getElementsByClassName('webkit-html-attribute-value')[0]);}
  82. return this._addNewAttribute();}
  83. if(this._node.nodeType()===Node.TEXT_NODE){const textNode=listItem.getElementsByClassName('webkit-html-text-node')[0];if(textNode){return this._startEditingTextNode(textNode);}
  84. return;}}
  85. _addNewAttribute(){const container=createElement('span');this._buildAttributeDOM(container,' ','',null);const attr=container.firstElementChild;attr.style.marginLeft='2px';attr.style.marginRight='2px';const tag=this.listItemElement.getElementsByClassName('webkit-html-tag')[0];this._insertInLastAttributePosition(tag,attr);attr.scrollIntoViewIfNeeded(true);return this._startEditingAttribute(attr,attr);}
  86. _triggerEditAttribute(attributeName){const attributeElements=this.listItemElement.getElementsByClassName('webkit-html-attribute-name');for(let i=0,len=attributeElements.length;i<len;++i){if(attributeElements[i].textContent===attributeName){for(let elem=attributeElements[i].nextSibling;elem;elem=elem.nextSibling){if(elem.nodeType!==Node.ELEMENT_NODE){continue;}
  87. if(elem.classList.contains('webkit-html-attribute-value')){return this._startEditingAttribute(elem.parentNode,elem);}}}}}
  88. _startEditingAttribute(attribute,elementForSelection){console.assert(this.listItemElement.isAncestor(attribute));if(UI.isBeingEdited(attribute)){return true;}
  89. const attributeNameElement=attribute.getElementsByClassName('webkit-html-attribute-name')[0];if(!attributeNameElement){return false;}
  90. const attributeName=attributeNameElement.textContent;const attributeValueElement=attribute.getElementsByClassName('webkit-html-attribute-value')[0];elementForSelection=attributeValueElement.isAncestor(elementForSelection)?attributeValueElement:elementForSelection;function removeZeroWidthSpaceRecursive(node){if(node.nodeType===Node.TEXT_NODE){node.nodeValue=node.nodeValue.replace(/\u200B/g,'');return;}
  91. if(node.nodeType!==Node.ELEMENT_NODE){return;}
  92. for(let child=node.firstChild;child;child=child.nextSibling){removeZeroWidthSpaceRecursive(child);}}
  93. const attributeValue=attributeName&&attributeValueElement?this._node.getAttribute(attributeName):undefined;if(attributeValue!==undefined){attributeValueElement.setTextContentTruncatedIfNeeded(attributeValue,Common.UIString('<value is too large to edit>'));}
  94. removeZeroWidthSpaceRecursive(attribute);const config=new UI.InplaceEditor.Config(this._attributeEditingCommitted.bind(this),this._editingCancelled.bind(this),attributeName);function postKeyDownFinishHandler(event){UI.handleElementValueModifications(event,attribute);return'';}
  95. if(!attributeValueElement.textContent.asParsedURL()){config.setPostKeydownFinishHandler(postKeyDownFinishHandler);}
  96. this._editing=UI.InplaceEditor.startEditing(attribute,config);this.listItemElement.getComponentSelection().selectAllChildren(elementForSelection);return true;}
  97. _startEditingTextNode(textNodeElement){if(UI.isBeingEdited(textNodeElement)){return true;}
  98. let textNode=this._node;if(textNode.nodeType()===Node.ELEMENT_NODE&&textNode.firstChild){textNode=textNode.firstChild;}
  99. const container=textNodeElement.enclosingNodeOrSelfWithClass('webkit-html-text-node');if(container){container.textContent=textNode.nodeValue();}
  100. const config=new UI.InplaceEditor.Config(this._textNodeEditingCommitted.bind(this,textNode),this._editingCancelled.bind(this));this._editing=UI.InplaceEditor.startEditing(textNodeElement,config);this.listItemElement.getComponentSelection().selectAllChildren(textNodeElement);return true;}
  101. _startEditingTagName(tagNameElement){if(!tagNameElement){tagNameElement=this.listItemElement.getElementsByClassName('webkit-html-tag-name')[0];if(!tagNameElement){return false;}}
  102. const tagName=tagNameElement.textContent;if(EditTagBlacklist.has(tagName.toLowerCase())){return false;}
  103. if(UI.isBeingEdited(tagNameElement)){return true;}
  104. const closingTagElement=this._distinctClosingTagElement();function keyupListener(event){if(closingTagElement){closingTagElement.textContent='</'+tagNameElement.textContent+'>';}}
  105. const keydownListener=event=>{if(event.key!==' '){return;}
  106. this._editing.commit();event.consume(true);};function editingComitted(element,newTagName){tagNameElement.removeEventListener('keyup',keyupListener,false);tagNameElement.removeEventListener('keydown',keydownListener,false);this._tagNameEditingCommitted.apply(this,arguments);}
  107. function editingCancelled(){tagNameElement.removeEventListener('keyup',keyupListener,false);tagNameElement.removeEventListener('keydown',keydownListener,false);this._editingCancelled.apply(this,arguments);}
  108. tagNameElement.addEventListener('keyup',keyupListener,false);tagNameElement.addEventListener('keydown',keydownListener,false);const config=new UI.InplaceEditor.Config(editingComitted.bind(this),editingCancelled.bind(this),tagName);this._editing=UI.InplaceEditor.startEditing(tagNameElement,config);this.listItemElement.getComponentSelection().selectAllChildren(tagNameElement);return true;}
  109. _startEditingAsHTML(commitCallback,disposeCallback,maybeInitialValue){if(maybeInitialValue===null){return;}
  110. let initialValue=maybeInitialValue;if(this._editing){return;}
  111. initialValue=this._convertWhitespaceToEntities(initialValue).text;this._htmlEditElement=createElement('div');this._htmlEditElement.className='source-code elements-tree-editor';let child=this.listItemElement.firstChild;while(child){child.style.display='none';child=child.nextSibling;}
  112. if(this.childrenListElement){this.childrenListElement.style.display='none';}
  113. this.listItemElement.appendChild(this._htmlEditElement);self.runtime.extension(UI.TextEditorFactory).instance().then(gotFactory.bind(this));function gotFactory(factory){const editor=factory.createEditor({lineNumbers:false,lineWrapping:Common.moduleSetting('domWordWrap').get(),mimeType:'text/html',autoHeight:false,padBottom:false});this._editing={commit:commit.bind(this),cancel:dispose.bind(this),editor:editor,resize:resize.bind(this)};resize.call(this);editor.widget().show((this._htmlEditElement));editor.setText(initialValue);editor.widget().focus();editor.widget().element.addEventListener('focusout',event=>{if(event.relatedTarget&&!event.relatedTarget.isSelfOrDescendant(editor.widget().element)){this._editing.commit();}},false);editor.widget().element.addEventListener('keydown',keydown.bind(this),true);this.treeOutline.setMultilineEditing(this._editing);}
  114. function resize(){if(this._htmlEditElement){this._htmlEditElement.style.width=this.treeOutline.visibleWidth()-this._computeLeftIndent()-30+'px';}
  115. this._editing.editor.onResize();}
  116. function commit(){commitCallback(initialValue,this._editing.editor.text());dispose.call(this);}
  117. function dispose(){this._editing.editor.widget().element.removeEventListener('blur',this._editing.commit,true);this._editing.editor.widget().detach();delete this._editing;this.listItemElement.removeChild(this._htmlEditElement);delete this._htmlEditElement;if(this.childrenListElement){this.childrenListElement.style.removeProperty('display');}
  118. let child=this.listItemElement.firstChild;while(child){child.style.removeProperty('display');child=child.nextSibling;}
  119. if(this.treeOutline){this.treeOutline.setMultilineEditing(null);this.treeOutline.focus();}
  120. disposeCallback();}
  121. function keydown(event){const isMetaOrCtrl=UI.KeyboardShortcut.eventHasCtrlOrMeta((event))&&!event.altKey&&!event.shiftKey;if(isEnterKey(event)&&(isMetaOrCtrl||event.isMetaOrCtrlForTest)){event.consume(true);this._editing.commit();}else if(event.keyCode===UI.KeyboardShortcut.Keys.Esc.code||event.key==='Escape'){event.consume(true);this._editing.cancel();}}}
  122. _attributeEditingCommitted(element,newText,oldText,attributeName,moveDirection){delete this._editing;const treeOutline=this.treeOutline;function moveToNextAttributeIfNeeded(error){if(error){this._editingCancelled(element,attributeName);}
  123. if(!moveDirection){return;}
  124. treeOutline.runPendingUpdates();treeOutline.focus();const attributes=this._node.attributes();for(let i=0;i<attributes.length;++i){if(attributes[i].name!==attributeName){continue;}
  125. if(moveDirection==='backward'){if(i===0){this._startEditingTagName();}else{this._triggerEditAttribute(attributes[i-1].name);}}else{if(i===attributes.length-1){this._addNewAttribute();}else{this._triggerEditAttribute(attributes[i+1].name);}}
  126. return;}
  127. if(moveDirection==='backward'){if(newText===' '){if(attributes.length>0){this._triggerEditAttribute(attributes[attributes.length-1].name);}}else{if(attributes.length>1){this._triggerEditAttribute(attributes[attributes.length-2].name);}}}else if(moveDirection==='forward'){if(!newText.isWhitespace()){this._addNewAttribute();}else{this._startEditingTagName();}}}
  128. if((attributeName.trim()||newText.trim())&&oldText!==newText){this._node.setAttribute(attributeName,newText,moveToNextAttributeIfNeeded.bind(this));return;}
  129. this.updateTitle();moveToNextAttributeIfNeeded.call(this);}
  130. _tagNameEditingCommitted(element,newText,oldText,tagName,moveDirection){delete this._editing;const self=this;function cancel(){const closingTagElement=self._distinctClosingTagElement();if(closingTagElement){closingTagElement.textContent='</'+tagName+'>';}
  131. self._editingCancelled(element,tagName);moveToNextAttributeIfNeeded.call(self);}
  132. function moveToNextAttributeIfNeeded(){if(moveDirection!=='forward'){this._addNewAttribute();return;}
  133. const attributes=this._node.attributes();if(attributes.length>0){this._triggerEditAttribute(attributes[0].name);}else{this._addNewAttribute();}}
  134. newText=newText.trim();if(newText===oldText){cancel();return;}
  135. const treeOutline=this.treeOutline;const wasExpanded=this.expanded;this._node.setNodeName(newText,(error,newNode)=>{if(error||!newNode){cancel();return;}
  136. const newTreeItem=treeOutline.selectNodeAfterEdit(wasExpanded,error,newNode);moveToNextAttributeIfNeeded.call(newTreeItem);});}
  137. _textNodeEditingCommitted(textNode,element,newText){delete this._editing;function callback(){this.updateTitle();}
  138. textNode.setNodeValue(newText,callback.bind(this));}
  139. _editingCancelled(element,context){delete this._editing;this.updateTitle();}
  140. _distinctClosingTagElement(){if(this.expanded){const closers=this.childrenListElement.querySelectorAll('.close');return closers[closers.length-1];}
  141. const tags=this.listItemElement.getElementsByClassName('webkit-html-tag');return(tags.length===1?null:tags[tags.length-1]);}
  142. updateTitle(updateRecord,onlySearchQueryChanged){if(this._editing){return;}
  143. if(onlySearchQueryChanged){this._hideSearchHighlight();}else{const nodeInfo=this._nodeTitleInfo(updateRecord||null);if(this._node.nodeType()===Node.DOCUMENT_FRAGMENT_NODE&&this._node.isInShadowTree()&&this._node.shadowRootType()){this.childrenListElement.classList.add('shadow-root');let depth=4;for(let node=this._node;depth&&node;node=node.parentNode){if(node.nodeType()===Node.DOCUMENT_FRAGMENT_NODE){depth--;}}
  144. if(!depth){this.childrenListElement.classList.add('shadow-root-deep');}else{this.childrenListElement.classList.add('shadow-root-depth-'+depth);}}
  145. const highlightElement=createElement('span');highlightElement.className='highlight';highlightElement.appendChild(nodeInfo);this.title=highlightElement;this.updateDecorations();this.listItemElement.insertBefore(this._gutterContainer,this.listItemElement.firstChild);delete this._highlightResult;delete this.selectionElement;delete this._hintElement;if(this.selected){this._createSelection();this._createHint();}}
  146. this._highlightSearchResults();}
  147. _computeLeftIndent(){let treeElement=this.parent;let depth=0;while(treeElement!==null){depth++;treeElement=treeElement.parent;}
  148. return 12*(depth-2)+(this.isExpandable()?1:12);}
  149. updateDecorations(){this._gutterContainer.style.left=(-this._computeLeftIndent())+'px';if(this.isClosingTag()){return;}
  150. if(this._node.nodeType()!==Node.ELEMENT_NODE){return;}
  151. this._decorationsThrottler.schedule(this._updateDecorationsInternal.bind(this));}
  152. _updateDecorationsInternal(){if(!this.treeOutline){return Promise.resolve();}
  153. const node=this._node;if(!this.treeOutline._decoratorExtensions){this.treeOutline._decoratorExtensions=self.runtime.extensions(Elements.MarkerDecorator);}
  154. const markerToExtension=new Map();for(let i=0;i<this.treeOutline._decoratorExtensions.length;++i){markerToExtension.set(this.treeOutline._decoratorExtensions[i].descriptor()['marker'],this.treeOutline._decoratorExtensions[i]);}
  155. const promises=[];const decorations=[];const descendantDecorations=[];node.traverseMarkers(visitor);function visitor(n,marker){const extension=markerToExtension.get(marker);if(!extension){return;}
  156. promises.push(extension.instance().then(collectDecoration.bind(null,n)));}
  157. function collectDecoration(n,decorator){const decoration=decorator.decorate(n);if(!decoration){return;}
  158. (n===node?decorations:descendantDecorations).push(decoration);}
  159. return Promise.all(promises).then(updateDecorationsUI.bind(this));function updateDecorationsUI(){this._decorationsElement.removeChildren();this._decorationsElement.classList.add('hidden');this._gutterContainer.classList.toggle('has-decorations',decorations.length||descendantDecorations.length);if(!decorations.length&&!descendantDecorations.length){return;}
  160. const colors=new Set();const titles=createElement('div');for(const decoration of decorations){const titleElement=titles.createChild('div');titleElement.textContent=decoration.title;colors.add(decoration.color);}
  161. if(this.expanded&&!decorations.length){return;}
  162. const descendantColors=new Set();if(descendantDecorations.length){let element=titles.createChild('div');element.textContent=Common.UIString('Children:');for(const decoration of descendantDecorations){element=titles.createChild('div');element.style.marginLeft='15px';element.textContent=decoration.title;descendantColors.add(decoration.color);}}
  163. let offset=0;processColors.call(this,colors,'elements-gutter-decoration');if(!this.expanded){processColors.call(this,descendantColors,'elements-gutter-decoration elements-has-decorated-children');}
  164. UI.Tooltip.install(this._decorationsElement,titles);function processColors(colors,className){for(const color of colors){const child=this._decorationsElement.createChild('div',className);this._decorationsElement.classList.remove('hidden');child.style.backgroundColor=color;child.style.borderColor=color;if(offset){child.style.marginLeft=offset+'px';}
  165. offset+=3;}}}}
  166. _buildAttributeDOM(parentElement,name,value,updateRecord,forceValue,node){const closingPunctuationRegex=/[\/;:\)\]\}]/g;let highlightIndex=0;let highlightCount;let additionalHighlightOffset=0;let result;function replacer(match,replaceOffset){while(highlightIndex<highlightCount&&result.entityRanges[highlightIndex].offset<replaceOffset){result.entityRanges[highlightIndex].offset+=additionalHighlightOffset;++highlightIndex;}
  167. additionalHighlightOffset+=1;return match+'\u200B';}
  168. function setValueWithEntities(element,value){result=this._convertWhitespaceToEntities(value);highlightCount=result.entityRanges.length;value=result.text.replace(closingPunctuationRegex,replacer);while(highlightIndex<highlightCount){result.entityRanges[highlightIndex].offset+=additionalHighlightOffset;++highlightIndex;}
  169. element.setTextContentTruncatedIfNeeded(value);UI.highlightRangesWithStyleClass(element,result.entityRanges,'webkit-html-entity-value');}
  170. const hasText=(forceValue||value.length>0);const attrSpanElement=parentElement.createChild('span','webkit-html-attribute');const attrNameElement=attrSpanElement.createChild('span','webkit-html-attribute-name');attrNameElement.textContent=name;if(hasText){attrSpanElement.createTextChild('=\u200B"');}
  171. const attrValueElement=attrSpanElement.createChild('span','webkit-html-attribute-value');if(updateRecord&&updateRecord.isAttributeModified(name)){UI.runCSSAnimationOnce(hasText?attrValueElement:attrNameElement,'dom-update-highlight');}
  172. function linkifyValue(value){const rewrittenHref=node.resolveURL(value);if(rewrittenHref===null){const span=createElement('span');setValueWithEntities.call(this,span,value);return span;}
  173. value=value.replace(closingPunctuationRegex,'$&\u200B');if(value.startsWith('data:')){value=value.trimMiddle(60);}
  174. const link=node.nodeName().toLowerCase()==='a'?UI.XLink.create(rewrittenHref,value,'',true):Components.Linkifier.linkifyURL(rewrittenHref,{text:value,preventClick:true});link[HrefSymbol]=rewrittenHref;return link;}
  175. const nodeName=node?node.nodeName().toLowerCase():'';if(nodeName&&(name==='src'||name==='href')){attrValueElement.appendChild(linkifyValue.call(this,value));}else if((nodeName==='img'||nodeName==='source')&&name==='srcset'){attrValueElement.appendChild(linkifySrcset.call(this,value));}else if(nodeName==='image'&&(name==='xlink:href'||name==='href')){attrValueElement.appendChild(linkifySrcset.call(this,value));}else{setValueWithEntities.call(this,attrValueElement,value);}
  176. if(hasText){attrSpanElement.createTextChild('"');}
  177. function linkifySrcset(value){const fragment=createDocumentFragment();let i=0;while(value.length){if(i++>0){fragment.createTextChild(' ');}
  178. value=value.trim();let url='';let descriptor='';const indexOfSpace=value.search(/\s/);if(indexOfSpace===-1){url=value;}else if(indexOfSpace>0&&value[indexOfSpace-1]===','){url=value.substring(0,indexOfSpace);}else{url=value.substring(0,indexOfSpace);const indexOfComma=value.indexOf(',',indexOfSpace);if(indexOfComma!==-1){descriptor=value.substring(indexOfSpace,indexOfComma+1);}else{descriptor=value.substring(indexOfSpace);}}
  179. if(url){if(url.endsWith(',')){fragment.appendChild(linkifyValue.call(this,url.substring(0,url.length-1)));fragment.createTextChild(',');}else{fragment.appendChild(linkifyValue.call(this,url));}}
  180. if(descriptor){fragment.createTextChild(descriptor);}
  181. value=value.substring(url.length+descriptor.length);}
  182. return fragment;}}
  183. _buildPseudoElementDOM(parentElement,pseudoElementName){const pseudoElement=parentElement.createChild('span','webkit-html-pseudo-element');pseudoElement.textContent='::'+pseudoElementName;parentElement.createTextChild('\u200B');}
  184. _buildTagDOM(parentElement,tagName,isClosingTag,isDistinctTreeElement,updateRecord){const node=this._node;const classes=['webkit-html-tag'];if(isClosingTag&&isDistinctTreeElement){classes.push('close');}
  185. const tagElement=parentElement.createChild('span',classes.join(' '));tagElement.createTextChild('<');const tagNameElement=tagElement.createChild('span',isClosingTag?'webkit-html-close-tag-name':'webkit-html-tag-name');tagNameElement.textContent=(isClosingTag?'/':'')+tagName;if(!isClosingTag){if(node.hasAttributes()){const attributes=node.attributes();for(let i=0;i<attributes.length;++i){const attr=attributes[i];tagElement.createTextChild(' ');this._buildAttributeDOM(tagElement,attr.name,attr.value,updateRecord,false,node);}}
  186. if(updateRecord){let hasUpdates=updateRecord.hasRemovedAttributes()||updateRecord.hasRemovedChildren();hasUpdates|=!this.expanded&&updateRecord.hasChangedChildren();if(hasUpdates){UI.runCSSAnimationOnce(tagNameElement,'dom-update-highlight');}}}
  187. tagElement.createTextChild('>');parentElement.createTextChild('\u200B');}
  188. _convertWhitespaceToEntities(text){let result='';let lastIndexAfterEntity=0;const entityRanges=[];const charToEntity=Elements.ElementsTreeOutline.MappedCharToEntity;for(let i=0,size=text.length;i<size;++i){const char=text.charAt(i);if(charToEntity[char]){result+=text.substring(lastIndexAfterEntity,i);const entityValue='&'+charToEntity[char]+';';entityRanges.push({offset:result.length,length:entityValue.length});result+=entityValue;lastIndexAfterEntity=i+1;}}
  189. if(result){result+=text.substring(lastIndexAfterEntity);}
  190. return{text:result||text,entityRanges:entityRanges};}
  191. _nodeTitleInfo(updateRecord){const node=this._node;const titleDOM=createDocumentFragment();switch(node.nodeType()){case Node.ATTRIBUTE_NODE:this._buildAttributeDOM(titleDOM,(node.name),(node.value),updateRecord,true);break;case Node.ELEMENT_NODE:const pseudoType=node.pseudoType();if(pseudoType){this._buildPseudoElementDOM(titleDOM,pseudoType);break;}
  192. const tagName=node.nodeNameInCorrectCase();if(this._elementCloseTag){this._buildTagDOM(titleDOM,tagName,true,true,updateRecord);break;}
  193. this._buildTagDOM(titleDOM,tagName,false,false,updateRecord);if(this.isExpandable()){if(!this.expanded){const textNodeElement=titleDOM.createChild('span','webkit-html-text-node bogus');textNodeElement.textContent='\u2026';titleDOM.createTextChild('\u200B');this._buildTagDOM(titleDOM,tagName,true,false,updateRecord);}
  194. break;}
  195. if(ElementsTreeElement.canShowInlineText(node)){const textNodeElement=titleDOM.createChild('span','webkit-html-text-node');const result=this._convertWhitespaceToEntities(node.firstChild.nodeValue());textNodeElement.textContent=result.text;UI.highlightRangesWithStyleClass(textNodeElement,result.entityRanges,'webkit-html-entity-value');titleDOM.createTextChild('\u200B');this._buildTagDOM(titleDOM,tagName,true,false,updateRecord);if(updateRecord&&updateRecord.hasChangedChildren()){UI.runCSSAnimationOnce(textNodeElement,'dom-update-highlight');}
  196. if(updateRecord&&updateRecord.isCharDataModified()){UI.runCSSAnimationOnce(textNodeElement,'dom-update-highlight');}
  197. break;}
  198. if(this.treeOutline.isXMLMimeType||!ForbiddenClosingTagElements.has(tagName)){this._buildTagDOM(titleDOM,tagName,true,false,updateRecord);}
  199. break;case Node.TEXT_NODE:if(node.parentNode&&node.parentNode.nodeName().toLowerCase()==='script'){const newNode=titleDOM.createChild('span','webkit-html-text-node webkit-html-js-node');const text=node.nodeValue();newNode.textContent=text.startsWith('\n')?text.substring(1):text;const javascriptSyntaxHighlighter=new UI.SyntaxHighlighter('text/javascript',true);javascriptSyntaxHighlighter.syntaxHighlightNode(newNode).then(updateSearchHighlight.bind(this));}else if(node.parentNode&&node.parentNode.nodeName().toLowerCase()==='style'){const newNode=titleDOM.createChild('span','webkit-html-text-node webkit-html-css-node');const text=node.nodeValue();newNode.textContent=text.startsWith('\n')?text.substring(1):text;const cssSyntaxHighlighter=new UI.SyntaxHighlighter('text/css',true);cssSyntaxHighlighter.syntaxHighlightNode(newNode).then(updateSearchHighlight.bind(this));}else{titleDOM.createTextChild('"');const textNodeElement=titleDOM.createChild('span','webkit-html-text-node');const result=this._convertWhitespaceToEntities(node.nodeValue());textNodeElement.textContent=result.text;UI.highlightRangesWithStyleClass(textNodeElement,result.entityRanges,'webkit-html-entity-value');titleDOM.createTextChild('"');if(updateRecord&&updateRecord.isCharDataModified()){UI.runCSSAnimationOnce(textNodeElement,'dom-update-highlight');}}
  200. break;case Node.COMMENT_NODE:const commentElement=titleDOM.createChild('span','webkit-html-comment');commentElement.createTextChild('<!--'+node.nodeValue()+'-->');break;case Node.DOCUMENT_TYPE_NODE:const docTypeElement=titleDOM.createChild('span','webkit-html-doctype');docTypeElement.createTextChild('<!doctype '+node.nodeName());if(node.publicId){docTypeElement.createTextChild(' PUBLIC "'+node.publicId+'"');if(node.systemId){docTypeElement.createTextChild(' "'+node.systemId+'"');}}else if(node.systemId){docTypeElement.createTextChild(' SYSTEM "'+node.systemId+'"');}
  201. if(node.internalSubset){docTypeElement.createTextChild(' ['+node.internalSubset+']');}
  202. docTypeElement.createTextChild('>');break;case Node.CDATA_SECTION_NODE:const cdataElement=titleDOM.createChild('span','webkit-html-text-node');cdataElement.createTextChild('<![CDATA['+node.nodeValue()+']]>');break;case Node.DOCUMENT_FRAGMENT_NODE:const fragmentElement=titleDOM.createChild('span','webkit-html-fragment');fragmentElement.textContent=node.nodeNameInCorrectCase().collapseWhitespace();break;default:titleDOM.createTextChild(node.nodeNameInCorrectCase().collapseWhitespace());}
  203. function updateSearchHighlight(){delete this._highlightResult;this._highlightSearchResults();}
  204. return titleDOM;}
  205. remove(){if(this._node.pseudoType()){return;}
  206. const parentElement=this.parent;if(!parentElement){return;}
  207. if(!this._node.parentNode||this._node.parentNode.nodeType()===Node.DOCUMENT_NODE){return;}
  208. this._node.removeNode();}
  209. toggleEditAsHTML(callback,startEditing){if(this._editing&&this._htmlEditElement){this._editing.commit();return;}
  210. if(startEditing===false){return;}
  211. function selectNode(error){if(callback){callback(!error);}}
  212. function commitChange(initialValue,value){if(initialValue!==value){node.setOuterHTML(value,selectNode);}}
  213. function disposeCallback(){if(callback){callback(false);}}
  214. const node=this._node;node.getOuterHTML().then(this._startEditingAsHTML.bind(this,commitChange,disposeCallback));}
  215. _copyCSSPath(){Host.InspectorFrontendHost.copyText(Elements.DOMPath.cssPath(this._node,true));}
  216. _copyJSPath(){Host.InspectorFrontendHost.copyText(Elements.DOMPath.jsPath(this._node,true));}
  217. _copyXPath(){Host.InspectorFrontendHost.copyText(Elements.DOMPath.xPath(this._node,true));}
  218. _copyFullXPath(){Host.InspectorFrontendHost.copyText(Elements.DOMPath.xPath(this._node,false));}
  219. async _copyStyles(){const node=this._node;const cssModel=node.domModel().cssModel();const cascade=await cssModel.cachedMatchedCascadeForNode(node);if(!cascade){return;}
  220. const lines=[];for(const style of cascade.nodeStyles().reverse()){for(const property of style.leadingProperties()){if(!property.parsedOk||property.disabled||!property.activeInStyle()||property.implicit){continue;}
  221. if(cascade.isInherited(style)&&!SDK.cssMetadata().isPropertyInherited(property.name)){continue;}
  222. if(style.parentRule&&style.parentRule.isUserAgent()){continue;}
  223. if(cascade.propertyState(property)!==SDK.CSSMatchedStyles.PropertyState.Active){continue;}
  224. lines.push(`${property.name}: ${property.value};`);}}
  225. Host.InspectorFrontendHost.copyText(lines.join('\n'));}
  226. _highlightSearchResults(){if(!this._searchQuery||!this._searchHighlightsVisible){return;}
  227. this._hideSearchHighlight();const text=this.listItemElement.textContent;const regexObject=createPlainTextSearchRegex(this._searchQuery,'gi');let match=regexObject.exec(text);const matchRanges=[];while(match){matchRanges.push(new TextUtils.SourceRange(match.index,match[0].length));match=regexObject.exec(text);}
  228. if(!matchRanges.length){matchRanges.push(new TextUtils.SourceRange(0,text.length));}
  229. this._highlightResult=[];UI.highlightSearchResults(this.listItemElement,matchRanges,this._highlightResult);}
  230. _editAsHTML(){const promise=Common.Revealer.reveal(this.node());promise.then(()=>UI.actionRegistry.action('elements.edit-as-html').execute());}}
  231. export const HrefSymbol=Symbol('ElementsTreeElement.Href');export const InitialChildrenLimit=500;export const ForbiddenClosingTagElements=new Set(['area','base','basefont','br','canvas','col','command','embed','frame','hr','img','input','keygen','link','menuitem','meta','param','source','track','wbr']);export const EditTagBlacklist=new Set(['html','head','body']);self.Elements=self.Elements||{};Elements=Elements||{};Elements.ElementsTreeElement=ElementsTreeElement;Elements.ElementsTreeElement.HrefSymbol=HrefSymbol;Elements.ElementsTreeElement.InitialChildrenLimit=InitialChildrenLimit;Elements.ElementsTreeElement.ForbiddenClosingTagElements=ForbiddenClosingTagElements;Elements.ElementsTreeElement.EditTagBlacklist=EditTagBlacklist;Elements.MultilineEditorController;