chrome-devtools-frontend
Version:
Chrome DevTools UI
336 lines (286 loc) • 11.7 kB
text/typescript
// Copyright 2025 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.
import * as Common from '../../core/common/common.js';
import * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import {dispatchClickEvent, renderElementIntoDOM} from '../../testing/DOMHelpers.js';
import {
describeWithEnvironment,
} from '../../testing/EnvironmentHelpers.js';
import {
setUpEnvironment,
} from '../../testing/OverridesHelpers.js';
import * as RenderCoordinator from '../../ui/components/render_coordinator/render_coordinator.js';
import * as Network from './network.js';
const {urlString} = Platform.DevToolsPath;
function createNetworkRequest(type: SDK.NetworkRequest.DirectSocketType) {
const networkRequest = SDK.NetworkRequest.NetworkRequest.createForSocket(
'requestId' as Protocol.Network.RequestId, urlString`www.example.com/some/path:3000`);
networkRequest.hasNetworkData = true;
networkRequest.setRemoteAddress('www.example.com', 3000);
switch (type) {
case SDK.NetworkRequest.DirectSocketType.TCP:
networkRequest.protocol = 'tcp';
break;
case SDK.NetworkRequest.DirectSocketType.UDP_BOUND, SDK.NetworkRequest.DirectSocketType.UDP_CONNECTED:
networkRequest.protocol = 'udp';
break;
}
networkRequest.statusText = 'Opening';
networkRequest.directSocketInfo = {
type,
status: SDK.NetworkRequest.DirectSocketStatus.OPEN,
createOptions: {
remoteAddr: 'www.example.com/some/path',
remotePort: 3000,
noDelay: false,
keepAliveDelay: 1001,
sendBufferSize: 1002,
receiveBufferSize: 1003,
dnsQueryType: Protocol.Network.DirectSocketDnsQueryType.Ipv4,
},
openInfo: {remoteAddr: 'www.sample.com', remotePort: 3005, localAddr: '127.0.0.1', localPort: 9472}
};
networkRequest.setResourceType(Common.ResourceType.resourceTypes.DirectSocket);
networkRequest.setIssueTime(Date.now(), Date.now());
return networkRequest;
}
describeWithEnvironment('ResourceDirectSocketChunkView', () => {
let chunkView: Network.ResourceDirectSocketChunkView.ResourceDirectSocketChunkView|undefined;
beforeEach(() => {
setUpEnvironment();
Common.Settings.Settings.instance().clearAll();
});
afterEach(() => {
if (chunkView) {
chunkView.detach();
chunkView = undefined;
}
});
async function renderView(request: SDK.NetworkRequest.NetworkRequest):
Promise<Network.ResourceDirectSocketChunkView.ResourceDirectSocketChunkView> {
const container = document.createElement('div');
renderElementIntoDOM(container);
const view = new Network.ResourceDirectSocketChunkView.ResourceDirectSocketChunkView(request);
view.markAsRoot();
view.show(container);
view.getFilterInputForTest().setValue('', false);
return view;
}
for (const [testName, type] of [
['tcp', SDK.NetworkRequest.DirectSocketType.TCP],
['udp_connected', SDK.NetworkRequest.DirectSocketType.UDP_CONNECTED],
]) {
it(`renders ${testName} messages with correct columns`, async () => {
const request = createNetworkRequest(type as SDK.NetworkRequest.DirectSocketType);
request.addDirectSocketChunk({
data: 'c29tZSBkYXRh',
type: SDK.NetworkRequest.DirectSocketChunkType.SEND,
timestamp: 1700000000.123,
});
request.addDirectSocketChunk({
data: 'c29tZSBkYXRhIDI=',
type: SDK.NetworkRequest.DirectSocketChunkType.RECEIVE,
timestamp: 1700000001.456,
});
chunkView = await renderView(request);
await RenderCoordinator.done();
assertGridData(chunkView, [
['c29tZSBkYXRh', '9\xA0B', '22:13:20.123'],
['c29tZSBkYXRhIDI=', '11\xA0B', '22:13:21.456'],
]);
});
}
it('renders UDP bound messages with correct columns', async () => {
const request = createNetworkRequest(SDK.NetworkRequest.DirectSocketType.UDP_BOUND);
request.addDirectSocketChunk({
data: 'c29tZSBkYXRh',
type: SDK.NetworkRequest.DirectSocketChunkType.SEND,
timestamp: 1700000000.123,
remoteAddress: '192.168.0.1',
remotePort: 12345,
});
request.addDirectSocketChunk({
data: 'c29tZSBkYXRhIDI=',
type: SDK.NetworkRequest.DirectSocketChunkType.RECEIVE,
timestamp: 1700000001.456,
remoteAddress: '134.168.0.1',
remotePort: 54321,
});
chunkView = await renderView(request);
await RenderCoordinator.done();
assertGridData(chunkView, [
['c29tZSBkYXRh', '192.168.0.1', '12345', '9\xA0B', '22:13:20.123'],
['c29tZSBkYXRhIDI=', '134.168.0.1', '54321', '11\xA0B', '22:13:21.456'],
]);
});
it('shows binary view on click', async () => {
const request = createNetworkRequest(SDK.NetworkRequest.DirectSocketType.TCP);
request.addDirectSocketChunk({
data: 'c29tZSBkYXRh',
type: SDK.NetworkRequest.DirectSocketChunkType.SEND,
timestamp: 1700000000.123,
});
chunkView = await renderView(request);
await RenderCoordinator.done();
const dataGrid = chunkView.getDataGridForTest();
dataGrid.rootNode().children[0].select();
await RenderCoordinator.done();
const splitWidget = chunkView.getSplitWidgetForTest();
const sidebarWidget = splitWidget.sidebarWidget();
assert.instanceOf(sidebarWidget, Network.BinaryResourceView.BinaryResourceView);
});
it('clears messages with "Clear All" button', async () => {
const request = createNetworkRequest(SDK.NetworkRequest.DirectSocketType.TCP);
request.addDirectSocketChunk({
data: 'c29tZSBkYXRh',
type: SDK.NetworkRequest.DirectSocketChunkType.SEND,
timestamp: 1700000000.123,
});
chunkView = await renderView(request);
await RenderCoordinator.done();
assertGridData(chunkView, [['c29tZSBkYXRh', '9\xA0B', '22:13:20.123']]);
const clearButton = chunkView.getClearAllButtonForTest();
dispatchClickEvent(clearButton.element);
await RenderCoordinator.done();
assertGridData(chunkView, []);
});
it('filters messages by regex', async () => {
const request = createNetworkRequest(SDK.NetworkRequest.DirectSocketType.TCP);
request.addDirectSocketChunk({
data: 'c29tZSBkYXRhIDE=',
type: SDK.NetworkRequest.DirectSocketChunkType.SEND,
timestamp: 1700000000.123,
});
request.addDirectSocketChunk({
data: 'b3RoZXIgZGF0YSAy',
type: SDK.NetworkRequest.DirectSocketChunkType.RECEIVE,
timestamp: 1700000001.456,
});
request.addDirectSocketChunk({
data: 'c29tZSBkYXRhIDM=',
type: SDK.NetworkRequest.DirectSocketChunkType.SEND,
timestamp: 1700000002.789,
});
chunkView = await renderView(request);
const filterInput = chunkView.getFilterInputForTest();
filterInput.setValue('c29tZ', true);
await RenderCoordinator.done();
assertGridData(chunkView, [
['c29tZSBkYXRhIDE=', '11\xA0B', '22:13:20.123'],
['c29tZSBkYXRhIDM=', '11\xA0B', '22:13:22.789'],
]);
filterInput.setValue('b3Ro', true);
await RenderCoordinator.done();
assertGridData(chunkView, [
['b3RoZXIgZGF0YSAy', '12\xA0B', '22:13:21.456'],
]);
});
it('filters messages by type (SEND/RECEIVE/ALL)', async () => {
const request = createNetworkRequest(SDK.NetworkRequest.DirectSocketType.TCP);
request.addDirectSocketChunk({
data: 'c2VuZA==',
type: SDK.NetworkRequest.DirectSocketChunkType.SEND,
timestamp: 1700000000.123,
});
request.addDirectSocketChunk({
data: 'cmVjZWl2ZQ==',
type: SDK.NetworkRequest.DirectSocketChunkType.RECEIVE,
timestamp: 1700000001.456,
});
chunkView = await renderView(request);
const filterCombobox = chunkView.getFilterTypeComboboxForTest();
// Filter by SEND.
filterCombobox.element.value = 'send';
filterCombobox.element.dispatchEvent(new Event('change'));
await RenderCoordinator.done();
assertGridData(chunkView, [['c2VuZA==', '4\xA0B', '22:13:20.123']]);
// Filter by RECEIVE.
filterCombobox.element.value = 'receive';
filterCombobox.element.dispatchEvent(new Event('change'));
await RenderCoordinator.done();
assertGridData(chunkView, [['cmVjZWl2ZQ==', '7\xA0B', '22:13:21.456']]);
// Filter by ALL.
filterCombobox.element.value = 'all';
filterCombobox.element.dispatchEvent(new Event('change'));
await RenderCoordinator.done();
assertGridData(chunkView, [
['c2VuZA==', '4\xA0B', '22:13:20.123'],
['cmVjZWl2ZQ==', '7\xA0B', '22:13:21.456'],
]);
});
it('sorts messages by time column (ASC/DESC)', async () => {
const request = createNetworkRequest(SDK.NetworkRequest.DirectSocketType.TCP);
request.addDirectSocketChunk({
data: 'MQ==',
type: SDK.NetworkRequest.DirectSocketChunkType.SEND,
timestamp: 1700000001.123,
});
request.addDirectSocketChunk({
data: 'Mg==',
type: SDK.NetworkRequest.DirectSocketChunkType.RECEIVE,
timestamp: 1700000000.456,
});
chunkView = await renderView(request);
await RenderCoordinator.done();
// Initial sort is ASC by time.
assertGridData(chunkView, [
['Mg==', '1\xA0B', '22:13:20.456'],
['MQ==', '1\xA0B', '22:13:21.123'],
]);
// Click time header to sort DESC.
const dataGrid = chunkView.getDataGridForTest();
const timeHeader = dataGrid.element.querySelector('th[jslog*="context: time"]');
assert.instanceOf(timeHeader, HTMLTableCellElement);
dispatchClickEvent(timeHeader);
await RenderCoordinator.done();
assertGridData(chunkView, [
['MQ==', '1\xA0B', '22:13:21.123'],
['Mg==', '1\xA0B', '22:13:20.456'],
]);
// Click time header to sort ASC again.
dispatchClickEvent(timeHeader);
await RenderCoordinator.done();
assertGridData(chunkView, [
['Mg==', '1\xA0B', '22:13:20.456'],
['MQ==', '1\xA0B', '22:13:21.123'],
]);
});
});
function assertTime(actual: string, expectedMillis: string): void {
assert.match(actual, /\d{2}:\d{2}:\d{2}\.\d{3}/);
assert.isTrue(actual.endsWith(expectedMillis));
}
function getInnerTextOfGrid(view: Network.ResourceDirectSocketChunkView.ResourceDirectSocketChunkView): string[][] {
const grid = view.getDataGridForTest();
return grid.rootNode().children.map(row => {
return Object.keys(row.data).map(key => {
const value = row.data[key];
if (value instanceof HTMLDivElement) {
return value.innerText.trim();
}
return value.toString().trim();
});
});
}
function assertGridData(
view: Network.ResourceDirectSocketChunkView.ResourceDirectSocketChunkView, rowsExpected: string[][]): void {
const actualGridData = getInnerTextOfGrid(view);
assert.lengthOf(actualGridData, rowsExpected.length, 'Number of rows should match');
for (let i = 0; i < rowsExpected.length; i++) {
const expectedRow = rowsExpected[i];
const actualRow = actualGridData[i];
assert.lengthOf(actualRow, expectedRow.length, `Number of columns in row ${i} should match`);
// Time is always the last column. It is checked differently,
// because the time is converted using local timezone.
for (let j = 0; j < expectedRow.length; j++) {
if (j === expectedRow.length - 1) {
assertTime(actualRow[j], expectedRow[j].substring(expectedRow[j].lastIndexOf('.') + 1));
} else {
assert.strictEqual(actualRow[j], expectedRow[j], `Row ${i}, Column ${j} data should match`);
}
}
}
}