UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

408 lines (365 loc) 13.6 kB
/* * Copyright (C) 2012 Google 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER OR 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. */ // See http://www.softwareishard.com/blog/har-12-spec/ // for HAR specification. // FIXME: Some fields are not yet supported due to back-end limitations. // See https://bugs.webkit.org/show_bug.cgi?id=58127 for details. /** * @unrestricted */ BrowserSDK.HAREntry = class { /** * @param {!SDK.NetworkRequest} request */ constructor(request) { this._request = request; } /** * @param {number} time * @return {number} */ static _toMilliseconds(time) { return time === -1 ? -1 : time * 1000; } /** * @param {!SDK.NetworkRequest} request * @return {!Promise<!Object>} */ static async build(request) { const harEntry = new BrowserSDK.HAREntry(request); let ipAddress = harEntry._request.remoteAddress(); const portPositionInString = ipAddress.lastIndexOf(':'); if (portPositionInString !== -1) ipAddress = ipAddress.substr(0, portPositionInString); const timings = harEntry._buildTimings(); let time = 0; // "ssl" is included in the connect field, so do not double count it. for (const t of [timings.blocked, timings.dns, timings.connect, timings.send, timings.wait, timings.receive]) time += Math.max(t, 0); const entry = { startedDateTime: BrowserSDK.HARLog.pseudoWallTime(harEntry._request, harEntry._request.issueTime()).toJSON(), time: time, request: await harEntry._buildRequest(), response: harEntry._buildResponse(), cache: {}, // Not supported yet. timings: timings, // IPv6 address should not have square brackets per (https://tools.ietf.org/html/rfc2373#section-2.2). serverIPAddress: ipAddress.replace(/\[\]/g, '') }; // Chrome specific. if (harEntry._request.cached()) entry._fromCache = harEntry._request.cachedInMemory() ? 'memory' : 'disk'; if (harEntry._request.connectionId !== '0') entry.connection = harEntry._request.connectionId; const page = BrowserSDK.PageLoad.forRequest(harEntry._request); if (page) entry.pageref = 'page_' + page.id; return entry; } /** * @return {!Promise<!Object>} */ async _buildRequest() { const headersText = this._request.requestHeadersText(); const res = { method: this._request.requestMethod, url: this._buildRequestURL(this._request.url()), httpVersion: this._request.requestHttpVersion(), headers: this._request.requestHeaders(), queryString: this._buildParameters(this._request.queryParameters || []), cookies: this._buildCookies(this._request.requestCookies || []), headersSize: headersText ? headersText.length : -1, bodySize: this.requestBodySize }; const postData = await this._buildPostData(); if (postData) res.postData = postData; return res; } /** * @return {!Object} */ _buildResponse() { const headersText = this._request.responseHeadersText; return { status: this._request.statusCode, statusText: this._request.statusText, httpVersion: this._request.responseHttpVersion(), headers: this._request.responseHeaders, cookies: this._buildCookies(this._request.responseCookies || []), content: this._buildContent(), redirectURL: this._request.responseHeaderValue('Location') || '', headersSize: headersText ? headersText.length : -1, bodySize: this.responseBodySize, _transferSize: this._request.transferSize, _error: this._request.localizedFailDescription }; } /** * @return {!Object} */ _buildContent() { const content = { size: this._request.resourceSize, mimeType: this._request.mimeType || 'x-unknown', // text: this._request.content // TODO: pull out into a boolean flag, as content can be huge (and needs to be requested with an async call) }; const compression = this.responseCompression; if (typeof compression === 'number') content.compression = compression; return content; } /** * @return {!BrowserSDK.HAREntry.Timing} */ _buildTimings() { // Order of events: request_start = 0, [proxy], [dns], [connect [ssl]], [send], duration const timing = this._request.timing; const issueTime = this._request.issueTime(); const startTime = this._request.startTime; const result = {blocked: -1, dns: -1, ssl: -1, connect: -1, send: 0, wait: 0, receive: 0, _blocked_queueing: -1}; const queuedTime = (issueTime < startTime) ? startTime - issueTime : -1; result.blocked = queuedTime; result._blocked_queueing = BrowserSDK.HAREntry._toMilliseconds(queuedTime); let highestTime = 0; if (timing) { // "blocked" here represents both queued + blocked/stalled + proxy (ie: anything before request was started). // We pick the better of when the network request start was reported and pref timing. const blockedStart = leastNonNegative([timing.dnsStart, timing.connectStart, timing.sendStart]); if (blockedStart !== Infinity) result.blocked += blockedStart; // Proxy is part of blocked but sometimes (like quic) blocked is -1 but has proxy timings. if (timing.proxyEnd !== -1) result._blocked_proxy = timing.proxyEnd - timing.proxyStart; if (result._blocked_proxy && result._blocked_proxy > result.blocked) result.blocked = result._blocked_proxy; const dnsStart = timing.dnsEnd >= 0 ? blockedStart : 0; const dnsEnd = timing.dnsEnd >= 0 ? timing.dnsEnd : -1; result.dns = dnsEnd - dnsStart; // SSL timing is included in connection timing. const sslStart = timing.sslEnd > 0 ? timing.sslStart : 0; const sslEnd = timing.sslEnd > 0 ? timing.sslEnd : -1; result.ssl = sslEnd - sslStart; const connectStart = timing.connectEnd >= 0 ? leastNonNegative([dnsEnd, blockedStart]) : 0; const connectEnd = timing.connectEnd >= 0 ? timing.connectEnd : -1; result.connect = connectEnd - connectStart; // Send should not be -1 for legacy reasons even if it is served from cache. const sendStart = timing.sendEnd >= 0 ? Math.max(connectEnd, dnsEnd, blockedStart) : 0; const sendEnd = timing.sendEnd >= 0 ? timing.sendEnd : 0; result.send = sendEnd - sendStart; // Quic sometimes says that sendStart is before connectionEnd (see: crbug.com/740792) if (result.send < 0) result.send = 0; highestTime = Math.max(sendEnd, connectEnd, sslEnd, dnsEnd, blockedStart, 0); } else if (this._request.responseReceivedTime === -1) { // Means that we don't have any more details after blocked, so attribute all to blocked. result.blocked = this._request.endTime - issueTime; return result; } const requestTime = timing ? timing.requestTime : startTime; const waitStart = highestTime; const waitEnd = BrowserSDK.HAREntry._toMilliseconds(this._request.responseReceivedTime - requestTime); result.wait = waitEnd - waitStart; const receiveStart = waitEnd; const receiveEnd = BrowserSDK.HAREntry._toMilliseconds(this._request.endTime - issueTime); result.receive = Math.max(receiveEnd - receiveStart, 0); return result; /** * @param {!Array<number>} values * @return {number} */ function leastNonNegative(values) { return values.reduce((best, value) => (value >= 0 && value < best) ? value : best, Infinity); } } /** * @return {!Promise<!Object>} */ async _buildPostData() { const postData = await this._request.requestFormData(); if (!postData) return null; const res = {mimeType: this._request.requestContentType(), text: postData}; const formParameters = await this._request.formParameters(); if (formParameters) res.params = this._buildParameters(formParameters); return res; } /** * @param {!Array.<!Object>} parameters * @return {!Array.<!Object>} */ _buildParameters(parameters) { return parameters.slice(); } /** * @param {string} url * @return {string} */ _buildRequestURL(url) { return url.split('#', 2)[0]; } /** * @param {!Array.<!SDK.Cookie>} cookies * @return {!Array.<!Object>} */ _buildCookies(cookies) { return cookies.map(this._buildCookie.bind(this)); } /** * @param {!SDK.Cookie} cookie * @return {!Object} */ _buildCookie(cookie) { const c = { name: cookie.name(), value: cookie.value(), path: cookie.path(), domain: cookie.domain(), expires: cookie.expiresDate(BrowserSDK.HARLog.pseudoWallTime(this._request, this._request.startTime)), httpOnly: cookie.httpOnly(), secure: cookie.secure() }; if (cookie.sameSite()) c.sameSite = cookie.sameSite(); return c; } /** * @return {number} */ get requestBodySize() { return !this._request.requestFormData ? 0 : this._request.requestFormData.length; } /** * @return {number} */ get responseBodySize() { if (this._request.cached() || this._request.statusCode === 304) return 0; if (!this._request.responseHeadersText) return -1; return this._request.transferSize - this._request.responseHeadersText.length; } /** * @return {number|undefined} */ get responseCompression() { if (this._request.cached() || this._request.statusCode === 304 || this._request.statusCode === 206) return; if (!this._request.responseHeadersText) return; return this._request.resourceSize - this.responseBodySize; } }; /** @typedef {!{ blocked: number, dns: number, ssl: number, connect: number, send: number, wait: number, receive: number, _blocked_queueing: number, _blocked_proxy: (number|undefined) }} */ BrowserSDK.HAREntry.Timing; /** * @unrestricted */ BrowserSDK.HARLog = class { /** * @param {!SDK.NetworkRequest} request * @param {number} monotonicTime * @return {!Date} */ static pseudoWallTime(request, monotonicTime) { return new Date(request.pseudoWallTime(monotonicTime) * 1000); } /** * @param {!Array.<!SDK.NetworkRequest>} requests * @return {!Promise<!Object>} */ static async build(requests) { const log = new BrowserSDK.HARLog(); const entryPromises = []; for (const request of requests) entryPromises.push(BrowserSDK.HAREntry.build(request)); const entries = await Promise.all(entryPromises); return {version: '1.2', creator: log._creator(), pages: log._buildPages(requests), entries: entries}; } _creator() { const webKitVersion = /AppleWebKit\/([^ ]+)/.exec(window.navigator.userAgent); return {name: 'WebInspector', version: webKitVersion ? webKitVersion[1] : 'n/a'}; } /** * @param {!Array.<!SDK.NetworkRequest>} requests * @return {!Array.<!Object>} */ _buildPages(requests) { const seenIdentifiers = {}; const pages = []; for (let i = 0; i < requests.length; ++i) { const request = requests[i]; const page = BrowserSDK.PageLoad.forRequest(request); if (!page || seenIdentifiers[page.id]) continue; seenIdentifiers[page.id] = true; pages.push(this._convertPage(page, request)); } return pages; } /** * @param {!BrowserSDK.PageLoad} page * @param {!SDK.NetworkRequest} request * @return {!Object} */ _convertPage(page, request) { return { startedDateTime: BrowserSDK.HARLog.pseudoWallTime(request, page.startTime).toJSON(), id: 'page_' + page.id, title: page.url, // We don't have actual page title here. URL is probably better than nothing. pageTimings: { onContentLoad: this._pageEventTime(page, page.contentLoadTime), onLoad: this._pageEventTime(page, page.loadTime) } }; } /** * @param {!BrowserSDK.PageLoad} page * @param {number} time * @return {number} */ _pageEventTime(page, time) { const startTime = page.startTime; if (time === -1 || startTime === -1) return -1; return BrowserSDK.HAREntry._toMilliseconds(time - startTime); } };