Commit e728204c authored by Rayan Kanso's avatar Rayan Kanso Committed by Commit Bot

Add UI to show Background Service events in DevTools.

Events are shown in a data grid. Clicking on a row will create a popover
to show metadata.

This CL also adds functionality for the refresh/clear buttons.

Screenshots: https://bugs.chromium.org/p/chromium/issues/detail?id=942174#c1

Bug: 942174, 927726
Change-Id: Id6fc6dba7ddbc2e175992e76ffea3e29da947753
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1524290
Commit-Queue: Rayan Kanso <rayankans@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#644745}
parent 38f4ab87
...@@ -542,9 +542,11 @@ void BackgroundFetchScheduler::LogBackgroundFetchEventForDevTools( ...@@ -542,9 +542,11 @@ void BackgroundFetchScheduler::LogBackgroundFetchEventForDevTools(
// Include common request metadata. // Include common request metadata.
if (request_info) { if (request_info) {
metadata["url"] = request_info->fetch_request()->url.spec(); metadata["url"] = request_info->fetch_request()->url.spec();
metadata["request index"] = request_info->request_index(); metadata["request index"] =
base::NumberToString(request_info->request_index());
if (request_info->request_body_size()) if (request_info->request_body_size())
metadata["upload size (bytes)"] = request_info->request_body_size(); metadata["upload size (bytes)"] =
base::NumberToString(request_info->request_body_size());
} }
devtools_context_->LogBackgroundServiceEvent( devtools_context_->LogBackgroundServiceEvent(
......
...@@ -57,8 +57,10 @@ ProtoMapToArray( ...@@ -57,8 +57,10 @@ ProtoMapToArray(
std::unique_ptr<protocol::BackgroundService::BackgroundServiceEvent> std::unique_ptr<protocol::BackgroundService::BackgroundServiceEvent>
ToBackgroundServiceEvent(const devtools::proto::BackgroundServiceEvent& event) { ToBackgroundServiceEvent(const devtools::proto::BackgroundServiceEvent& event) {
base::Time timestamp = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(event.timestamp()));
return protocol::BackgroundService::BackgroundServiceEvent::Create() return protocol::BackgroundService::BackgroundServiceEvent::Create()
.SetTimestamp(event.timestamp() / 1'000'000) // microseconds -> seconds .SetTimestamp(timestamp.ToJsTime() / 1'000) // milliseconds -> seconds
.SetOrigin(event.origin()) .SetOrigin(event.origin())
.SetServiceWorkerRegistrationId( .SetServiceWorkerRegistrationId(
base::NumberToString(event.service_worker_registration_id())) base::NumberToString(event.service_worker_registration_id()))
......
...@@ -13,12 +13,16 @@ Resources.BackgroundServiceModel = class extends SDK.SDKModel { ...@@ -13,12 +13,16 @@ Resources.BackgroundServiceModel = class extends SDK.SDKModel {
super(target); super(target);
this._backgroundServiceAgent = target.backgroundServiceAgent(); this._backgroundServiceAgent = target.backgroundServiceAgent();
target.registerBackgroundServiceDispatcher(this); target.registerBackgroundServiceDispatcher(this);
/** @const {!Map<!Protocol.BackgroundService.ServiceName, Array<!Protocol.BackgroundService.BackgroundServiceEvent>>} */
this._events = new Map();
} }
/** /**
* @param {!Protocol.BackgroundService.ServiceName} serviceName * @param {!Protocol.BackgroundService.ServiceName} serviceName
*/ */
enable(serviceName) { enable(serviceName) {
this._events.set(serviceName, []);
this._backgroundServiceAgent.startObserving(serviceName); this._backgroundServiceAgent.startObserving(serviceName);
} }
...@@ -34,9 +38,18 @@ Resources.BackgroundServiceModel = class extends SDK.SDKModel { ...@@ -34,9 +38,18 @@ Resources.BackgroundServiceModel = class extends SDK.SDKModel {
* @param {!Protocol.BackgroundService.ServiceName} serviceName * @param {!Protocol.BackgroundService.ServiceName} serviceName
*/ */
clearEvents(serviceName) { clearEvents(serviceName) {
this._events.set(serviceName, []);
this._backgroundServiceAgent.clearEvents(serviceName); this._backgroundServiceAgent.clearEvents(serviceName);
} }
/**
* @param {!Protocol.BackgroundService.ServiceName} serviceName
* @return {!Array<!Protocol.BackgroundService.BackgroundServiceEvent>}
*/
getEvents(serviceName) {
return this._events.get(serviceName) || [];
}
/** /**
* @override * @override
* @param {boolean} isRecording * @param {boolean} isRecording
...@@ -52,6 +65,7 @@ Resources.BackgroundServiceModel = class extends SDK.SDKModel { ...@@ -52,6 +65,7 @@ Resources.BackgroundServiceModel = class extends SDK.SDKModel {
* @param {!Protocol.BackgroundService.BackgroundServiceEvent} backgroundServiceEvent * @param {!Protocol.BackgroundService.BackgroundServiceEvent} backgroundServiceEvent
*/ */
backgroundServiceEventReceived(backgroundServiceEvent) { backgroundServiceEventReceived(backgroundServiceEvent) {
this._events.get(backgroundServiceEvent.service).push(backgroundServiceEvent);
this.dispatchEventToListeners( this.dispatchEventToListeners(
Resources.BackgroundServiceModel.Events.BackgroundServiceEventReceived, backgroundServiceEvent); Resources.BackgroundServiceModel.Events.BackgroundServiceEventReceived, backgroundServiceEvent);
} }
......
...@@ -28,6 +28,10 @@ Resources.BackgroundServiceView = class extends UI.VBox { ...@@ -28,6 +28,10 @@ Resources.BackgroundServiceView = class extends UI.VBox {
/** @const {!UI.Toolbar} */ /** @const {!UI.Toolbar} */
this._toolbar = new UI.Toolbar('background-service-toolbar', this.contentElement); this._toolbar = new UI.Toolbar('background-service-toolbar', this.contentElement);
this._setupToolbar(); this._setupToolbar();
/** @const {!DataGrid.DataGrid} */
this._dataGrid = this._createDataGrid();
this._dataGrid.asWidget().show(this.contentElement);
} }
/** /**
...@@ -41,17 +45,17 @@ Resources.BackgroundServiceView = class extends UI.VBox { ...@@ -41,17 +45,17 @@ Resources.BackgroundServiceView = class extends UI.VBox {
this._toolbar.appendToolbarItem(this._recordButton); this._toolbar.appendToolbarItem(this._recordButton);
const refreshButton = new UI.ToolbarButton(Common.UIString('Refresh'), 'largeicon-refresh'); const refreshButton = new UI.ToolbarButton(Common.UIString('Refresh'), 'largeicon-refresh');
refreshButton.addEventListener(UI.ToolbarButton.Events.Click, () => {}); refreshButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._refreshView());
this._toolbar.appendToolbarItem(refreshButton); this._toolbar.appendToolbarItem(refreshButton);
const clearButton = new UI.ToolbarButton(Common.UIString('Clear'), 'largeicon-clear'); const clearButton = new UI.ToolbarButton(Common.UIString('Clear'), 'largeicon-clear');
clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => {}); clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._clearView());
this._toolbar.appendToolbarItem(clearButton); this._toolbar.appendToolbarItem(clearButton);
this._toolbar.appendSeparator(); this._toolbar.appendSeparator();
const deleteButton = new UI.ToolbarButton(Common.UIString('Delete'), 'largeicon-trash-bin'); const deleteButton = new UI.ToolbarButton(Common.UIString('Delete'), 'largeicon-trash-bin');
deleteButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._model.clearEvents(this._serviceName)); deleteButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._deleteEvents());
this._toolbar.appendToolbarItem(deleteButton); this._toolbar.appendToolbarItem(deleteButton);
} }
...@@ -62,6 +66,31 @@ Resources.BackgroundServiceView = class extends UI.VBox { ...@@ -62,6 +66,31 @@ Resources.BackgroundServiceView = class extends UI.VBox {
this._model.setRecording(!this._recordButton.toggled(), this._serviceName); this._model.setRecording(!this._recordButton.toggled(), this._serviceName);
} }
/**
* Called when the `Refresh` button is clicked.
*/
_refreshView() {
this._clearView();
const events = this._model.getEvents(this._serviceName).filter(event => this._acceptEvent(event));
for (const event of events)
this._addEvent(event);
}
/**
* Called when the `Clear` button is clicked.
*/
_clearView() {
this._dataGrid.rootNode().removeChildren();
}
/**
* Called when the `Delete` button is clicked.
*/
_deleteEvents() {
this._model.clearEvents(this._serviceName);
this._clearView();
}
/** /**
* @param {!Common.Event} event * @param {!Common.Event} event
*/ */
...@@ -77,7 +106,127 @@ Resources.BackgroundServiceView = class extends UI.VBox { ...@@ -77,7 +106,127 @@ Resources.BackgroundServiceView = class extends UI.VBox {
*/ */
_onEventReceived(event) { _onEventReceived(event) {
const serviceEvent = /** @type {!Protocol.BackgroundService.BackgroundServiceEvent} */ (event.data); const serviceEvent = /** @type {!Protocol.BackgroundService.BackgroundServiceEvent} */ (event.data);
if (serviceEvent.service !== this._serviceName) if (!this._acceptEvent(serviceEvent))
return; return;
this._addEvent(serviceEvent);
}
/**
* @param {!Protocol.BackgroundService.BackgroundServiceEvent} serviceEvent
*/
_addEvent(serviceEvent) {
const numEvents = this._dataGrid.rootNode().children.length;
const dataNode = new Resources.BackgroundServiceView.EventDataNode(numEvents, serviceEvent);
this._dataGrid.rootNode().appendChild(dataNode);
}
/**
* @return {!DataGrid.DataGrid}
*/
_createDataGrid() {
const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([
{id: 'id', title: Common.UIString('#'), weight: 1},
{id: 'timestamp', title: Common.UIString('Timestamp'), weight: 8},
{id: 'origin', title: Common.UIString('Origin'), weight: 10},
{id: 'swid', title: Common.UIString('SW ID'), weight: 2},
{id: 'eventName', title: Common.UIString('Event'), weight: 10},
{id: 'instanceId', title: Common.UIString('Instance ID'), weight: 10},
]);
const dataGrid = new DataGrid.DataGrid(columns);
dataGrid.setStriped(true);
return dataGrid;
}
/**
* Filtration function to know whether event should be shown or not.
* @param {!Protocol.BackgroundService.BackgroundServiceEvent} event
* @return {boolean}
*/
_acceptEvent(event) {
return event.service === this._serviceName;
}
};
Resources.BackgroundServiceView.EventDataNode = class extends DataGrid.DataGridNode {
/**
* @param {number} eventId
* @param {!Protocol.BackgroundService.BackgroundServiceEvent} serviceEvent
*/
constructor(eventId, serviceEvent) {
super(Resources.BackgroundServiceView.EventDataNode._createGridData(eventId, serviceEvent));
/** @const {!Array<!Protocol.BackgroundService.EventMetadata>} */
this._eventMetadata = serviceEvent.eventMetadata;
/** @type {?UI.PopoverHelper} */
this._popoverHelper = null;
}
/**
* @param {number} eventId
* @param {!Protocol.BackgroundService.BackgroundServiceEvent} serviceEvent
* @return {!Object<string, string>}
*/
static _createGridData(eventId, serviceEvent) {
return {
id: eventId,
timestamp: new Date(serviceEvent.timestamp * 1000).toLocaleString(),
origin: serviceEvent.origin,
swid: serviceEvent.serviceWorkerRegistrationId,
eventName: serviceEvent.eventName,
instanceId: serviceEvent.instanceId,
};
}
/**
* @override
* @return {!Element}
*/
createElement() {
const element = super.createElement();
this._popoverHelper = new UI.PopoverHelper(element, event => this._createPopover(event));
this._popoverHelper.setHasPadding(true);
this._popoverHelper.setTimeout(300, 300);
return element;
}
/**
* @param {!Event} event
* @return {?UI.PopoverRequest}
*/
_createPopover(event) {
if (event.type !== 'mousedown')
return null;
// Create popover container.
const container = createElementWithClass('div', 'background-service-popover-container');
UI.appendStyle(container, 'resources/backgroundServiceView.css');
if (!this._eventMetadata.length) {
const entryDiv = createElementWithClass('div', 'background-service-metadata-entry');
entryDiv.textContent = 'There is no metadata for this event';
container.appendChild(entryDiv);
}
for (const entry of this._eventMetadata) {
const entryDiv = createElementWithClass('div', 'background-service-metadata-entry');
const key = createElementWithClass('label', 'background-service-metadata-key');
key.textContent = `${entry.key}: `;
const value = createElementWithClass('label', 'background-service-metadata-value');
value.textContent = entry.value;
entryDiv.appendChild(key);
entryDiv.appendChild(value);
container.appendChild(entryDiv);
}
return {
box: event.target.boxInWindow(),
show: popover => {
popover.contentElement.appendChild(container);
return Promise.resolve(true);
},
};
} }
}; };
.background-service-toolbar { .background-service-toolbar {
background-color: var(--toolbar-bg-color); background-color: var(--toolbar-bg-color);
border-bottom: var(--divider-border); border-bottom: var(--divider-border);
} }
\ No newline at end of file
.data-grid {
flex: auto;
}
.background-service-popover-container {
padding: 12px 16px 0px 16px;
}
.background-service-metadata-entry {
margin-bottom: 12px;
}
.background-service-metadata-key {
font-weight: bold;
}
.background-service-metadata-value {
font-family: monospace;
}
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