UIUtils.js 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. export const highlightedSearchResultClassName='highlighted-search-result';export const highlightedCurrentSearchResultClassName='current-search-result';export function installDragHandle(element,elementDragStart,elementDrag,elementDragEnd,cursor,hoverCursor,startDelay){function onMouseDown(event){const dragHandler=new DragHandler();const dragStart=dragHandler.elementDragStart.bind(dragHandler,element,elementDragStart,elementDrag,elementDragEnd,cursor,event);if(startDelay){startTimer=setTimeout(dragStart,startDelay);}else{dragStart();}}
  2. function onMouseUp(){if(startTimer){clearTimeout(startTimer);}
  3. startTimer=null;}
  4. let startTimer;element.addEventListener('mousedown',onMouseDown,false);if(startDelay){element.addEventListener('mouseup',onMouseUp,false);}
  5. if(hoverCursor!==null){element.style.cursor=hoverCursor||cursor||'';}}
  6. export function elementDragStart(targetElement,elementDragStart,elementDrag,elementDragEnd,cursor,event){const dragHandler=new DragHandler();dragHandler.elementDragStart(targetElement,elementDragStart,elementDrag,elementDragEnd,cursor,event);}
  7. class DragHandler{constructor(){this._elementDragMove=this._elementDragMove.bind(this);this._elementDragEnd=this._elementDragEnd.bind(this);this._mouseOutWhileDragging=this._mouseOutWhileDragging.bind(this);}
  8. _createGlassPane(){this._glassPaneInUse=true;if(!DragHandler._glassPaneUsageCount++){DragHandler._glassPane=new UI.GlassPane();DragHandler._glassPane.setPointerEventsBehavior(UI.GlassPane.PointerEventsBehavior.BlockedByGlassPane);DragHandler._glassPane.show(DragHandler._documentForMouseOut);}}
  9. _disposeGlassPane(){if(!this._glassPaneInUse){return;}
  10. this._glassPaneInUse=false;if(--DragHandler._glassPaneUsageCount){return;}
  11. DragHandler._glassPane.hide();delete DragHandler._glassPane;delete DragHandler._documentForMouseOut;}
  12. elementDragStart(targetElement,elementDragStart,elementDrag,elementDragEnd,cursor,event){if(event.button||(Host.isMac()&&event.ctrlKey)){return;}
  13. if(this._elementDraggingEventListener){return;}
  14. if(elementDragStart&&!elementDragStart((event))){return;}
  15. const targetDocument=event.target.ownerDocument;this._elementDraggingEventListener=elementDrag;this._elementEndDraggingEventListener=elementDragEnd;console.assert((DragHandler._documentForMouseOut||targetDocument)===targetDocument,'Dragging on multiple documents.');DragHandler._documentForMouseOut=targetDocument;this._dragEventsTargetDocument=targetDocument;try{this._dragEventsTargetDocumentTop=targetDocument.defaultView.top.document;}catch(e){this._dragEventsTargetDocumentTop=this._dragEventsTargetDocument;}
  16. targetDocument.addEventListener('mousemove',this._elementDragMove,true);targetDocument.addEventListener('mouseup',this._elementDragEnd,true);targetDocument.addEventListener('mouseout',this._mouseOutWhileDragging,true);if(targetDocument!==this._dragEventsTargetDocumentTop){this._dragEventsTargetDocumentTop.addEventListener('mouseup',this._elementDragEnd,true);}
  17. if(typeof cursor==='string'){this._restoreCursorAfterDrag=restoreCursor.bind(this,targetElement.style.cursor);targetElement.style.cursor=cursor;targetDocument.body.style.cursor=cursor;}
  18. function restoreCursor(oldCursor){targetDocument.body.style.removeProperty('cursor');targetElement.style.cursor=oldCursor;this._restoreCursorAfterDrag=null;}
  19. event.preventDefault();}
  20. _mouseOutWhileDragging(){this._unregisterMouseOutWhileDragging();this._createGlassPane();}
  21. _unregisterMouseOutWhileDragging(){if(!DragHandler._documentForMouseOut){return;}
  22. DragHandler._documentForMouseOut.removeEventListener('mouseout',this._mouseOutWhileDragging,true);}
  23. _unregisterDragEvents(){if(!this._dragEventsTargetDocument){return;}
  24. this._dragEventsTargetDocument.removeEventListener('mousemove',this._elementDragMove,true);this._dragEventsTargetDocument.removeEventListener('mouseup',this._elementDragEnd,true);if(this._dragEventsTargetDocument!==this._dragEventsTargetDocumentTop){this._dragEventsTargetDocumentTop.removeEventListener('mouseup',this._elementDragEnd,true);}
  25. delete this._dragEventsTargetDocument;delete this._dragEventsTargetDocumentTop;}
  26. _elementDragMove(event){if(event.buttons!==1){this._elementDragEnd(event);return;}
  27. if(this._elementDraggingEventListener((event))){this._cancelDragEvents(event);}}
  28. _cancelDragEvents(event){this._unregisterDragEvents();this._unregisterMouseOutWhileDragging();if(this._restoreCursorAfterDrag){this._restoreCursorAfterDrag();}
  29. this._disposeGlassPane();delete this._elementDraggingEventListener;delete this._elementEndDraggingEventListener;}
  30. _elementDragEnd(event){const elementDragEnd=this._elementEndDraggingEventListener;this._cancelDragEvents((event));event.preventDefault();if(elementDragEnd){elementDragEnd((event));}}}
  31. DragHandler._glassPaneUsageCount=0;export function isBeingEdited(node){if(!node||node.nodeType!==Node.ELEMENT_NODE){return false;}
  32. let element=(node);if(element.classList.contains('text-prompt')||element.nodeName==='INPUT'||element.nodeName==='TEXTAREA'){return true;}
  33. if(!UI.__editingCount){return false;}
  34. while(element){if(element.__editing){return true;}
  35. element=element.parentElementOrShadowHost();}
  36. return false;}
  37. export function isEditing(){if(UI.__editingCount){return true;}
  38. const focused=document.deepActiveElement();if(!focused){return false;}
  39. return focused.classList.contains('text-prompt')||focused.nodeName==='INPUT'||focused.nodeName==='TEXTAREA';}
  40. export function markBeingEdited(element,value){if(value){if(element.__editing){return false;}
  41. element.classList.add('being-edited');element.__editing=true;UI.__editingCount=(UI.__editingCount||0)+1;}else{if(!element.__editing){return false;}
  42. element.classList.remove('being-edited');delete element.__editing;--UI.__editingCount;}
  43. return true;}
  44. const _numberRegex=/^(-?(?:\d+(?:\.\d+)?|\.\d+))$/;export const StyleValueDelimiters=' \xA0\t\n"\':;,/()';function _valueModificationDirection(event){let direction=null;if(event.type==='mousewheel'){if(event.wheelDeltaY>0||event.wheelDeltaX>0){direction='Up';}else if(event.wheelDeltaY<0||event.wheelDeltaX<0){direction='Down';}}else{if(event.key==='ArrowUp'||event.key==='PageUp'){direction='Up';}else if(event.key==='ArrowDown'||event.key==='PageDown'){direction='Down';}}
  45. return direction;}
  46. function _modifiedHexValue(hexString,event){const direction=_valueModificationDirection(event);if(!direction){return null;}
  47. const mouseEvent=(event);const number=parseInt(hexString,16);if(isNaN(number)||!isFinite(number)){return null;}
  48. const hexStrLen=hexString.length;const channelLen=hexStrLen/3;if(channelLen!==1&&channelLen!==2){return null;}
  49. let delta=0;if(UI.KeyboardShortcut.eventHasCtrlOrMeta(mouseEvent)){delta+=Math.pow(16,channelLen*2);}
  50. if(mouseEvent.shiftKey){delta+=Math.pow(16,channelLen);}
  51. if(mouseEvent.altKey){delta+=1;}
  52. if(delta===0){delta=1;}
  53. if(direction==='Down'){delta*=-1;}
  54. const maxValue=Math.pow(16,hexStrLen)-1;const result=Number.constrain(number+delta,0,maxValue);let resultString=result.toString(16).toUpperCase();for(let i=0,lengthDelta=hexStrLen-resultString.length;i<lengthDelta;++i){resultString='0'+resultString;}
  55. return resultString;}
  56. function _modifiedFloatNumber(number,event,modifierMultiplier){const direction=_valueModificationDirection(event);if(!direction){return null;}
  57. const mouseEvent=(event);let delta=1;if(UI.KeyboardShortcut.eventHasCtrlOrMeta(mouseEvent)){delta=100;}else if(mouseEvent.shiftKey){delta=10;}else if(mouseEvent.altKey){delta=0.1;}
  58. if(direction==='Down'){delta*=-1;}
  59. if(modifierMultiplier){delta*=modifierMultiplier;}
  60. const result=Number((number+delta).toFixed(6));if(!String(result).match(_numberRegex)){return null;}
  61. return result;}
  62. export function createReplacementString(wordString,event,customNumberHandler){let prefix;let suffix;let number;let replacementString=null;let matches=/(.*#)([\da-fA-F]+)(.*)/.exec(wordString);if(matches&&matches.length){prefix=matches[1];suffix=matches[3];number=_modifiedHexValue(matches[2],event);if(number!==null){replacementString=prefix+number+suffix;}}else{matches=/(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString);if(matches&&matches.length){prefix=matches[1];suffix=matches[3];number=_modifiedFloatNumber(parseFloat(matches[2]),event);if(number!==null){replacementString=customNumberHandler?customNumberHandler(prefix,number,suffix):prefix+number+suffix;}}}
  63. return replacementString;}
  64. export function handleElementValueModifications(event,element,finishHandler,suggestionHandler,customNumberHandler){function createRange(){return document.createRange();}
  65. const arrowKeyOrMouseWheelEvent=(event.key==='ArrowUp'||event.key==='ArrowDown'||event.type==='mousewheel');const pageKeyPressed=(event.key==='PageUp'||event.key==='PageDown');if(!arrowKeyOrMouseWheelEvent&&!pageKeyPressed){return false;}
  66. const selection=element.getComponentSelection();if(!selection.rangeCount){return false;}
  67. const selectionRange=selection.getRangeAt(0);if(!selectionRange.commonAncestorContainer.isSelfOrDescendant(element)){return false;}
  68. const originalValue=element.textContent;const wordRange=selectionRange.startContainer.rangeOfWord(selectionRange.startOffset,StyleValueDelimiters,element);const wordString=wordRange.toString();if(suggestionHandler&&suggestionHandler(wordString)){return false;}
  69. const replacementString=createReplacementString(wordString,event,customNumberHandler);if(replacementString){const replacementTextNode=createTextNode(replacementString);wordRange.deleteContents();wordRange.insertNode(replacementTextNode);const finalSelectionRange=createRange();finalSelectionRange.setStart(replacementTextNode,0);finalSelectionRange.setEnd(replacementTextNode,replacementString.length);selection.removeAllRanges();selection.addRange(finalSelectionRange);event.handled=true;event.preventDefault();if(finishHandler){finishHandler(originalValue,replacementString);}
  70. return true;}
  71. return false;}
  72. Number.preciseMillisToString=function(ms,precision){precision=precision||0;const format='%.'+precision+'f\xa0ms';return Common.UIString(format,ms);};export const _microsFormat=new Common.UIStringFormat('%.0f\xa0\u03bcs');export const _subMillisFormat=new Common.UIStringFormat('%.2f\xa0ms');export const _millisFormat=new Common.UIStringFormat('%.0f\xa0ms');export const _secondsFormat=new Common.UIStringFormat('%.2f\xa0s');export const _minutesFormat=new Common.UIStringFormat('%.1f\xa0min');export const _hoursFormat=new Common.UIStringFormat('%.1f\xa0hrs');export const _daysFormat=new Common.UIStringFormat('%.1f\xa0days');Number.millisToString=function(ms,higherResolution){if(!isFinite(ms)){return'-';}
  73. if(ms===0){return'0';}
  74. if(higherResolution&&ms<0.1){return _microsFormat.format(ms*1000);}
  75. if(higherResolution&&ms<1000){return _subMillisFormat.format(ms);}
  76. if(ms<1000){return _millisFormat.format(ms);}
  77. const seconds=ms/1000;if(seconds<60){return _secondsFormat.format(seconds);}
  78. const minutes=seconds/60;if(minutes<60){return _minutesFormat.format(minutes);}
  79. const hours=minutes/60;if(hours<24){return _hoursFormat.format(hours);}
  80. const days=hours/24;return _daysFormat.format(days);};Number.secondsToString=function(seconds,higherResolution){if(!isFinite(seconds)){return'-';}
  81. return Number.millisToString(seconds*1000,higherResolution);};Number.bytesToString=function(bytes){if(bytes<1024){return Common.UIString('%.0f\xa0B',bytes);}
  82. const kilobytes=bytes/1024;if(kilobytes<100){return Common.UIString('%.1f\xa0KB',kilobytes);}
  83. if(kilobytes<1024){return Common.UIString('%.0f\xa0KB',kilobytes);}
  84. const megabytes=kilobytes/1024;if(megabytes<100){return Common.UIString('%.1f\xa0MB',megabytes);}else{return Common.UIString('%.0f\xa0MB',megabytes);}};Number.withThousandsSeparator=function(num){let str=num+'';const re=/(\d+)(\d{3})/;while(str.match(re)){str=str.replace(re,'$1\xa0$2');}
  85. return str;};export function formatLocalized(format,substitutions){const formatters={s:substitution=>substitution};function append(a,b){a.appendChild(typeof b==='string'?createTextNode(b):b);return a;}
  86. return String.format(Common.UIString(format),substitutions,formatters,createElement('span'),append).formattedResult;}
  87. export function openLinkExternallyLabel(){return Common.UIString('Open in new tab');}
  88. export function copyLinkAddressLabel(){return Common.UIString('Copy link address');}
  89. export function anotherProfilerActiveLabel(){return Common.UIString('Another profiler is already active');}
  90. export function asyncStackTraceLabel(description){if(description){if(description==='Promise.resolve'){return ls`Promise resolved (async)`;}else if(description==='Promise.reject'){return ls`Promise rejected (async)`;}
  91. return ls`${description} (async)`;}
  92. return Common.UIString('Async Call');}
  93. export function installComponentRootStyles(element){_injectCoreStyles(element);element.classList.add('platform-'+Host.platform());if(!Host.isMac()&&measuredScrollbarWidth(element.ownerDocument)===0){element.classList.add('overlay-scrollbar-enabled');}}
  94. let _measuredScrollbarWidth;export function measuredScrollbarWidth(document){if(typeof _measuredScrollbarWidth==='number'){return _measuredScrollbarWidth;}
  95. if(!document){return 16;}
  96. const scrollDiv=document.createElement('div');scrollDiv.setAttribute('style','width: 100px; height: 100px; overflow: scroll;');document.body.appendChild(scrollDiv);_measuredScrollbarWidth=scrollDiv.offsetWidth-scrollDiv.clientWidth;document.body.removeChild(scrollDiv);return _measuredScrollbarWidth;}
  97. export function createShadowRootWithCoreStyles(element,cssFile,delegatesFocus){const shadowRoot=element.attachShadow({mode:'open',delegatesFocus});_injectCoreStyles(shadowRoot);if(cssFile){appendStyle(shadowRoot,cssFile);}
  98. shadowRoot.addEventListener('focus',_focusChanged.bind(UI),true);return shadowRoot;}
  99. function _injectCoreStyles(root){appendStyle(root,'ui/inspectorCommon.css');appendStyle(root,'ui/textButton.css');UI.themeSupport.injectHighlightStyleSheets(root);UI.themeSupport.injectCustomStyleSheets(root);}
  100. function _windowFocused(document,event){if(event.target.document.nodeType===Node.DOCUMENT_NODE){document.body.classList.remove('inactive');}
  101. UI._keyboardFocus=true;const listener=()=>{const activeElement=document.deepActiveElement();if(activeElement){activeElement.removeAttribute('data-keyboard-focus');}
  102. UI._keyboardFocus=false;};document.defaultView.requestAnimationFrame(()=>{UI._keyboardFocus=false;document.removeEventListener('mousedown',listener,true);});document.addEventListener('mousedown',listener,true);}
  103. function _windowBlurred(document,event){if(event.target.document.nodeType===Node.DOCUMENT_NODE){document.body.classList.add('inactive');}}
  104. function _focusChanged(event){const document=event.target&&event.target.ownerDocument;const element=document?document.deepActiveElement():null;UI.Widget.focusWidgetForNode(element);UI.XWidget.focusWidgetForNode(element);if(!UI._keyboardFocus){return;}
  105. UI.markAsFocusedByKeyboard(element);}
  106. UI.markAsFocusedByKeyboard=function(element){element.setAttribute('data-keyboard-focus','true');element.addEventListener('blur',()=>element.removeAttribute('data-keyboard-focus'),{once:true,capture:true});};export class ElementFocusRestorer{constructor(element){this._element=element;this._previous=element.ownerDocument.deepActiveElement();element.focus();}
  107. restore(){if(!this._element){return;}
  108. if(this._element.hasFocus()&&this._previous){this._previous.focus();}
  109. this._previous=null;this._element=null;}}
  110. export function highlightSearchResult(element,offset,length,domChanges){const result=highlightSearchResults(element,[new TextUtils.SourceRange(offset,length)],domChanges);return result.length?result[0]:null;}
  111. export function highlightSearchResults(element,resultRanges,changes){return highlightRangesWithStyleClass(element,resultRanges,highlightedSearchResultClassName,changes);}
  112. export function runCSSAnimationOnce(element,className){function animationEndCallback(){element.classList.remove(className);element.removeEventListener('webkitAnimationEnd',animationEndCallback,false);}
  113. if(element.classList.contains(className)){element.classList.remove(className);}
  114. element.addEventListener('webkitAnimationEnd',animationEndCallback,false);element.classList.add(className);}
  115. export function highlightRangesWithStyleClass(element,resultRanges,styleClass,changes){changes=changes||[];const highlightNodes=[];const textNodes=element.childTextNodes();const lineText=textNodes.map(function(node){return node.textContent;}).join('');const ownerDocument=element.ownerDocument;if(textNodes.length===0){return highlightNodes;}
  116. const nodeRanges=[];let rangeEndOffset=0;for(let i=0;i<textNodes.length;++i){const range={};range.offset=rangeEndOffset;range.length=textNodes[i].textContent.length;rangeEndOffset=range.offset+range.length;nodeRanges.push(range);}
  117. let startIndex=0;for(let i=0;i<resultRanges.length;++i){const startOffset=resultRanges[i].offset;const endOffset=startOffset+resultRanges[i].length;while(startIndex<textNodes.length&&nodeRanges[startIndex].offset+nodeRanges[startIndex].length<=startOffset){startIndex++;}
  118. let endIndex=startIndex;while(endIndex<textNodes.length&&nodeRanges[endIndex].offset+nodeRanges[endIndex].length<endOffset){endIndex++;}
  119. if(endIndex===textNodes.length){break;}
  120. const highlightNode=ownerDocument.createElement('span');highlightNode.className=styleClass;highlightNode.textContent=lineText.substring(startOffset,endOffset);const lastTextNode=textNodes[endIndex];const lastText=lastTextNode.textContent;lastTextNode.textContent=lastText.substring(endOffset-nodeRanges[endIndex].offset);changes.push({node:lastTextNode,type:'changed',oldText:lastText,newText:lastTextNode.textContent});if(startIndex===endIndex){lastTextNode.parentElement.insertBefore(highlightNode,lastTextNode);changes.push({node:highlightNode,type:'added',nextSibling:lastTextNode,parent:lastTextNode.parentElement});highlightNodes.push(highlightNode);const prefixNode=ownerDocument.createTextNode(lastText.substring(0,startOffset-nodeRanges[startIndex].offset));lastTextNode.parentElement.insertBefore(prefixNode,highlightNode);changes.push({node:prefixNode,type:'added',nextSibling:highlightNode,parent:lastTextNode.parentElement});}else{const firstTextNode=textNodes[startIndex];const firstText=firstTextNode.textContent;const anchorElement=firstTextNode.nextSibling;firstTextNode.parentElement.insertBefore(highlightNode,anchorElement);changes.push({node:highlightNode,type:'added',nextSibling:anchorElement,parent:firstTextNode.parentElement});highlightNodes.push(highlightNode);firstTextNode.textContent=firstText.substring(0,startOffset-nodeRanges[startIndex].offset);changes.push({node:firstTextNode,type:'changed',oldText:firstText,newText:firstTextNode.textContent});for(let j=startIndex+1;j<endIndex;j++){const textNode=textNodes[j];const text=textNode.textContent;textNode.textContent='';changes.push({node:textNode,type:'changed',oldText:text,newText:textNode.textContent});}}
  121. startIndex=endIndex;nodeRanges[startIndex].offset=endOffset;nodeRanges[startIndex].length=lastTextNode.textContent.length;}
  122. return highlightNodes;}
  123. export function applyDomChanges(domChanges){for(let i=0,size=domChanges.length;i<size;++i){const entry=domChanges[i];switch(entry.type){case'added':entry.parent.insertBefore(entry.node,entry.nextSibling);break;case'changed':entry.node.textContent=entry.newText;break;}}}
  124. export function revertDomChanges(domChanges){for(let i=domChanges.length-1;i>=0;--i){const entry=domChanges[i];switch(entry.type){case'added':entry.node.remove();break;case'changed':entry.node.textContent=entry.oldText;break;}}}
  125. export function measurePreferredSize(element,containerElement){const oldParent=element.parentElement;const oldNextSibling=element.nextSibling;containerElement=containerElement||element.ownerDocument.body;containerElement.appendChild(element);element.positionAt(0,0);const result=element.getBoundingClientRect();element.positionAt(undefined,undefined);if(oldParent){oldParent.insertBefore(element,oldNextSibling);}else{element.remove();}
  126. return new UI.Size(result.width,result.height);}
  127. class InvokeOnceHandlers{constructor(autoInvoke){this._handlers=null;this._autoInvoke=autoInvoke;}
  128. add(object,method){if(!this._handlers){this._handlers=new Map();if(this._autoInvoke){this.scheduleInvoke();}}
  129. let methods=this._handlers.get(object);if(!methods){methods=new Set();this._handlers.set(object,methods);}
  130. methods.add(method);}
  131. scheduleInvoke(){if(this._handlers){requestAnimationFrame(this._invoke.bind(this));}}
  132. _invoke(){const handlers=this._handlers;this._handlers=null;const keys=handlers.keysArray();for(let i=0;i<keys.length;++i){const object=keys[i];const methods=handlers.get(object).valuesArray();for(let j=0;j<methods.length;++j){methods[j].call(object);}}}}
  133. let _coalescingLevel=0;let _postUpdateHandlers=null;export function startBatchUpdate(){if(!_coalescingLevel++){_postUpdateHandlers=new InvokeOnceHandlers(false);}}
  134. export function endBatchUpdate(){if(--_coalescingLevel){return;}
  135. _postUpdateHandlers.scheduleInvoke();_postUpdateHandlers=null;}
  136. export function invokeOnceAfterBatchUpdate(object,method){if(!_postUpdateHandlers){_postUpdateHandlers=new InvokeOnceHandlers(true);}
  137. _postUpdateHandlers.add(object,method);}
  138. export function animateFunction(window,func,params,duration,animationComplete){const start=window.performance.now();let raf=window.requestAnimationFrame(animationStep);function animationStep(timestamp){const progress=Number.constrain((timestamp-start)/duration,0,1);func(...params.map(p=>p.from+(p.to-p.from)*progress));if(progress<1){raf=window.requestAnimationFrame(animationStep);}else if(animationComplete){animationComplete();}}
  139. return()=>window.cancelAnimationFrame(raf);}
  140. export class LongClickController extends Common.Object{constructor(element,callback,isEditKeyFunc=event=>isEnterOrSpaceKey(event)){super();this._element=element;this._callback=callback;this._editKey=isEditKeyFunc;this._enable();}
  141. reset(){if(this._longClickInterval){clearInterval(this._longClickInterval);delete this._longClickInterval;}}
  142. _enable(){if(this._longClickData){return;}
  143. const boundKeyDown=keyDown.bind(this);const boundKeyUp=keyUp.bind(this);const boundMouseDown=mouseDown.bind(this);const boundMouseUp=mouseUp.bind(this);const boundReset=this.reset.bind(this);this._element.addEventListener('keydown',boundKeyDown,false);this._element.addEventListener('keyup',boundKeyUp,false);this._element.addEventListener('mousedown',boundMouseDown,false);this._element.addEventListener('mouseout',boundReset,false);this._element.addEventListener('mouseup',boundMouseUp,false);this._element.addEventListener('click',boundReset,true);this._longClickData={mouseUp:boundMouseUp,mouseDown:boundMouseDown,reset:boundReset};function keyDown(e){if(this._editKey(e)){e.consume(true);const callback=this._callback;this._longClickInterval=setTimeout(callback.bind(null,e),LongClickController.TIME_MS);}}
  144. function keyUp(e){if(this._editKey(e)){e.consume(true);this.reset();}}
  145. function mouseDown(e){if(e.which!==1){return;}
  146. const callback=this._callback;this._longClickInterval=setTimeout(callback.bind(null,e),LongClickController.TIME_MS);}
  147. function mouseUp(e){if(e.which!==1){return;}
  148. this.reset();}}
  149. dispose(){if(!this._longClickData){return;}
  150. this._element.removeEventListener('mousedown',this._longClickData.mouseDown,false);this._element.removeEventListener('mouseout',this._longClickData.reset,false);this._element.removeEventListener('mouseup',this._longClickData.mouseUp,false);this._element.addEventListener('click',this._longClickData.reset,true);delete this._longClickData;}}
  151. LongClickController.TIME_MS=200;export function initializeUIUtils(document,themeSetting){document.body.classList.toggle('inactive',!document.hasFocus());document.defaultView.addEventListener('focus',_windowFocused.bind(UI,document),false);document.defaultView.addEventListener('blur',_windowBlurred.bind(UI,document),false);document.addEventListener('focus',_focusChanged.bind(UI),true);document.addEventListener('keydown',event=>{UI._keyboardFocus=true;document.defaultView.requestAnimationFrame(()=>void(UI._keyboardFocus=false));},true);if(!UI.themeSupport){UI.themeSupport=new ThemeSupport(themeSetting);}
  152. UI.themeSupport.applyTheme(document);const body=(document.body);appendStyle(body,'ui/inspectorStyle.css');UI.GlassPane.setContainer((document.body));}
  153. export function beautifyFunctionName(name){return name||Common.UIString('(anonymous)');}
  154. export function registerCustomElement(localName,typeExtension,definition){self.customElements.define(typeExtension,class extends definition{constructor(){super();this.setAttribute('is',typeExtension);}},{extends:localName});return()=>createElement(localName,typeExtension);}
  155. export function createTextButton(text,clickHandler,className,primary){const element=createElementWithClass('button',className||'');element.textContent=text;element.classList.add('text-button');if(primary){element.classList.add('primary-button');}
  156. if(clickHandler){element.addEventListener('click',clickHandler,false);}
  157. element.type='button';return element;}
  158. export function createInput(className,type){const element=createElementWithClass('input',className||'');element.spellcheck=false;element.classList.add('harmony-input');if(type){element.type=type;}
  159. return element;}
  160. export function createLabel(title,className,associatedControl){const element=createElementWithClass('label',className||'');element.textContent=title;if(associatedControl){UI.ARIAUtils.bindLabelToControl(element,associatedControl);}
  161. return element;}
  162. export function createRadioLabel(name,title,checked){const element=createElement('span','dt-radio');element.radioElement.name=name;element.radioElement.checked=!!checked;element.labelElement.createTextChild(title);return element;}
  163. export function createIconLabel(title,iconClass){const element=createElement('span','dt-icon-label');element.createChild('span').textContent=title;element.type=iconClass;return element;}
  164. export function createSlider(min,max,tabIndex){const element=createElement('span','dt-slider');element.sliderElement.min=min;element.sliderElement.max=max;element.sliderElement.step=1;element.sliderElement.tabIndex=tabIndex;return element;}
  165. export function appendStyle(node,cssFile){const content=Root.Runtime.cachedResources[cssFile]||'';if(!content){console.error(cssFile+' not preloaded. Check module.json');}
  166. let styleElement=createElement('style');styleElement.textContent=content;node.appendChild(styleElement);const themeStyleSheet=UI.themeSupport.themeStyleSheet(cssFile,content);if(themeStyleSheet){styleElement=createElement('style');styleElement.textContent=themeStyleSheet+'\n'+Root.Runtime.resolveSourceURL(cssFile+'.theme');node.appendChild(styleElement);}}
  167. export class CheckboxLabel extends HTMLSpanElement{constructor(){super();this._shadowRoot;this.checkboxElement;this.textElement;CheckboxLabel._lastId=(CheckboxLabel._lastId||0)+1;const id='ui-checkbox-label'+CheckboxLabel._lastId;this._shadowRoot=createShadowRootWithCoreStyles(this,'ui/checkboxTextLabel.css');this.checkboxElement=(this._shadowRoot.createChild('input'));this.checkboxElement.type='checkbox';this.checkboxElement.setAttribute('id',id);this.textElement=this._shadowRoot.createChild('label','dt-checkbox-text');this.textElement.setAttribute('for',id);this._shadowRoot.createChild('slot');}
  168. static create(title,checked,subtitle){if(!CheckboxLabel._constructor){CheckboxLabel._constructor=registerCustomElement('span','dt-checkbox',CheckboxLabel);}
  169. const element=(CheckboxLabel._constructor());element.checkboxElement.checked=!!checked;if(title!==undefined){element.textElement.textContent=title;if(subtitle!==undefined){element.textElement.createChild('div','dt-checkbox-subtitle').textContent=subtitle;}}
  170. return element;}
  171. set backgroundColor(color){this.checkboxElement.classList.add('dt-checkbox-themed');this.checkboxElement.style.backgroundColor=color;}
  172. set checkColor(color){this.checkboxElement.classList.add('dt-checkbox-themed');const stylesheet=createElement('style');stylesheet.textContent='input.dt-checkbox-themed:checked:after { background-color: '+color+'}';this._shadowRoot.appendChild(stylesheet);}
  173. set borderColor(color){this.checkboxElement.classList.add('dt-checkbox-themed');this.checkboxElement.style.borderColor=color;}}
  174. (function(){let labelId=0;registerCustomElement('span','dt-radio',class extends HTMLSpanElement{constructor(){super();this.radioElement=this.createChild('input','dt-radio-button');this.labelElement=this.createChild('label');const id='dt-radio-button-id'+(++labelId);this.radioElement.id=id;this.radioElement.type='radio';this.labelElement.htmlFor=id;const root=createShadowRootWithCoreStyles(this,'ui/radioButton.css');root.createChild('slot');this.addEventListener('click',radioClickHandler,false);}});function radioClickHandler(event){if(this.radioElement.checked||this.radioElement.disabled){return;}
  175. this.radioElement.checked=true;this.radioElement.dispatchEvent(new Event('change'));}
  176. registerCustomElement('span','dt-icon-label',class extends HTMLSpanElement{constructor(){super();const root=createShadowRootWithCoreStyles(this);this._iconElement=UI.Icon.create();this._iconElement.style.setProperty('margin-right','4px');root.appendChild(this._iconElement);root.createChild('slot');}
  177. set type(type){this._iconElement.setIconType(type);}});registerCustomElement('span','dt-slider',class extends HTMLSpanElement{constructor(){super();const root=createShadowRootWithCoreStyles(this,'ui/slider.css');this.sliderElement=createElementWithClass('input','dt-range-input');this.sliderElement.type='range';root.appendChild(this.sliderElement);}
  178. set value(amount){this.sliderElement.value=amount;}
  179. get value(){return this.sliderElement.value;}});registerCustomElement('span','dt-small-bubble',class extends HTMLSpanElement{constructor(){super();const root=createShadowRootWithCoreStyles(this,'ui/smallBubble.css');this._textElement=root.createChild('div');this._textElement.className='info';this._textElement.createChild('slot');}
  180. set type(type){this._textElement.className=type;}});registerCustomElement('div','dt-close-button',class extends HTMLDivElement{constructor(){super();const root=createShadowRootWithCoreStyles(this,'ui/closeButton.css');this._buttonElement=root.createChild('div','close-button');UI.ARIAUtils.setAccessibleName(this._buttonElement,ls`Close`);UI.ARIAUtils.markAsButton(this._buttonElement);const regularIcon=UI.Icon.create('smallicon-cross','default-icon');this._hoverIcon=UI.Icon.create('mediumicon-red-cross-hover','hover-icon');this._activeIcon=UI.Icon.create('mediumicon-red-cross-active','active-icon');this._buttonElement.appendChild(regularIcon);this._buttonElement.appendChild(this._hoverIcon);this._buttonElement.appendChild(this._activeIcon);}
  181. set gray(gray){if(gray){this._hoverIcon.setIconType('mediumicon-gray-cross-hover');this._activeIcon.setIconType('mediumicon-gray-cross-active');}else{this._hoverIcon.setIconType('mediumicon-red-cross-hover');this._activeIcon.setIconType('mediumicon-red-cross-active');}}
  182. setAccessibleName(name){UI.ARIAUtils.setAccessibleName(this._buttonElement,name);}
  183. setTabbable(tabbable){if(tabbable){this._buttonElement.tabIndex=0;}else{this._buttonElement.tabIndex=-1;}}});})();export function bindInput(input,apply,validate,numeric,modifierMultiplier){input.addEventListener('change',onChange,false);input.addEventListener('input',onInput,false);input.addEventListener('keydown',onKeyDown,false);input.addEventListener('focus',input.select.bind(input),false);function onInput(){input.classList.toggle('error-input',!validate(input.value));}
  184. function onChange(){const{valid}=validate(input.value);input.classList.toggle('error-input',!valid);if(valid){apply(input.value);}}
  185. function onKeyDown(event){if(isEnterKey(event)){const{valid}=validate(input.value);if(valid){apply(input.value);}
  186. event.preventDefault();return;}
  187. if(!numeric){return;}
  188. const value=_modifiedFloatNumber(parseFloat(input.value),event,modifierMultiplier);const stringValue=value?String(value):'';const{valid}=validate(stringValue);if(!valid||!value){return;}
  189. input.value=stringValue;apply(input.value);event.preventDefault();}
  190. function setValue(value){if(value===input.value){return;}
  191. const{valid}=validate(value);input.classList.toggle('error-input',!valid);input.value=value;}
  192. return setValue;}
  193. export function trimText(context,text,maxWidth,trimFunction){const maxLength=200;if(maxWidth<=10){return'';}
  194. if(text.length>maxLength){text=trimFunction(text,maxLength);}
  195. const textWidth=measureTextWidth(context,text);if(textWidth<=maxWidth){return text;}
  196. let l=0;let r=text.length;let lv=0;let rv=textWidth;while(l<r&&lv!==rv&&lv!==maxWidth){const m=Math.ceil(l+(r-l)*(maxWidth-lv)/(rv-lv));const mv=measureTextWidth(context,trimFunction(text,m));if(mv<=maxWidth){l=m;lv=mv;}else{r=m-1;rv=mv;}}
  197. text=trimFunction(text,l);return text!=='\u2026'?text:'';}
  198. export function trimTextMiddle(context,text,maxWidth){return trimText(context,text,maxWidth,(text,width)=>text.trimMiddle(width));}
  199. export function trimTextEnd(context,text,maxWidth){return trimText(context,text,maxWidth,(text,width)=>text.trimEndWithMaxLength(width));}
  200. export function measureTextWidth(context,text){const maxCacheableLength=200;if(text.length>maxCacheableLength){return context.measureText(text).width;}
  201. let widthCache=measureTextWidth._textWidthCache;if(!widthCache){widthCache=new Map();measureTextWidth._textWidthCache=widthCache;}
  202. const font=context.font;let textWidths=widthCache.get(font);if(!textWidths){textWidths=new Map();widthCache.set(font,textWidths);}
  203. let width=textWidths.get(text);if(!width){width=context.measureText(text).width;textWidths.set(text,width);}
  204. return width;}
  205. export class ThemeSupport{constructor(setting){const systemPreferredTheme=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'default';this._themeName=setting.get()==='systemPreferred'?systemPreferredTheme:setting.get();this._themableProperties=new Set(['color','box-shadow','text-shadow','outline-color','background-image','background-color','border-left-color','border-right-color','border-top-color','border-bottom-color','-webkit-border-image','fill','stroke']);this._cachedThemePatches=new Map();this._setting=setting;this._customSheets=new Set();}
  206. hasTheme(){return this._themeName!=='default';}
  207. themeName(){return this._themeName;}
  208. injectHighlightStyleSheets(element){this._injectingStyleSheet=true;appendStyle(element,'ui/inspectorSyntaxHighlight.css');if(this._themeName==='dark'){appendStyle(element,'ui/inspectorSyntaxHighlightDark.css');}
  209. this._injectingStyleSheet=false;}
  210. injectCustomStyleSheets(element){for(const sheet of this._customSheets){const styleElement=createElement('style');styleElement.textContent=sheet;element.appendChild(styleElement);}}
  211. addCustomStylesheet(sheetText){this._customSheets.add(sheetText);}
  212. applyTheme(document){if(!this.hasTheme()){return;}
  213. if(this._themeName==='dark'){document.documentElement.classList.add('-theme-with-dark-background');}
  214. const styleSheets=document.styleSheets;const result=[];for(let i=0;i<styleSheets.length;++i){result.push(this._patchForTheme(styleSheets[i].href,styleSheets[i]));}
  215. result.push('/*# sourceURL=inspector.css.theme */');const styleElement=createElement('style');styleElement.textContent=result.join('\n');document.head.appendChild(styleElement);}
  216. themeStyleSheet(id,text){if(!this.hasTheme()||this._injectingStyleSheet){return'';}
  217. let patch=this._cachedThemePatches.get(id);if(!patch){const styleElement=createElement('style');styleElement.textContent=text;document.body.appendChild(styleElement);patch=this._patchForTheme(id,styleElement.sheet);document.body.removeChild(styleElement);}
  218. return patch;}
  219. _patchForTheme(id,styleSheet){const cached=this._cachedThemePatches.get(id);if(cached){return cached;}
  220. try{const rules=styleSheet.cssRules;const result=[];for(let j=0;j<rules.length;++j){if(rules[j]instanceof CSSImportRule){result.push(this._patchForTheme(rules[j].styleSheet.href,rules[j].styleSheet));continue;}
  221. const output=[];const style=rules[j].style;const selectorText=rules[j].selectorText;for(let i=0;style&&i<style.length;++i){this._patchProperty(selectorText,style,style[i],output);}
  222. if(output.length){result.push(rules[j].selectorText+'{'+output.join('')+'}');}}
  223. const fullText=result.join('\n');this._cachedThemePatches.set(id,fullText);return fullText;}catch(e){this._setting.set('default');return'';}}
  224. _patchProperty(selectorText,style,name,output){if(!this._themableProperties.has(name)){return;}
  225. const value=style.getPropertyValue(name);if(!value||value==='none'||value==='inherit'||value==='initial'||value==='transparent'){return;}
  226. if(name==='background-image'&&value.indexOf('gradient')===-1){return;}
  227. if(selectorText.indexOf('-theme-')!==-1){return;}
  228. let colorUsage=ThemeSupport.ColorUsage.Unknown;if(name.indexOf('background')===0||name.indexOf('border')===0){colorUsage|=ThemeSupport.ColorUsage.Background;}
  229. if(name.indexOf('background')===-1){colorUsage|=ThemeSupport.ColorUsage.Foreground;}
  230. output.push(name);output.push(':');const items=value.replace(Common.Color.Regex,'\0$1\0').split('\0');for(let i=0;i<items.length;++i){output.push(this.patchColorText(items[i],(colorUsage)));}
  231. if(style.getPropertyPriority(name)){output.push(' !important');}
  232. output.push(';');}
  233. patchColorText(text,colorUsage){const color=Common.Color.parse(text);if(!color){return text;}
  234. const outColor=this.patchColor(color,colorUsage);let outText=outColor.asString(null);if(!outText){outText=outColor.asString(outColor.hasAlpha()?Common.Color.Format.RGBA:Common.Color.Format.RGB);}
  235. return outText||text;}
  236. patchColor(color,colorUsage){const hsla=color.hsla();this._patchHSLA(hsla,colorUsage);const rgba=[];Common.Color.hsl2rgb(hsla,rgba);return new Common.Color(rgba,color.format());}
  237. _patchHSLA(hsla,colorUsage){const hue=hsla[0];const sat=hsla[1];let lit=hsla[2];const alpha=hsla[3];switch(this._themeName){case'dark':const minCap=colorUsage&ThemeSupport.ColorUsage.Background?0.14:0;const maxCap=colorUsage&ThemeSupport.ColorUsage.Foreground?0.9:1;lit=1-lit;if(lit<minCap*2){lit=minCap+lit/2;}else if(lit>2*maxCap-1){lit=maxCap-1/2+lit/2;}
  238. break;}
  239. hsla[0]=Number.constrain(hue,0,1);hsla[1]=Number.constrain(sat,0,1);hsla[2]=Number.constrain(lit,0,1);hsla[3]=Number.constrain(alpha,0,1);}}
  240. ThemeSupport.ColorUsage={Unknown:0,Foreground:1<<0,Background:1<<1,};export function createDocumentationLink(article,title){return UI.XLink.create('https://developers.google.com/web/tools/chrome-devtools/'+article,title);}
  241. export function loadImage(url){return new Promise(fulfill=>{const image=new Image();image.addEventListener('load',()=>fulfill(image));image.addEventListener('error',()=>fulfill(null));image.src=url;});}
  242. export function loadImageFromData(data){return data?loadImage('data:image/jpg;base64,'+data):Promise.resolve(null);}
  243. export function createFileSelectorElement(callback){const fileSelectorElement=createElement('input');fileSelectorElement.type='file';fileSelectorElement.style.display='none';fileSelectorElement.setAttribute('tabindex',-1);fileSelectorElement.onchange=onChange;function onChange(event){callback(fileSelectorElement.files[0]);}
  244. return fileSelectorElement;}
  245. export const MaxLengthForDisplayedURLs=150;export class MessageDialog{static async show(message,where){const dialog=new UI.Dialog();dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.MeasureContent);dialog.setDimmed(true);const shadowRoot=createShadowRootWithCoreStyles(dialog.contentElement,'ui/confirmDialog.css');const content=shadowRoot.createChild('div','widget');await new Promise(resolve=>{const okButton=createTextButton(Common.UIString('OK'),resolve,'',true);content.createChild('div','message').createChild('span').textContent=message;content.createChild('div','button').appendChild(okButton);dialog.setOutsideClickCallback(event=>{event.consume();resolve();});dialog.show(where);okButton.focus();});dialog.hide();}}
  246. export class ConfirmDialog{static async show(message,where){const dialog=new UI.Dialog();dialog.setSizeBehavior(UI.GlassPane.SizeBehavior.MeasureContent);dialog.setDimmed(true);const shadowRoot=createShadowRootWithCoreStyles(dialog.contentElement,'ui/confirmDialog.css');const content=shadowRoot.createChild('div','widget');content.createChild('div','message').createChild('span').textContent=message;const buttonsBar=content.createChild('div','button');const result=await new Promise(resolve=>{buttonsBar.appendChild(createTextButton(Common.UIString('OK'),()=>resolve(true),'',true));buttonsBar.appendChild(createTextButton(Common.UIString('Cancel'),()=>resolve(false)));dialog.setOutsideClickCallback(event=>{event.consume();resolve(false);});dialog.show(where);});dialog.hide();return result;}}
  247. export function createInlineButton(toolbarButton){const element=createElement('span');const shadowRoot=createShadowRootWithCoreStyles(element,'ui/inlineButton.css');element.classList.add('inline-button');const toolbar=new UI.Toolbar('');toolbar.appendToolbarItem(toolbarButton);shadowRoot.appendChild(toolbar.element);return element;}
  248. export function createExpandableText(text,maxLength){const clickHandler=()=>{if(expandElement.parentElement){expandElement.parentElement.insertBefore(createTextNode(text.slice(maxLength)),expandElement);}
  249. expandElement.remove();};const fragment=createDocumentFragment();fragment.textContent=text.slice(0,maxLength);const expandElement=fragment.createChild('span');const totalBytes=Number.bytesToString(2*text.length);if(text.length<10000000){expandElement.setAttribute('data-text',ls`Show more (${totalBytes})`);expandElement.classList.add('expandable-inline-button');expandElement.addEventListener('click',clickHandler);expandElement.addEventListener('keydown',event=>{if(event.key==='Enter'||event.key===' '){clickHandler();}});UI.ARIAUtils.markAsButton(expandElement);}else{expandElement.setAttribute('data-text',ls`long text was truncated (${totalBytes})`);expandElement.classList.add('undisplayable-text');}
  250. const copyButton=fragment.createChild('span','expandable-inline-button');copyButton.setAttribute('data-text',ls`Copy`);copyButton.addEventListener('click',()=>{Host.InspectorFrontendHost.copyText(text);});copyButton.addEventListener('keydown',event=>{if(event.key==='Enter'||event.key===' '){Host.InspectorFrontendHost.copyText(text);}});UI.ARIAUtils.markAsButton(copyButton);return fragment;}
  251. export class Renderer{render(object,options){}}
  252. Renderer.render=async function(object,options){if(!object){throw new Error('Can\'t render '+object);}
  253. const renderer=await self.runtime.extension(Renderer,object).instance();return renderer?renderer.render(object,options||{}):null;};export function formatTimestamp(timestamp,full){const date=new Date(timestamp);const yymmdd=date.getFullYear()+'-'+leadZero(date.getMonth()+1,2)+'-'+leadZero(date.getDate(),2);const hhmmssfff=leadZero(date.getHours(),2)+':'+leadZero(date.getMinutes(),2)+':'+
  254. leadZero(date.getSeconds(),2)+'.'+leadZero(date.getMilliseconds(),3);return full?(yymmdd+' '+hhmmssfff):hhmmssfff;function leadZero(value,length){const valueString=String(value);return valueString.padStart(length,'0');}}
  255. self.UI=self.UI||{};UI=UI||{};UI.themeSupport;UI.highlightedSearchResultClassName=highlightedSearchResultClassName;UI.highlightedCurrentSearchResultClassName=highlightedCurrentSearchResultClassName;UI.StyleValueDelimiters=StyleValueDelimiters;UI.MaxLengthForDisplayedURLs=MaxLengthForDisplayedURLs;UI.ElementFocusRestorer=ElementFocusRestorer;UI.LongClickController=LongClickController;UI.ThemeSupport=ThemeSupport;UI.MessageDialog=MessageDialog;UI.ConfirmDialog=ConfirmDialog;UI.CheckboxLabel=CheckboxLabel;UI.Renderer=Renderer;UI.Renderer.Options;UI.installDragHandle=installDragHandle;UI.elementDragStart=elementDragStart;UI.isBeingEdited=isBeingEdited;UI.isEditing=isEditing;UI.markBeingEdited=markBeingEdited;UI.createReplacementString=createReplacementString;UI.handleElementValueModifications=handleElementValueModifications;UI.formatLocalized=formatLocalized;UI.openLinkExternallyLabel=openLinkExternallyLabel;UI.copyLinkAddressLabel=copyLinkAddressLabel;UI.anotherProfilerActiveLabel=anotherProfilerActiveLabel;UI.asyncStackTraceLabel=asyncStackTraceLabel;UI.installComponentRootStyles=installComponentRootStyles;UI.measuredScrollbarWidth=measuredScrollbarWidth;UI.createShadowRootWithCoreStyles=createShadowRootWithCoreStyles;UI.highlightSearchResult=highlightSearchResult;UI.highlightSearchResults=highlightSearchResults;UI.runCSSAnimationOnce=runCSSAnimationOnce;UI.highlightRangesWithStyleClass=highlightRangesWithStyleClass;UI.applyDomChanges=applyDomChanges;UI.revertDomChanges=revertDomChanges;UI.measurePreferredSize=measurePreferredSize;UI.startBatchUpdate=startBatchUpdate;UI.endBatchUpdate=endBatchUpdate;UI.invokeOnceAfterBatchUpdate=invokeOnceAfterBatchUpdate;UI.animateFunction=animateFunction;UI.initializeUIUtils=initializeUIUtils;UI.beautifyFunctionName=beautifyFunctionName;UI.registerCustomElement=registerCustomElement;UI.createTextButton=createTextButton;UI.createInput=createInput;UI.createLabel=createLabel;UI.createRadioLabel=createRadioLabel;UI.createIconLabel=createIconLabel;UI.createSlider=createSlider;UI.appendStyle=appendStyle;UI.bindInput=bindInput;UI.trimText=trimText;UI.trimTextMiddle=trimTextMiddle;UI.trimTextEnd=trimTextEnd;UI.measureTextWidth=measureTextWidth;UI.createDocumentationLink=createDocumentationLink;UI.loadImage=loadImage;UI.loadImageFromData=loadImageFromData;UI.createFileSelectorElement=createFileSelectorElement;UI.createInlineButton=createInlineButton;UI.createExpandableText=createExpandableText;UI.formatTimestamp=formatTimestamp;