| /* | 
 |  * Copyright (C) 2017 Apple Inc. All rights reserved. | 
 |  * | 
 |  * Redistribution and use in source and binary forms, with or without | 
 |  * modification, are permitted provided that the following conditions | 
 |  * are met: | 
 |  * 1. Redistributions of source code must retain the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer. | 
 |  * 2. Redistributions in binary form must reproduce the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer in the | 
 |  *    documentation and/or other materials provided with the distribution. | 
 |  * | 
 |  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' | 
 |  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | 
 |  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 
 |  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS | 
 |  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
 |  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
 |  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
 |  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
 |  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
 |  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | 
 |  * THE POSSIBILITY OF SUCH DAMAGE. | 
 |  */ | 
 |  | 
 | WI.ResourceHeadersContentView = class ResourceHeadersContentView extends WI.ContentView | 
 | { | 
 |     constructor(resource, delegate) | 
 |     { | 
 |         super(null); | 
 |  | 
 |         console.assert(resource instanceof WI.Resource); | 
 |         console.assert(delegate); | 
 |  | 
 |         this._resource = resource; | 
 |         this._resource.addEventListener(WI.Resource.Event.MetricsDidChange, this._resourceMetricsDidChange, this); | 
 |         this._resource.addEventListener(WI.Resource.Event.RequestHeadersDidChange, this._resourceRequestHeadersDidChange, this); | 
 |         this._resource.addEventListener(WI.Resource.Event.ResponseReceived, this._resourceResponseReceived, this); | 
 |  | 
 |         this._delegate = delegate; | 
 |  | 
 |         this._searchQuery = null; | 
 |         this._searchResults = null; | 
 |         this._searchDOMChanges = []; | 
 |         this._searchIndex = -1; | 
 |         this._automaticallyRevealFirstSearchResult = false; | 
 |         this._bouncyHighlightElement = null; | 
 |         this._popover = null; | 
 |         this._popoverCallStackIconElement = null; | 
 |  | 
 |         this._redirectDetailsSections = []; | 
 |  | 
 |         this.element.classList.add("resource-details", "resource-headers"); | 
 |         this.element.tabIndex = 0; | 
 |  | 
 |         this._needsSummaryRefresh = false; | 
 |         this._needsRedirectHeadersRefresh = false; | 
 |         this._needsRequestHeadersRefresh = false; | 
 |         this._needsResponseHeadersRefresh = false; | 
 |     } | 
 |  | 
 |     // Protected | 
 |  | 
 |     initialLayout() | 
 |     { | 
 |         super.initialLayout(); | 
 |  | 
 |         this._summarySection = new WI.ResourceDetailsSection(WI.UIString("Summary"), "summary"); | 
 |         this.element.appendChild(this._summarySection.element); | 
 |         this._refreshSummarySection(); | 
 |  | 
 |         this._refreshRedirectHeadersSections(); | 
 |  | 
 |         this._requestHeadersSection = new WI.ResourceDetailsSection(WI.UIString("Request"), "headers"); | 
 |         this.element.appendChild(this._requestHeadersSection.element); | 
 |         this._refreshRequestHeadersSection(); | 
 |  | 
 |         this._responseHeadersSection = new WI.ResourceDetailsSection(WI.UIString("Response"), "headers"); | 
 |         this.element.appendChild(this._responseHeadersSection.element); | 
 |         this._refreshResponseHeadersSection(); | 
 |  | 
 |         if (this._resource.urlComponents.queryString) { | 
 |             this._queryStringSection = new WI.ResourceDetailsSection(WI.UIString("Query String Parameters")); | 
 |             this.element.appendChild(this._queryStringSection.element); | 
 |             this._refreshQueryStringSection(); | 
 |         } | 
 |  | 
 |         if (this._resource.requestData) { | 
 |             this._requestDataSection = new WI.ResourceDetailsSection(WI.UIString("Request Data")); | 
 |             this.element.appendChild(this._requestDataSection.element); | 
 |             this._refreshRequestDataSection(); | 
 |         } | 
 |  | 
 |         this._needsSummaryRefresh = false; | 
 |         this._needsRedirectHeadersRefresh = false; | 
 |         this._needsRequestHeadersRefresh = false; | 
 |         this._needsResponseHeadersRefresh = false; | 
 |     } | 
 |  | 
 |     layout() | 
 |     { | 
 |         super.layout(); | 
 |  | 
 |         if (this._needsSummaryRefresh) { | 
 |             this._refreshSummarySection(); | 
 |             this._needsSummaryRefresh = false; | 
 |         } | 
 |  | 
 |         if (this._needsRedirectHeadersRefresh) { | 
 |             this._refreshRedirectHeadersSections(); | 
 |             this._needsRedirectHeadersRefresh = false; | 
 |         } | 
 |  | 
 |         if (this._needsRequestHeadersRefresh) { | 
 |             this._refreshRequestHeadersSection(); | 
 |             this._needsRequestHeadersRefresh = false; | 
 |         } | 
 |  | 
 |         if (this._needsResponseHeadersRefresh) { | 
 |             this._refreshResponseHeadersSection(); | 
 |             this._needsResponseHeadersRefresh = false; | 
 |         } | 
 |     } | 
 |  | 
 |     detached() | 
 |     { | 
 |         if (this._popover) | 
 |             this._popover.dismiss(); | 
 |  | 
 |         super.detached(); | 
 |     } | 
 |  | 
 |     closed() | 
 |     { | 
 |         this._resource.removeEventListener(WI.Resource.Event.MetricsDidChange, this._resourceMetricsDidChange, this); | 
 |         this._resource.removeEventListener(WI.Resource.Event.RequestHeadersDidChange, this._resourceRequestHeadersDidChange, this); | 
 |         this._resource.removeEventListener(WI.Resource.Event.ResponseReceived, this._resourceResponseReceived, this); | 
 |  | 
 |         super.closed(); | 
 |     } | 
 |  | 
 |     get supportsSearch() | 
 |     { | 
 |         return true; | 
 |     } | 
 |  | 
 |     get numberOfSearchResults() | 
 |     { | 
 |         return this._searchResults ? this._searchResults.length : null; | 
 |     } | 
 |  | 
 |     get hasPerformedSearch() | 
 |     { | 
 |         return this._searchResults !== null; | 
 |     } | 
 |  | 
 |     set automaticallyRevealFirstSearchResult(reveal) | 
 |     { | 
 |         this._automaticallyRevealFirstSearchResult = reveal; | 
 |  | 
 |         // If we haven't shown a search result yet, reveal one now. | 
 |         if (this._automaticallyRevealFirstSearchResult && this.numberOfSearchResults > 0) { | 
 |             if (this._searchIndex === -1) | 
 |                 this.revealNextSearchResult(); | 
 |         } | 
 |     } | 
 |  | 
 |     performSearch(query) | 
 |     { | 
 |         if (query === this._searchQuery) | 
 |             return; | 
 |  | 
 |         WI.revertDOMChanges(this._searchDOMChanges); | 
 |  | 
 |         this._searchQuery = query; | 
 |         this._searchResults = []; | 
 |         this._searchDOMChanges = []; | 
 |         this._searchIndex = -1; | 
 |  | 
 |         this._perfomSearchOnKeyValuePairs(); | 
 |  | 
 |         this.dispatchEventToListeners(WI.ContentView.Event.NumberOfSearchResultsDidChange); | 
 |  | 
 |         if (this._automaticallyRevealFirstSearchResult && this._searchResults.length > 0) | 
 |             this.revealNextSearchResult(); | 
 |     } | 
 |  | 
 |     searchCleared() | 
 |     { | 
 |         WI.revertDOMChanges(this._searchDOMChanges); | 
 |  | 
 |         this._searchQuery = null; | 
 |         this._searchResults = null; | 
 |         this._searchDOMChanges = []; | 
 |         this._searchIndex = -1; | 
 |     } | 
 |  | 
 |     revealPreviousSearchResult(changeFocus) | 
 |     { | 
 |         if (!this.numberOfSearchResults) | 
 |             return; | 
 |  | 
 |         if (this._searchIndex > 0) | 
 |             --this._searchIndex; | 
 |         else | 
 |             this._searchIndex = this._searchResults.length - 1; | 
 |  | 
 |         this._revealSearchResult(this._searchIndex, changeFocus); | 
 |     } | 
 |  | 
 |     revealNextSearchResult(changeFocus) | 
 |     { | 
 |         if (!this.numberOfSearchResults) | 
 |             return; | 
 |  | 
 |         if (this._searchIndex + 1 < this._searchResults.length) | 
 |             ++this._searchIndex; | 
 |         else | 
 |             this._searchIndex = 0; | 
 |  | 
 |         this._revealSearchResult(this._searchIndex, changeFocus); | 
 |     } | 
 |  | 
 |     // Private | 
 |  | 
 |     _responseSourceDisplayString(responseSource) | 
 |     { | 
 |         switch (responseSource) { | 
 |         case WI.Resource.ResponseSource.Network: | 
 |             return WI.UIString("Network"); | 
 |         case WI.Resource.ResponseSource.MemoryCache: | 
 |             return WI.UIString("Memory Cache"); | 
 |         case WI.Resource.ResponseSource.DiskCache: | 
 |             return WI.UIString("Disk Cache"); | 
 |         case WI.Resource.ResponseSource.ServiceWorker: | 
 |             return WI.UIString("Service Worker"); | 
 |         case WI.Resource.ResponseSource.InspectorOverride: | 
 |             return WI.UIString("Local Override"); | 
 |         case WI.Resource.ResponseSource.Unknown: | 
 |         default: | 
 |             return null; | 
 |         } | 
 |     } | 
 |  | 
 |     _refreshSummarySection() | 
 |     { | 
 |         let detailsElement = this._summarySection.detailsElement; | 
 |         detailsElement.removeChildren(); | 
 |  | 
 |         this._summarySection.toggleError(this._resource.hadLoadingError()); | 
 |  | 
 |         for (let redirect of this._resource.redirects) | 
 |             this._summarySection.appendKeyValuePair(WI.UIString("URL"), redirect.url.insertWordBreakCharacters(), "url"); | 
 |         this._summarySection.appendKeyValuePair(WI.UIString("URL"), this._resource.displayURL.insertWordBreakCharacters(), "url"); | 
 |  | 
 |         let status = emDash; | 
 |         if (!isNaN(this._resource.statusCode)) | 
 |             status = this._resource.statusCode + (this._resource.statusText ? " " + this._resource.statusText : ""); | 
 |         this._summarySection.appendKeyValuePair(WI.UIString("Status"), status); | 
 |  | 
 |         // FIXME: <https://webkit.org/b/178827> Web Inspector: Should be able to link directly to the ServiceWorker that handled a particular load | 
 |  | 
 |         let source = this._responseSourceDisplayString(this._resource.responseSource) || emDash; | 
 |         this._summarySection.appendKeyValuePair(WI.UIString("Source"), source); | 
 |  | 
 |         if (this._resource.remoteAddress) | 
 |             this._summarySection.appendKeyValuePair(WI.UIString("Address"), this._resource.remoteAddress); | 
 |  | 
 |         let initiatorLocation = this._resource.initiatorSourceCodeLocation; | 
 |         if (initiatorLocation) { | 
 |  | 
 |             let fragment = document.createDocumentFragment(); | 
 |  | 
 |             const options = { | 
 |                 dontFloat: true, | 
 |                 ignoreSearchTab: true, | 
 |                 ignoreNetworkTab: true, | 
 |             }; | 
 |             let link = WI.createSourceCodeLocationLink(initiatorLocation, options); | 
 |             fragment.appendChild(link); | 
 |  | 
 |             let callFrames = this._resource.initiatorCallFrames; | 
 |             if (callFrames) { | 
 |                 this._popoverCallStackIconElement = document.createElement("img"); | 
 |                 this._popoverCallStackIconElement.className = "call-stack"; | 
 |                 fragment.appendChild(this._popoverCallStackIconElement); | 
 |  | 
 |                 this._popoverCallStackIconElement.addEventListener("click", (event) => { | 
 |                     if (!this._popover) { | 
 |                         this._popover = new WI.Popover(this); | 
 |                         this._popover.windowResizeHandler = () => { this._presentPopoverBelowCallStackElement(); }; | 
 |                     } | 
 |  | 
 |                     const selectable = false; | 
 |                     let callFramesTreeOutline = new WI.TreeOutline(selectable); | 
 |                     callFramesTreeOutline.disclosureButtons = false; | 
 |                     let callFrameTreeController = new WI.CallFrameTreeController(callFramesTreeOutline); | 
 |                     callFrameTreeController.callFrames = callFrames; | 
 |  | 
 |                     let popoverContent = document.createElement("div"); | 
 |                     popoverContent.appendChild(callFrameTreeController.treeOutline.element); | 
 |                     this._popover.content = popoverContent; | 
 |  | 
 |                     this._presentPopoverBelowCallStackElement(); | 
 |                 }); | 
 |             } | 
 |  | 
 |             let pair = this._summarySection.appendKeyValuePair(WI.UIString("Initiator"), fragment); | 
 |             pair.classList.add("initiator"); | 
 |  | 
 |             if (this._popover && this._popover.visible) | 
 |                 this._presentPopoverBelowCallStackElement(); | 
 |         } | 
 |     } | 
 |  | 
 |     _refreshRedirectHeadersSections() | 
 |     { | 
 |         let referenceElement = this._redirectDetailsSections.length ? this._redirectDetailsSections.lastValue.element : this._summarySection.element; | 
 |  | 
 |         for (let i = this._redirectDetailsSections.length; i < this._resource.redirects.length; ++i) { | 
 |             let redirect = this._resource.redirects[i]; | 
 |  | 
 |             let redirectRequestSection = new WI.ResourceDetailsSection(WI.UIString("Request"), "redirect"); | 
 |  | 
 |             // FIXME: <https://webkit.org/b/190214> Web Inspector: expose full load metrics for redirect requests | 
 |             redirectRequestSection.appendKeyValuePair(`${redirect.requestMethod} ${redirect.urlComponents.path}`, null, "h1-status"); | 
 |  | 
 |             for (let key in redirect.requestHeaders) | 
 |                 redirectRequestSection.appendKeyValuePair(key, redirect.requestHeaders[key], "header"); | 
 |  | 
 |             referenceElement = this.element.insertBefore(redirectRequestSection.element, referenceElement.nextElementSibling); | 
 |             this._redirectDetailsSections.push(redirectRequestSection); | 
 |  | 
 |             let redirectResponseSection = new WI.ResourceDetailsSection(WI.UIString("Redirect Response"), "redirect"); | 
 |  | 
 |             // FIXME: <https://webkit.org/b/190214> Web Inspector: expose full load metrics for redirect requests | 
 |             redirectResponseSection.appendKeyValuePair(`${redirect.responseStatusCode} ${redirect.responseStatusText}`, null, "h1-status"); | 
 |  | 
 |             for (let key in redirect.responseHeaders) | 
 |                 redirectResponseSection.appendKeyValuePair(key, redirect.responseHeaders[key], "header"); | 
 |  | 
 |             referenceElement = this.element.insertBefore(redirectResponseSection.element, referenceElement.nextElementSibling); | 
 |             this._redirectDetailsSections.push(redirectResponseSection); | 
 |         } | 
 |     } | 
 |  | 
 |     _refreshRequestHeadersSection() | 
 |     { | 
 |         let detailsElement = this._requestHeadersSection.detailsElement; | 
 |         detailsElement.removeChildren(); | 
 |  | 
 |         // A revalidation request still sends a request even though we served from cache, so show the request. | 
 |         if (this._resource.statusCode !== 304) { | 
 |             if (this._resource.responseSource === WI.Resource.ResponseSource.MemoryCache) { | 
 |                 this._requestHeadersSection.markIncompleteSectionWithMessage(WI.UIString("No request, served from the memory cache.")); | 
 |                 return; | 
 |             } | 
 |             if (this._resource.responseSource === WI.Resource.ResponseSource.DiskCache) { | 
 |                 this._requestHeadersSection.markIncompleteSectionWithMessage(WI.UIString("No request, served from the disk cache.")); | 
 |                 return; | 
 |             } | 
 |         } | 
 |  | 
 |         let protocol = this._resource.protocol || ""; | 
 |         let urlComponents = this._resource.urlComponents; | 
 |         if (protocol.startsWith("http/1")) { | 
 |             // HTTP/1.1 request line: | 
 |             // https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1 | 
 |             let requestLine = `${this._resource.requestMethod} ${urlComponents.path} ${protocol.toUpperCase()}`; | 
 |             this._requestHeadersSection.appendKeyValuePair(requestLine, null, "h1-status"); | 
 |         } else if (protocol === "h2") { | 
 |             // HTTP/2 Request pseudo headers: | 
 |             // https://tools.ietf.org/html/rfc7540#section-8.1.2.3 | 
 |             this._requestHeadersSection.appendKeyValuePair(":method", this._resource.requestMethod, "h2-pseudo-header"); | 
 |             this._requestHeadersSection.appendKeyValuePair(":scheme", urlComponents.scheme, "h2-pseudo-header"); | 
 |             this._requestHeadersSection.appendKeyValuePair(":authority", WI.h2Authority(urlComponents), "h2-pseudo-header"); | 
 |             this._requestHeadersSection.appendKeyValuePair(":path", WI.h2Path(urlComponents), "h2-pseudo-header"); | 
 |         } | 
 |  | 
 |         let requestHeaders = this._resource.requestHeaders; | 
 |         for (let key in requestHeaders) | 
 |             this._requestHeadersSection.appendKeyValuePair(key, requestHeaders[key], "header"); | 
 |  | 
 |         if (!detailsElement.firstChild) | 
 |             this._requestHeadersSection.markIncompleteSectionWithMessage(WI.UIString("No request headers")); | 
 |     } | 
 |  | 
 |     _refreshResponseHeadersSection() | 
 |     { | 
 |         let detailsElement = this._responseHeadersSection.detailsElement; | 
 |         detailsElement.removeChildren(); | 
 |  | 
 |         if (!this._resource.hasResponse()) { | 
 |             this._responseHeadersSection.markIncompleteSectionWithLoadingIndicator(); | 
 |             return; | 
 |         } | 
 |  | 
 |         this._responseHeadersSection.toggleIncomplete(false); | 
 |  | 
 |         let protocol = this._resource.protocol || ""; | 
 |         if (protocol.startsWith("http/1")) { | 
 |             // HTTP/1.1 response status line: | 
 |             // https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1 | 
 |             let responseLine = `${protocol.toUpperCase()} ${this._resource.statusCode} ${this._resource.statusText}`; | 
 |             this._responseHeadersSection.appendKeyValuePair(responseLine, null, "h1-status"); | 
 |         } else if (protocol === "h2") { | 
 |             // HTTP/2 Response pseudo headers: | 
 |             // https://tools.ietf.org/html/rfc7540#section-8.1.2.4 | 
 |             this._responseHeadersSection.appendKeyValuePair(":status", this._resource.statusCode, "h2-pseudo-header"); | 
 |         } | 
 |  | 
 |         let responseHeaders = this._resource.responseHeaders; | 
 |         for (let key in responseHeaders) { | 
 |             // Split multiple Set-Cookie response headers out into their multiple headers instead of as a combined value. | 
 |             if (key.toLowerCase() === "set-cookie") { | 
 |                 let responseCookies = this._resource.responseCookies; | 
 |                 console.assert(responseCookies.length > 0); | 
 |                 for (let cookie of responseCookies) | 
 |                     this._responseHeadersSection.appendKeyValuePair(key, cookie.header, "header"); | 
 |                 continue; | 
 |             } | 
 |  | 
 |             this._responseHeadersSection.appendKeyValuePair(key, responseHeaders[key], "header"); | 
 |         } | 
 |  | 
 |         if (!detailsElement.firstChild) | 
 |             this._responseHeadersSection.markIncompleteSectionWithMessage(WI.UIString("No response headers")); | 
 |     } | 
 |  | 
 |     _refreshQueryStringSection() | 
 |     { | 
 |         if (!this._queryStringSection) | 
 |             return; | 
 |  | 
 |         let detailsElement = this._queryStringSection.detailsElement; | 
 |         detailsElement.removeChildren(); | 
 |  | 
 |         let queryString = this._resource.urlComponents.queryString; | 
 |         let queryStringPairs = parseQueryString(queryString, true); | 
 |         for (let {name, value} of queryStringPairs) | 
 |             this._queryStringSection.appendKeyValuePair(name, value); | 
 |     } | 
 |  | 
 |     _refreshRequestDataSection() | 
 |     { | 
 |         if (!this._requestDataSection) | 
 |             return; | 
 |  | 
 |         let detailsElement = this._requestDataSection.detailsElement; | 
 |         detailsElement.removeChildren(); | 
 |  | 
 |         let requestData = this._resource.requestData; | 
 |         let requestDataContentType = this._resource.requestDataContentType || ""; | 
 |  | 
 |         if (requestDataContentType && requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) { | 
 |             // Simple form data that should be parsable like a query string. | 
 |             this._requestDataSection.appendKeyValuePair(WI.UIString("MIME Type"), requestDataContentType); | 
 |             let queryStringPairs = parseQueryString(requestData, true); | 
 |             for (let {name, value} of queryStringPairs) | 
 |                 this._requestDataSection.appendKeyValuePair(name, value); | 
 |             return; | 
 |         } | 
 |  | 
 |         let mimeTypeComponents = parseMIMEType(requestDataContentType); | 
 |         let mimeType = mimeTypeComponents.type; | 
 |         let boundary = mimeTypeComponents.boundary; | 
 |         let encoding = mimeTypeComponents.encoding; | 
 |  | 
 |         this._requestDataSection.appendKeyValuePair(WI.UIString("MIME Type"), mimeType); | 
 |         if (boundary) | 
 |             this._requestDataSection.appendKeyValuePair(WI.UIString("Boundary"), boundary); | 
 |         if (encoding) | 
 |             this._requestDataSection.appendKeyValuePair(WI.UIString("Encoding"), encoding); | 
 |  | 
 |         let goToButton = detailsElement.appendChild(WI.createGoToArrowButton()); | 
 |         goToButton.addEventListener("click", () => { this._delegate.headersContentViewGoToRequestData(this); }); | 
 |         this._requestDataSection.appendKeyValuePair(WI.UIString("Request Data"), goToButton); | 
 |     } | 
 |  | 
 |     _perfomSearchOnKeyValuePairs() | 
 |     { | 
 |         let searchRegex = WI.SearchUtilities.searchRegExpForString(this._searchQuery, WI.SearchUtilities.defaultSettings); | 
 |         if (!searchRegex) { | 
 |             this.searchCleared(); | 
 |             this.dispatchEventToListeners(WI.TextEditor.Event.NumberOfSearchResultsDidChange); | 
 |             return; | 
 |         } | 
 |  | 
 |         let elements = this.element.querySelectorAll(".key, .value"); | 
 |         for (let element of elements) { | 
 |             let matchRanges = []; | 
 |             let text = element.textContent; | 
 |             let match; | 
 |             while (match = searchRegex.exec(text)) | 
 |                 matchRanges.push({offset: match.index, length: match[0].length}); | 
 |  | 
 |             if (matchRanges.length) { | 
 |                 let highlightedNodes = WI.highlightRangesWithStyleClass(element, matchRanges, "search-highlight", this._searchDOMChanges); | 
 |                 this._searchResults.pushAll(highlightedNodes); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     _revealSearchResult(index, changeFocus) | 
 |     { | 
 |         let highlightElement = this._searchResults[index]; | 
 |         if (!highlightElement) | 
 |             return; | 
 |  | 
 |         highlightElement.scrollIntoViewIfNeeded(); | 
 |  | 
 |         if (!this._bouncyHighlightElement) { | 
 |             this._bouncyHighlightElement = document.createElement("div"); | 
 |             this._bouncyHighlightElement.className = "bouncy-highlight"; | 
 |             this._bouncyHighlightElement.addEventListener("animationend", (event) => { | 
 |                 this._bouncyHighlightElement.remove(); | 
 |             }); | 
 |         } | 
 |  | 
 |         this._bouncyHighlightElement.remove(); | 
 |  | 
 |         let computedStyles = window.getComputedStyle(highlightElement); | 
 |         let highlightElementRect = highlightElement.getBoundingClientRect(); | 
 |         let contentViewRect = this.element.getBoundingClientRect(); | 
 |         let contentViewScrollTop = this.element.scrollTop; | 
 |         let contentViewScrollLeft = this.element.scrollLeft; | 
 |  | 
 |         this._bouncyHighlightElement.textContent = highlightElement.textContent; | 
 |         this._bouncyHighlightElement.style.top = (highlightElementRect.top - contentViewRect.top + contentViewScrollTop) + "px"; | 
 |         this._bouncyHighlightElement.style.left = (highlightElementRect.left - contentViewRect.left + contentViewScrollLeft) + "px"; | 
 |         this._bouncyHighlightElement.style.fontWeight = computedStyles.fontWeight; | 
 |  | 
 |         this.element.appendChild(this._bouncyHighlightElement); | 
 |     } | 
 |  | 
 |     _presentPopoverBelowCallStackElement() | 
 |     { | 
 |         let bounds = WI.Rect.rectFromClientRect(this._popoverCallStackIconElement.getBoundingClientRect()); | 
 |         this._popover.present(bounds.pad(2), [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MAX_X]); | 
 |     } | 
 |  | 
 |     _resourceMetricsDidChange(event) | 
 |     { | 
 |         this._needsSummaryRefresh = true; | 
 |         this._needsRequestHeadersRefresh = true; | 
 |         this._needsResponseHeadersRefresh = true; | 
 |         this.needsLayout(); | 
 |     } | 
 |  | 
 |     _resourceRequestHeadersDidChange(event) | 
 |     { | 
 |         this._needsSummaryRefresh = true; | 
 |         this._needsRedirectHeadersRefresh = true; | 
 |         this._needsRequestHeadersRefresh = true; | 
 |         this.needsLayout(); | 
 |     } | 
 |  | 
 |     _resourceResponseReceived(event) | 
 |     { | 
 |         this._needsSummaryRefresh = true; | 
 |         this._needsResponseHeadersRefresh = true; | 
 |         this.needsLayout(); | 
 |     } | 
 | }; |