blob: 4370c87710cf0e5067ac5a30469fff642ebb042c [file] [log] [blame]
// Copyright 2018 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.
export default class ProtocolMonitorImpl extends UI.VBox {
constructor() {
super(true);
this._nodes = [];
this._started = false;
this._startTime = 0;
this._nodeForId = {};
this._filter = node => true;
this._columns = [
{id: 'method', title: ls`Method`, visible: true, sortable: true, weight: 60},
{id: 'direction', title: ls`Direction`, visible: false, sortable: true, hideable: true, weight: 30},
{id: 'request', title: ls`Request`, visible: true, hideable: true, weight: 60},
{id: 'response', title: ls`Response`, visible: true, hideable: true, weight: 60},
{id: 'timestamp', title: ls`Timestamp`, visible: false, sortable: true, hideable: true, weight: 30},
{id: 'target', title: ls`Target`, visible: false, sortable: true, hideable: true, weight: 30}
];
this.registerRequiredCSS('protocol_monitor/protocolMonitor.css');
const topToolbar = new UI.Toolbar('protocol-monitor-toolbar', this.contentElement);
const recordButton = new UI.ToolbarToggle(ls`Record`, 'largeicon-start-recording', 'largeicon-stop-recording');
recordButton.addEventListener(UI.ToolbarButton.Events.Click, () => {
recordButton.setToggled(!recordButton.toggled());
this._setRecording(recordButton.toggled());
});
recordButton.setToggleWithRedColor(true);
topToolbar.appendToolbarItem(recordButton);
recordButton.setToggled(true);
const clearButton = new UI.ToolbarButton(ls`Clear all`, 'largeicon-clear');
clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => {
this._dataGrid.rootNode().removeChildren();
this._nodes = [];
this._nodeForId = {};
});
topToolbar.appendToolbarItem(clearButton);
const split = new UI.SplitWidget(true, true, 'protocol-monitor-panel-split', 250);
split.show(this.contentElement);
this._dataGrid = new DataGrid.SortableDataGrid(this._columns);
this._dataGrid.element.style.flex = '1';
this._infoWidget = new ProtocolMonitor.ProtocolMonitor.InfoWidget();
split.setMainWidget(this._dataGrid.asWidget());
split.setSidebarWidget(this._infoWidget);
this._dataGrid.addEventListener(
DataGrid.DataGrid.Events.SelectedNode, event => this._infoWidget.render(event.data.data));
this._dataGrid.addEventListener(DataGrid.DataGrid.Events.DeselectedNode, event => this._infoWidget.render(null));
this._dataGrid.setHeaderContextMenuCallback(this._innerHeaderContextMenu.bind(this));
this._dataGrid.setRowContextMenuCallback(this._innerRowContextMenu.bind(this));
this._dataGrid.addEventListener(DataGrid.DataGrid.Events.SortingChanged, this._sortDataGrid.bind(this));
this._dataGrid.setStickToBottom(true);
this._dataGrid.sortNodes(DataGrid.SortableDataGrid.NumericComparator.bind(null, 'timestamp'), false);
this._updateColumnVisibility();
const keys = ['method', 'request', 'response', 'direction'];
this._filterParser = new TextUtils.FilterParser(keys);
this._suggestionBuilder = new UI.FilterSuggestionBuilder(keys);
this._textFilterUI = new UI.ToolbarInput(
ls`Filter`, '', 1, .2, '', this._suggestionBuilder.completions.bind(this._suggestionBuilder));
this._textFilterUI.addEventListener(UI.ToolbarInput.Event.TextChanged, event => {
const query = /** @type {string} */ (event.data);
const filters = this._filterParser.parse(query);
this._filter = node => {
for (const {key, text, negative} of filters) {
if (!text) {
continue;
}
const data = key ? node.data[key] : node.data;
if (!data) {
continue;
}
const found = JSON.stringify(data).toLowerCase().indexOf(text.toLowerCase()) !== -1;
if (found === negative) {
return false;
}
}
return true;
};
this._filterNodes();
});
topToolbar.appendToolbarItem(this._textFilterUI);
}
_filterNodes() {
for (const node of this._nodes) {
if (this._filter(node)) {
if (!node.parent) {
this._dataGrid.insertChild(node);
}
} else {
node.remove();
}
}
}
/**
* @param {!UI.ContextSubMenu} contextMenu
*/
_innerHeaderContextMenu(contextMenu) {
const columnConfigs = this._columns.filter(columnConfig => columnConfig.hideable);
for (const columnConfig of columnConfigs) {
contextMenu.headerSection().appendCheckboxItem(
columnConfig.title, this._toggleColumnVisibility.bind(this, columnConfig), columnConfig.visible);
}
}
/**
* @param {!UI.ContextMenu} contextMenu
* @param {!ProtocolMonitor.ProtocolMonitor.ProtocolNode} node
*/
_innerRowContextMenu(contextMenu, node) {
contextMenu.defaultSection().appendItem(ls`Filter`, () => {
this._textFilterUI.setValue(`method:${node.data.method}`, true);
});
contextMenu.defaultSection().appendItem(ls`Documentation`, () => {
const [domain, method] = node.data.method.split('.');
const type = node.data.direction === 'sent' ? 'method' : 'event';
Host.InspectorFrontendHost.openInNewTab(
`https://chromedevtools.github.io/devtools-protocol/tot/${domain}#${type}-${method}`);
});
}
/**
* @param {!Object} columnConfig
*/
_toggleColumnVisibility(columnConfig) {
columnConfig.visible = !columnConfig.visible;
this._updateColumnVisibility();
}
_updateColumnVisibility() {
const visibleColumns = /** @type {!Object.<string, boolean>} */ ({});
for (const columnConfig of this._columns) {
visibleColumns[columnConfig.id] = columnConfig.visible;
}
this._dataGrid.setColumnsVisiblity(visibleColumns);
}
_sortDataGrid() {
const sortColumnId = this._dataGrid.sortColumnId();
if (!sortColumnId) {
return;
}
let columnIsNumeric = true;
switch (sortColumnId) {
case 'method':
case 'direction':
columnIsNumeric = false;
break;
}
const comparator =
columnIsNumeric ? DataGrid.SortableDataGrid.NumericComparator : DataGrid.SortableDataGrid.StringComparator;
this._dataGrid.sortNodes(comparator.bind(null, sortColumnId), !this._dataGrid.isSortOrderAscending());
}
/**
* @override
*/
wasShown() {
if (this._started) {
return;
}
this._started = true;
this._startTime = Date.now();
this._setRecording(true);
}
/**
* @param {boolean} recording
*/
_setRecording(recording) {
if (recording) {
Protocol.test.onMessageSent = this._messageSent.bind(this);
Protocol.test.onMessageReceived = this._messageRecieved.bind(this);
} else {
Protocol.test.onMessageSent = null;
Protocol.test.onMessageReceived = null;
}
}
/**
* @param {?SDK.Target} target
* @return {string}
*/
_targetToString(target) {
if (!target) {
return '';
}
return target.decorateLabel(`${target.name()} ${target === SDK.targetManager.mainTarget() ? '' : target.id()}`);
}
/**
* @param {!Object} message
* @param {?Protocol.TargetBase} target
*/
_messageRecieved(message, target) {
if ('id' in message) {
const node = this._nodeForId[message.id];
if (!node) {
return;
}
node.data.response = message.result || message.error;
node.hasError = !!message.error;
node.refresh();
if (this._dataGrid.selectedNode === node) {
this._infoWidget.render(node.data);
}
return;
}
const sdkTarget = /** @type {?SDK.Target} */ (target);
const node = new ProtocolMonitor.ProtocolMonitor.ProtocolNode({
method: message.method,
direction: 'recieved',
response: message.params,
timestamp: Date.now() - this._startTime,
request: '',
target: this._targetToString(sdkTarget)
});
this._nodes.push(node);
if (this._filter(node)) {
this._dataGrid.insertChild(node);
}
}
/**
* @param {{domain: string, method: string, params: !Object, id: number}} message
* @param {?Protocol.TargetBase} target
*/
_messageSent(message, target) {
const sdkTarget = /** @type {?SDK.Target} */ (target);
const node = new ProtocolMonitor.ProtocolMonitor.ProtocolNode({
method: message.method,
direction: 'sent',
request: message.params,
timestamp: Date.now() - this._startTime,
response: '(pending)',
id: message.id,
target: this._targetToString(sdkTarget)
});
this._nodeForId[message.id] = node;
this._nodes.push(node);
if (this._filter(node)) {
this._dataGrid.insertChild(node);
}
}
}
export class ProtocolNode extends DataGrid.SortableDataGridNode {
constructor(data) {
super(data);
this.hasError = false;
}
/**
* @override
* @param {string} columnId
* @return {!Element}
*/
createCell(columnId) {
switch (columnId) {
case 'response':
if (!this.data[columnId] && this.data.direction === 'send') {
const cell = this.createTD(columnId);
cell.textContent = '(pending)';
return cell;
}
// fall through
case 'request': {
const cell = this.createTD(columnId);
const obj = SDK.RemoteObject.fromLocalObject(this.data[columnId]);
cell.textContent = obj.description.trimEndWithMaxLength(50);
cell.classList.add('source-code');
return cell;
}
case 'timestamp': {
const cell = this.createTD(columnId);
cell.textContent = ls`${this.data[columnId]} ms`;
return cell;
}
}
return super.createCell(columnId);
}
/**
* @override
*/
element() {
const element = super.element();
element.classList.toggle('protocol-message-sent', this.data.direction === 'sent');
element.classList.toggle('protocol-message-recieved', this.data.direction !== 'sent');
element.classList.toggle('error', this.hasError);
return element;
}
}
export class InfoWidget extends UI.VBox {
constructor() {
super();
this._tabbedPane = new UI.TabbedPane();
this._tabbedPane.appendTab('request', 'Request', new UI.Widget());
this._tabbedPane.appendTab('response', 'Response', new UI.Widget());
this._tabbedPane.show(this.contentElement);
this._tabbedPane.selectTab('response');
this.render(null);
}
/**
* @param {?{method: string, direction: string, request: ?Object, response: ?Object, timestamp: number}} data
*/
render(data) {
const requestEnabled = data && data.direction === 'sent';
this._tabbedPane.setTabEnabled('request', !!requestEnabled);
if (!data) {
this._tabbedPane.changeTabView('request', new UI.EmptyWidget(ls`No message selected`));
this._tabbedPane.changeTabView('response', new UI.EmptyWidget(ls`No message selected`));
return;
}
if (!requestEnabled) {
this._tabbedPane.selectTab('response');
}
this._tabbedPane.changeTabView('request', SourceFrame.JSONView.createViewSync(data.request));
this._tabbedPane.changeTabView('response', SourceFrame.JSONView.createViewSync(data.response));
}
}
/* Legacy exported object */
self.ProtocolMonitor = self.ProtocolMonitor || {};
/* Legacy exported object */
ProtocolMonitor = ProtocolMonitor || {};
/**
* @constructor
*/
ProtocolMonitor.ProtocolMonitor = ProtocolMonitorImpl;
/**
* @constructor
*/
ProtocolMonitor.ProtocolMonitor.InfoWidget = InfoWidget;
/**
* @constructor
*/
ProtocolMonitor.ProtocolMonitor.ProtocolNode = ProtocolNode;