Commit 5edcd785 authored by Joel Einbinder's avatar Joel Einbinder Committed by Commit Bot

DevTools: Protocol Monitor

Change-Id: Ib3db9d58f1c4b1f91a1fc876a354907208958e11
Reviewed-on: https://chromium-review.googlesource.com/912466
Commit-Queue: Paul Irish <paulirish@chromium.org>
Reviewed-by: default avatarPavel Feldman <pfeldman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#542391}
parent 2b3efc09
......@@ -377,6 +377,9 @@ all_devtools_files = [
"front_end/main/Main.js",
"front_end/main/module.json",
"front_end/main/SimpleApp.js",
"front_end/protocol_monitor/ProtocolMonitor.js",
"front_end/protocol_monitor/protocolMonitor.css",
"front_end/protocol_monitor/module.json",
"front_end/mobile_throttling/MobileThrottlingSelector.js",
"front_end/mobile_throttling/module.json",
"front_end/mobile_throttling/NetworkPanelIndicator.js",
......@@ -999,6 +1002,7 @@ generated_non_autostart_non_remote_modules = [
"$resources_out_dir/browser_console/browser_console_module.js",
"$resources_out_dir/browser_debugger/browser_debugger_module.js",
"$resources_out_dir/changes/changes_module.js",
"$resources_out_dir/protocol_monitor/protocol_monitor_module.js",
"$resources_out_dir/cm/cm_module.js",
"$resources_out_dir/color_picker/color_picker_module.js",
"$resources_out_dir/console/console_module.js",
......
......@@ -75,6 +75,7 @@
.data-grid table.data tr {
display: none;
height: 20px;
}
.data-grid table.data tr.revealed {
......
......@@ -110,9 +110,10 @@ Main.Main = class {
Runtime.experiments.register('colorContrastRatio', 'Color contrast ratio line in color picker', true);
Runtime.experiments.register('emptySourceMapAutoStepping', 'Empty sourcemap auto-stepping');
Runtime.experiments.register('inputEventsOnTimelineOverview', 'Input events on Timeline overview', true);
Runtime.experiments.register('oopifInlineDOM', 'OOPIF: inline DOM ', true);
Runtime.experiments.register('nativeHeapProfiler', 'Native memory sampling heap profiler', true);
Runtime.experiments.register('networkSearch', 'Network search', true);
Runtime.experiments.register('oopifInlineDOM', 'OOPIF: inline DOM ', true);
Runtime.experiments.register('protocolMonitor', 'Protocol Monitor');
Runtime.experiments.register('sourceDiff', 'Source diff');
Runtime.experiments.register(
'stepIntoAsync', 'Introduce separate step action, stepInto becomes powerful enough to go inside async call');
......
......@@ -212,11 +212,12 @@ Protocol.InspectorBackend.Connection.Factory;
/**
* @unrestricted
*/
Protocol.TargetBase = class {
Protocol.TargetBase = class extends Common.Object {
/**
* @param {!Protocol.InspectorBackend.Connection.Factory} connectionFactory
*/
constructor(connectionFactory) {
super();
this._connection =
connectionFactory({onMessage: this._onMessage.bind(this), onDisconnect: this._onDisconnect.bind(this)});
this._lastMessageId = 1;
......@@ -290,6 +291,11 @@ Protocol.TargetBase = class {
if (Protocol.InspectorBackend.Options.dumpInspectorProtocolMessages)
this._dumpProtocolMessage('frontend: ' + message, '[FE] ' + domain);
if (this.hasEventListeners(Protocol.TargetBase.Events.MessageSent)) {
this.dispatchEventToListeners(
Protocol.TargetBase.Events.MessageSent,
{domain, method, params: JSON.parse(JSON.stringify(params)), id: messageId});
}
this._connection.sendMessage(message);
++this._pendingResponsesCount;
......@@ -332,6 +338,12 @@ Protocol.TargetBase = class {
this._dumpProtocolMessage(
'backend: ' + ((typeof message === 'string') ? message : JSON.stringify(message)), 'Backend');
}
if (this.hasEventListeners(Protocol.TargetBase.Events.MessageReceived)) {
this.dispatchEventToListeners(Protocol.TargetBase.Events.MessageReceived, {
message: JSON.parse((typeof message === 'string') ? message : JSON.stringify(message)),
});
}
const messageObject = /** @type {!Object} */ ((typeof message === 'string') ? JSON.parse(message) : message);
......@@ -476,6 +488,11 @@ Protocol.TargetBase = class {
}
};
Protocol.TargetBase.Events = {
MessageSent: Symbol('MessageSent'),
MessageReceived: Symbol('MessageReceived')
};
/**
* @unrestricted
*/
......
{
"dependencies": [
"common"
],
"scripts": [
"InspectorBackend.js",
"../InspectorBackendCommands.js"
......
// 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.
ProtocolMonitor.ProtocolMonitor = class extends UI.VBox {
constructor() {
super(true);
this._nodes = [];
this._recordingListeners = null;
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}
];
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.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 (!(key in node.data) || !text)
continue;
const found = JSON.stringify(node.data[key]).indexOf(text) !== -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.ContextMenu} 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);
}
contextMenu.show();
}
/**
* @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 (this._recordingListeners) {
Common.EventTarget.removeEventListeners(this._recordingListeners);
this._recordingListeners = null;
}
if (recording) {
this._recordingListeners = [
SDK.targetManager.mainTarget().addEventListener(
Protocol.TargetBase.Events.MessageReceived, this._messageRecieved, this),
SDK.targetManager.mainTarget().addEventListener(Protocol.TargetBase.Events.MessageSent, this._messageSent, this)
];
}
}
_messageRecieved(event) {
const message = event.data.message;
if ('id' in message) {
const node = this._nodeForId[message.id];
if (!node)
return;
node.data.response = message.result;
node.refresh();
if (this._dataGrid.selectedNode === node)
this._infoWidget.render(node.data);
return;
}
const node = new ProtocolMonitor.ProtocolMonitor.ProtocolNode({
method: message.method,
direction: 'recieved',
response: message.params,
timestamp: Date.now() - this._startTime,
request: ''
});
this._nodes.push(node);
if (this._filter(node))
this._dataGrid.insertChild(node);
}
_messageSent(event) {
const message = event.data;
const node = new ProtocolMonitor.ProtocolMonitor.ProtocolNode({
method: message.method,
direction: 'sent',
request: message.params,
timestamp: Date.now() - this._startTime,
response: '(pending)',
id: message.id
});
this._nodeForId[message.id] = node;
this._nodes.push(node);
if (this._filter(node))
this._dataGrid.insertChild(node);
}
};
ProtocolMonitor.ProtocolMonitor.ProtocolNode = class extends DataGrid.SortableDataGridNode {
constructor(data) {
super(data);
}
/**
* @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.trimEnd(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');
return element;
}
};
ProtocolMonitor.ProtocolMonitor.InfoWidget = class 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));
}
};
{
"extensions": [
{
"type": "view",
"location": "drawer-view",
"id": "protocol-monitor",
"title": "Protocol monitor",
"order": 100,
"persistence": "closeable",
"className": "ProtocolMonitor.ProtocolMonitor",
"experiment": "protocolMonitor"
}
],
"dependencies": ["platform", "ui", "host", "components", "data_grid", "source_frame"],
"scripts": [
"ProtocolMonitor.js"
],
"resources": [
"protocolMonitor.css"
]
}
\ No newline at end of file
.data-grid {
border: none;
}
.data-grid {
flex: auto;
border: none;
}
.data-grid .data {
background-image: none;
}
.data-grid td {
/* border-bottom: 1px solid #ccc; */
border-left-color: #ccc;
}
.data-grid tr.selected {
background-color: #def;
}
.data-grid th {
border-left-color: #ccc;
}
.protocol-message-sent {
background-color: hsl(281, 64%, 95%);
}
.protocol-monitor-toolbar {
border-bottom:1px solid #dadada;
}
......@@ -42,6 +42,7 @@
{ "name": "sources" },
{ "name": "terminal", "type": "remote" },
{ "name": "text_editor" },
{ "name": "workspace_diff" }
{ "name": "workspace_diff" },
{ "name": "protocol_monitor"}
]
}
......@@ -55,7 +55,7 @@ SourceFrame.JSONView = class extends UI.VBox {
/**
* @param {string} content
* @return {!Promise<?SourceFrame.JSONView>}
* @return {!Promise<?UI.SearchableView>}
*/
static async createView(content) {
// We support non-strict JSON parsing by parsing an AST tree which is why we offload it to a worker.
......@@ -72,6 +72,20 @@ SourceFrame.JSONView = class extends UI.VBox {
return searchableView;
}
/**
* @param {?Object} obj
* @return {!UI.SearchableView}
*/
static createViewSync(obj) {
const jsonView = new SourceFrame.JSONView(new SourceFrame.ParsedJSON(obj, '', ''));
const searchableView = new UI.SearchableView(jsonView);
searchableView.setPlaceholder(Common.UIString('Find'));
jsonView._searchableView = searchableView;
jsonView.show(searchableView.element);
jsonView.element.setAttribute('tabIndex', 0);
return searchableView;
}
/**
* @param {?string} text
* @return {!Promise<?SourceFrame.ParsedJSON>}
......
......@@ -26,12 +26,15 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// For testing.
UI.panels = [];
UI.panels = {};
/**
* @unrestricted
*/
UI.Panel = class extends UI.VBox {
/**
* @param {string} name
*/
constructor(name) {
super();
......
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