blob: 260f476eb67510b3569be68141f6f96c8a744239 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
HARImporter.Importer = class {
/**
* @param {!HARImporter.HARLog} log
* @return {!Array<!SDK.NetworkRequest>}
*/
static requestsFromHARLog(log) {
/** @type {!Map<string, !HARImporter.HARPage>} */
const pages = new Map();
for (const page of log.pages)
pages.set(page.id, page);
log.entries.sort((a, b) => a.startedDateTime - b.startedDateTime);
/** @type {!Map<string, !BrowserSDK.PageLoad>} */
const pageLoads = new Map();
/** @type {!Array<!SDK.NetworkRequest>} */
const requests = [];
for (const entry of log.entries) {
let pageLoad = pageLoads.get(entry.pageref);
const documentURL = pageLoad ? pageLoad.mainRequest.url() : entry.request.url;
const request = new SDK.NetworkRequest('har-' + requests.length, entry.request.url, documentURL, '', '', null);
const page = pages.get(entry.pageref);
if (!pageLoad && page) {
pageLoad = HARImporter.Importer._buildPageLoad(page, request);
pageLoads.set(entry.pageref, pageLoad);
}
HARImporter.Importer._fillRequestFromHAREntry(request, entry, pageLoad);
if (pageLoad)
pageLoad.bindRequest(request);
requests.push(request);
}
return requests;
}
/**
* @param {!HARImporter.HARPage} page
* @param {!SDK.NetworkRequest} mainRequest
* @return {!BrowserSDK.PageLoad}
*/
static _buildPageLoad(page, mainRequest) {
const pageLoad = new BrowserSDK.PageLoad(mainRequest);
pageLoad.startTime = page.startedDateTime;
pageLoad.contentLoadTime = page.pageTimings.onContentLoad * 1000;
pageLoad.loadTime = page.pageTimings.onLoad * 1000;
return pageLoad;
}
/**
* @param {!SDK.NetworkRequest} request
* @param {!HARImporter.HAREntry} entry
* @param {?BrowserSDK.PageLoad} pageLoad
*/
static _fillRequestFromHAREntry(request, entry, pageLoad) {
// Request data.
if (entry.request.postData)
request.setRequestFormData(true, entry.request.postData.text);
else
request.setRequestFormData(false, null);
request.connectionId = entry.connection || '';
request.requestMethod = entry.request.method;
request.setRequestHeaders(entry.request.headers);
// Response data.
if (entry.response.content.mimeType && entry.response.content.mimeType !== 'x-unknown')
request.mimeType = entry.response.content.mimeType;
request.responseHeaders = entry.response.headers;
request.statusCode = entry.response.status;
request.statusText = entry.response.statusText;
let protocol = entry.response.httpVersion.toLowerCase();
if (protocol === 'http/2.0')
protocol = 'h2';
request.protocol = protocol.replace(/^http\/2\.0?\+quic/, 'http/2+quic');
// Timing data.
const issueTime = entry.startedDateTime.getTime() / 1000;
request.setIssueTime(issueTime, issueTime);
// Content data.
const contentSize = entry.response.content.size > 0 ? entry.response.content.size : 0;
const headersSize = entry.response.headersSize > 0 ? entry.response.headersSize : 0;
const bodySize = entry.response.bodySize > 0 ? entry.response.bodySize : 0;
request.resourceSize = contentSize || (headersSize + bodySize);
let transferSize = entry.response.customAsNumber('transferSize');
if (transferSize === undefined)
transferSize = entry.response.headersSize + entry.response.bodySize;
request.setTransferSize(transferSize >= 0 ? transferSize : 0);
const fromCache = entry.customAsString('fromCache');
if (fromCache === 'memory')
request.setFromMemoryCache();
else if (fromCache === 'disk')
request.setFromDiskCache();
const contentData = {error: null, content: null, encoded: entry.response.content.encoding === 'base64'};
if (entry.response.content.text !== undefined)
contentData.content = entry.response.content.text;
request.setContentDataProvider(async () => contentData);
// Timing data.
HARImporter.Importer._setupTiming(request, issueTime, entry.time, entry.timings);
// Meta data.
request.setRemoteAddress(entry.serverIPAddress || '', 80); // Har does not support port numbers.
let resourceType = (pageLoad && pageLoad.mainRequest === request) ?
Common.resourceTypes.Document :
Common.ResourceType.fromMimeType(entry.response.content.mimeType);
if (!resourceType)
resourceType = Common.ResourceType.fromURL(entry.request.url) || Common.resourceTypes.Other;
request.setResourceType(resourceType);
request.finished = true;
}
/**
* @param {!SDK.NetworkRequest} request
* @param {number} issueTime
* @param {number} entryTotalDuration
* @param {!HARImporter.HARTimings} timings
*/
static _setupTiming(request, issueTime, entryTotalDuration, timings) {
/**
* @param {number|undefined} timing
* @return {number}
*/
function accumulateTime(timing) {
if (timing === undefined || timing < 0)
return -1;
lastEntry += timing;
return lastEntry;
}
let lastEntry = timings.blocked >= 0 ? timings.blocked : 0;
const proxy = timings.customAsNumber('blocked_proxy') || -1;
const queueing = timings.customAsNumber('blocked_queueing') || -1;
// SSL is part of connect for both HAR and Chrome's format so subtract it here.
const ssl = timings.ssl >= 0 ? timings.ssl : 0;
if (timings.connect > 0)
timings.connect -= ssl;
const timing = {
proxyStart: proxy > 0 ? lastEntry - proxy : -1,
proxyEnd: proxy > 0 ? lastEntry : -1,
requestTime: issueTime + (queueing > 0 ? queueing : 0) / 1000,
dnsStart: timings.dns >= 0 ? lastEntry : -1,
dnsEnd: accumulateTime(timings.dns),
// Add ssl to end time without modifying lastEntry (see comment above).
connectStart: timings.connect >= 0 ? lastEntry : -1,
connectEnd: accumulateTime(timings.connect) + ssl,
// Now update lastEntry to add ssl timing back in (see comment above).
sslStart: timings.ssl >= 0 ? lastEntry : -1,
sslEnd: accumulateTime(timings.ssl),
workerStart: -1,
workerReady: -1,
sendStart: timings.send >= 0 ? lastEntry : -1,
sendEnd: accumulateTime(timings.send),
pushStart: 0,
pushEnd: 0,
receiveHeadersEnd: accumulateTime(timings.wait)
};
accumulateTime(timings.receive);
request.timing = timing;
request.endTime = issueTime + Math.max(entryTotalDuration, lastEntry) / 1000;
}
};