Commit 96836775 authored by Joey Arhar's avatar Joey Arhar Committed by Commit Bot

[DevTools] Add WebSocket messages to HAR exports

This patch also enables importing of the newly exported WebSocket
messages

Bug: 496006
Change-Id: I000468d266a570ca46eaa62ac9d7554713896acb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1574606Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Commit-Queue: Joey Arhar <jarhar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#653373}
parent 3d256ea0
......@@ -73,6 +73,15 @@ HARImporter.HARBase = class {
return;
return value;
}
/**
* @param {string} name
* @return {!Array|undefined}
*/
customAsArray(name) {
const value = /** @type {!Object} */ (this)['_' + name];
return Array.isArray(value) ? value : undefined;
}
};
// Using any of these classes may throw.
......@@ -164,6 +173,8 @@ HARImporter.HAREntry = class extends HARImporter.HARBase {
this._initiator = new HARImporter.HARInitiator(data['_initiator']);
this._priority = HARImporter.HARBase._optionalString(data['_priority']);
this._resourceType = HARImporter.HARBase._optionalString(data['_resourceType']);
if (Array.isArray(data['_webSocketMessages']))
this._webSocketMessages = data['_webSocketMessages'].map(message => new HARImporter.HARWebSocketMessage(message));
}
};
......@@ -331,3 +342,16 @@ HARImporter.HARInitiator = class extends HARImporter.HARBase {
this.lineNumber = HARImporter.HARBase._optionalNumber(data['lineNumber']);
}
};
HARImporter.HARWebSocketMessage = class extends HARImporter.HARBase {
/**
* @param {*} data
*/
constructor(data) {
super(data);
this.time = HARImporter.HARBase._optionalNumber(data['time']);
this.opcode = HARImporter.HARBase._optionalNumber(data['opcode']);
this.data = HARImporter.HARBase._optionalString(data['data']);
this.type = HARImporter.HARBase._optionalString(data['type']);
}
};
......@@ -122,6 +122,24 @@ HARImporter.Importer = class {
if (Protocol.Network.ResourcePriority.hasOwnProperty(priority))
request.setPriority(/** @type {!Protocol.Network.ResourcePriority} */ (priority));
const messages = entry.customAsArray('webSocketMessages');
if (messages) {
for (const message of messages) {
if (message.time === undefined)
continue;
if (!Object.values(SDK.NetworkRequest.WebSocketFrameType).includes(message.type))
continue;
if (message.opcode === undefined)
continue;
if (message.data === undefined)
continue;
const mask = message.type === SDK.NetworkRequest.WebSocketFrameType.Send;
request.addFrame(
{time: message.time, text: message.data, opCode: message.opcode, mask: mask, type: message.type});
}
}
request.finished = true;
}
......
......@@ -401,14 +401,6 @@ Network.NetworkLogView = class extends UI.VBox {
return request.parsedURL.isValid && (request.scheme in Network.NetworkLogView.HTTPSchemas);
}
/**
* @param {!SDK.NetworkRequest} request
* @return {boolean}
*/
static FinishedRequestsFilter(request) {
return request.finished;
}
/**
* @param {number} windowStart
* @param {number} windowEnd
......@@ -1278,8 +1270,10 @@ Network.NetworkLogView = class extends UI.VBox {
}
_harRequests() {
const httpRequests = SDK.networkLog.requests().filter(Network.NetworkLogView.HTTPRequestsFilter);
return httpRequests.filter(Network.NetworkLogView.FinishedRequestsFilter);
return SDK.networkLog.requests().filter(Network.NetworkLogView.HTTPRequestsFilter).filter(request => {
return request.finished ||
(request.resourceType() === Common.resourceTypes.WebSocket && request.responseReceivedTime);
});
}
async _copyAll() {
......
......@@ -174,14 +174,24 @@ SDK.HARLog.Entry = class {
};
// 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 = SDK.NetworkLog.PageLoad.forRequest(harEntry._request);
if (page)
entry.pageref = 'page_' + page.id;
if (harEntry._request.resourceType() === Common.resourceTypes.WebSocket) {
const messages = [];
for (const message of harEntry._request.frames())
messages.push({type: message.type, time: message.time, opcode: message.opCode, data: message.text});
entry._webSocketMessages = messages;
}
return entry;
}
......
......@@ -681,7 +681,7 @@ SDK.NetworkDispatcher = class {
if (!networkRequest)
return;
networkRequest.addFrame(response, time, false);
networkRequest.addProtocolFrame(response, time, false);
networkRequest.responseReceivedTime = time;
this._updateNetworkRequest(networkRequest);
......@@ -698,7 +698,7 @@ SDK.NetworkDispatcher = class {
if (!networkRequest)
return;
networkRequest.addFrame(response, time, true);
networkRequest.addProtocolFrame(response, time, true);
networkRequest.responseReceivedTime = time;
this._updateNetworkRequest(networkRequest);
......@@ -715,7 +715,7 @@ SDK.NetworkDispatcher = class {
if (!networkRequest)
return;
networkRequest.addFrameError(errorMessage, time);
networkRequest.addProtocolFrameError(errorMessage, time);
networkRequest.responseReceivedTime = time;
this._updateNetworkRequest(networkRequest);
......
......@@ -1195,8 +1195,8 @@ SDK.NetworkRequest = class extends Common.Object {
* @param {string} errorMessage
* @param {number} time
*/
addFrameError(errorMessage, time) {
this._addFrame({
addProtocolFrameError(errorMessage, time) {
this.addFrame({
type: SDK.NetworkRequest.WebSocketFrameType.Error,
text: errorMessage,
time: this.pseudoWallTime(time),
......@@ -1210,9 +1210,9 @@ SDK.NetworkRequest = class extends Common.Object {
* @param {number} time
* @param {boolean} sent
*/
addFrame(response, time, sent) {
addProtocolFrame(response, time, sent) {
const type = sent ? SDK.NetworkRequest.WebSocketFrameType.Send : SDK.NetworkRequest.WebSocketFrameType.Receive;
this._addFrame({
this.addFrame({
type: type,
text: response.payloadData,
time: this.pseudoWallTime(time),
......@@ -1224,7 +1224,7 @@ SDK.NetworkRequest = class extends Common.Object {
/**
* @param {!SDK.NetworkRequest.WebSocketFrame} frame
*/
_addFrame(frame) {
addFrame(frame) {
this._frames.push(frame);
this.dispatchEventToListeners(SDK.NetworkRequest.Events.WebsocketFrameAdded, frame);
}
......
......@@ -64,7 +64,8 @@ requests: [
"pushEnd": 0,
"receiveHeadersEnd": 5.624447982991114
},
"endTime": 1542746587.7661417
"endTime": 1542746587.7661417,
"frames": []
},
{
"url": "http://localhost:8000/post-endpoint",
......@@ -122,7 +123,8 @@ requests: [
"pushEnd": 0,
"receiveHeadersEnd": 2.828536113491282
},
"endTime": 1542746587.8738945
"endTime": 1542746587.8738945,
"frames": []
},
{
"url": "http://localhost:8000/js_file.js",
......@@ -180,7 +182,8 @@ requests: [
"pushEnd": 0,
"receiveHeadersEnd": 2.828536113491282
},
"endTime": 1542746587.8738945
"endTime": 1542746587.8738945,
"frames": []
},
{
"url": "http://localhost:8000/endpoint",
......@@ -236,7 +239,173 @@ requests: [
"pushEnd": 0,
"receiveHeadersEnd": 2.828536113491282
},
"endTime": 1542746587.8738945
"endTime": 1542746587.8738945,
"frames": []
},
{
"url": "ws://localhost:8880/echo",
"documentURL": "ws://localhost:8880/echo",
"initiator": {
"type": "script"
},
"requestFormData": null,
"connectionId": "",
"requestMethod": "GET",
"requestHeaders": [
{
"name": "Pragma",
"value": "no-cache"
},
{
"name": "Origin",
"value": "http://localhost:8000"
},
{
"name": "Accept-Encoding",
"value": "gzip, deflate, br"
},
{
"name": "Host",
"value": "localhost:8880"
},
{
"name": "Accept-Language",
"value": "en-US,en;q=0.9"
},
{
"name": "Sec-WebSocket-Key",
"value": "EBTeYTo1PMrIJhQV3KCyLA=="
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3762.0 Safari/537.36"
},
{
"name": "Upgrade",
"value": "websocket"
},
{
"name": "Sec-WebSocket-Extensions",
"value": "permessage-deflate; client_max_window_bits"
},
{
"name": "Cache-Control",
"value": "no-cache"
},
{
"name": "Connection",
"value": "Upgrade"
},
{
"name": "Sec-WebSocket-Version",
"value": "13"
}
],
"responseHeaders": [
{
"name": "Sec-WebSocket-Accept",
"value": "U81HpQbqlT7cIvlTLbf4dTv7m5w="
},
{
"name": "Connection",
"value": "Upgrade"
},
{
"name": "Sec-WebSocket-Extensions",
"value": "permessage-deflate"
},
{
"name": "Upgrade",
"value": "websocket"
}
],
"statusCode": 101,
"statusText": "Switching Protocols",
"protocol": "http/1.1",
"resourceSize": 175,
"transferSize": 0,
"cached": false,
"cachedInMemory": false,
"contentData": {
"error": null,
"content": null,
"encoded": false
},
"remoteAddress": ":80",
"resourceType": {
"_name": "websocket",
"_title": "WebSocket",
"_category": {
"title": "WebSockets",
"shortTitle": "WS"
},
"_isTextType": false
},
"priority": null,
"finished": true,
"timing": {
"proxyStart": -1,
"proxyEnd": -1,
"requestTime": 1555608228.174,
"dnsStart": -1,
"dnsEnd": -1,
"connectStart": -1,
"connectEnd": -1,
"sslStart": -1,
"sslEnd": -1,
"workerStart": -1,
"workerReady": -1,
"sendStart": 0,
"sendEnd": 0,
"pushStart": 0,
"pushEnd": 0,
"receiveHeadersEnd": 33045.93599960208
},
"endTime": 1555608261.2199361,
"frames": [
{
"time": 1555608234.452854,
"text": "message one",
"opCode": 1,
"mask": true,
"type": "send"
},
{
"time": 1555608234.454548,
"text": "message one",
"opCode": 1,
"mask": false,
"type": "receive"
},
{
"time": 1555608237.98099,
"text": "message two",
"opCode": 1,
"mask": true,
"type": "send"
},
{
"time": 1555608237.9821968,
"text": "message two",
"opCode": 1,
"mask": false,
"type": "receive"
},
{
"time": 1555608261.219595,
"text": "YmluYXJ5IG1lc3NhZ2U=",
"opCode": 2,
"mask": true,
"type": "send"
},
{
"time": 1555608261.2207098,
"text": "YmluYXJ5IG1lc3NhZ2U=",
"opCode": 2,
"mask": false,
"type": "receive"
}
]
}
]
......@@ -14,7 +14,7 @@
url: request.url(),
documentURL: request.documentURL,
initiator: request.initiator(),
requestFormData: await(request.requestFormData()),
requestFormData: await (request.requestFormData()),
connectionId: request.connectionId,
requestMethod: request.requestMethod,
requestHeaders: request.requestHeaders(),
......@@ -27,13 +27,14 @@
transferSize: request.transferSize,
cached: request.cached(),
cachedInMemory: request.cachedInMemory(),
contentData: await(request.contentData()),
contentData: await (request.contentData()),
remoteAddress: request.remoteAddress(),
resourceType: request.resourceType(),
priority: request.priority(),
finished: request.finished,
timing: request.timing,
endTime: request.endTime
endTime: request.endTime,
frames: request.frames()
};
}));
TestRunner.addResult(
......@@ -258,6 +259,168 @@ const harJson = {
'_resourceType': 'fetch',
'connection': '2945',
'pageref': 'page_1'
},
{
'startedDateTime': '2019-04-18T17:23:48.174Z',
'time': 33045.93599960208,
'request': {
'method': 'GET',
'url': 'ws://localhost:8880/echo',
'httpVersion': 'HTTP/1.1',
'headers': [
{
'name': 'Pragma',
'value': 'no-cache'
},
{
'name': 'Origin',
'value': 'http://localhost:8000'
},
{
'name': 'Accept-Encoding',
'value': 'gzip, deflate, br'
},
{
'name': 'Host',
'value': 'localhost:8880'
},
{
'name': 'Accept-Language',
'value': 'en-US,en;q=0.9'
},
{
'name': 'Sec-WebSocket-Key',
'value': 'EBTeYTo1PMrIJhQV3KCyLA=='
},
{
'name': 'User-Agent',
'value': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3762.0 Safari/537.36'
},
{
'name': 'Upgrade',
'value': 'websocket'
},
{
'name': 'Sec-WebSocket-Extensions',
'value': 'permessage-deflate; client_max_window_bits'
},
{
'name': 'Cache-Control',
'value': 'no-cache'
},
{
'name': 'Connection',
'value': 'Upgrade'
},
{
'name': 'Sec-WebSocket-Version',
'value': '13'
}
],
'queryString': [],
'cookies': [],
'headersSize': 506,
'bodySize': 0
},
'response': {
'status': 101,
'statusText': 'Switching Protocols',
'httpVersion': 'HTTP/1.1',
'headers': [
{
'name': 'Sec-WebSocket-Accept',
'value': 'U81HpQbqlT7cIvlTLbf4dTv7m5w='
},
{
'name': 'Connection',
'value': 'Upgrade'
},
{
'name': 'Sec-WebSocket-Extensions',
'value': 'permessage-deflate'
},
{
'name': 'Upgrade',
'value': 'websocket'
}
],
'cookies': [],
'content': {
'size': 0,
'mimeType': 'x-unknown',
'compression': 175
},
'redirectURL': '',
'headersSize': 175,
'bodySize': -175,
'_transferSize': 0
},
'cache': {},
'timings': {
'blocked': -1,
'dns': -1,
'ssl': -1,
'connect': -1,
'send': 0,
'wait': 33045.93599960208,
'receive': 0,
'_blocked_queueing': -1
},
'serverIPAddress': '',
'_initiator': {
'type': 'script',
'stack': {
'callFrames': [
{
'functionName': '',
'scriptId': '73',
'url': '',
'lineNumber': 0,
'columnNumber': 5
}
]
}
},
'_priority': null,
'_resourceType': 'websocket',
'_webSocketMessages': [
{
'type': 'send',
'time': 1555608234.452854,
'opcode': 1,
'data': 'message one'
},
{
'type': 'receive',
'time': 1555608234.454548,
'opcode': 1,
'data': 'message one'
},
{
'type': 'send',
'time': 1555608237.98099,
'opcode': 1,
'data': 'message two'
},
{
'type': 'receive',
'time': 1555608237.9821968,
'opcode': 1,
'data': 'message two'
},
{
'type': 'send',
'time': 1555608261.219595,
'opcode': 2,
'data': 'YmluYXJ5IG1lc3NhZ2U='
},
{
'type': 'receive',
'time': 1555608261.2207098,
'opcode': 2,
'data': 'YmluYXJ5IG1lc3NhZ2U='
}
]
}
]
}
......
Verifies that HAR exports contain websocket messages
messages: [
{
"type": "receive",
"opcode": 1,
"data": "last message"
},
{
"type": "receive",
"opcode": 1,
"data": "text message"
},
{
"type": "receive",
"opcode": 2,
"data": "YmluYXJ5IG1lc3NhZ2U="
},
{
"type": "send",
"opcode": 1,
"data": "last message"
},
{
"type": "send",
"opcode": 1,
"data": "text message"
},
{
"type": "send",
"opcode": 2,
"data": "YmluYXJ5IG1lc3NhZ2U="
}
]
// Copyright 2019 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.
(async function() {
TestRunner.addResult('Verifies that HAR exports contain websocket messages');
await TestRunner.loadModule('network_test_runner');
await TestRunner.showPanel('network');
await TestRunner.NetworkAgent.setCacheDisabled(true);
const lastMessagePromise = new Promise(resolve => {
TestRunner.networkManager.addEventListener(SDK.NetworkManager.Events.RequestUpdated, event => {
const request = event.data;
if (!request.frames().length)
return;
const lastFrame = request.frames()[request.frames().length - 1];
if (lastFrame.text === 'last message' && lastFrame.type === SDK.NetworkRequest.WebSocketFrameType.Receive)
resolve();
});
});
await TestRunner.evaluateInPagePromise(`
new Promise(resolve => {
ws = new WebSocket('ws://127.0.0.1:8880/echo');
ws.onopen = () => {
ws.send('text message');
ws.send(new TextEncoder().encode('binary message'));
ws.send('last message');
};
});
`);
await lastMessagePromise;
const harString = await new Promise(async resolve => {
const stream = new TestRunner.StringOutputStream(resolve);
const progress = new Common.Progress();
await Network.HARWriter.write(
stream, NetworkTestRunner.networkRequests(), progress);
progress.done();
stream.close();
});
const har = JSON.parse(harString);
const websocketEntry = har.log.entries.find(entry => entry.request.url.endsWith('/echo'));
const messages = websocketEntry._webSocketMessages.map(message => {
return {
type: message.type,
opcode: message.opcode,
data: message.data
};
}).sort((messageOne, messageTwo) => {
return (messageOne.type + messageOne.data).localeCompare(messageTwo.type + messageTwo.data);
});
TestRunner.addResult('messages: ' + JSON.stringify(messages, null, 2));
TestRunner.completeTest();
})();
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment