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(
// Include common request metadata.
if (request_info) {
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())
metadata["upload size (bytes)"] = request_info->request_body_size();
metadata["upload size (bytes)"] =
base::NumberToString(request_info->request_body_size());
}
devtools_context_->LogBackgroundServiceEvent(
......
......@@ -57,8 +57,10 @@ ProtoMapToArray(
std::unique_ptr<protocol::BackgroundService::BackgroundServiceEvent>
ToBackgroundServiceEvent(const devtools::proto::BackgroundServiceEvent& event) {
base::Time timestamp = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(event.timestamp()));
return protocol::BackgroundService::BackgroundServiceEvent::Create()
.SetTimestamp(event.timestamp() / 1'000'000) // microseconds -> seconds
.SetTimestamp(timestamp.ToJsTime() / 1'000) // milliseconds -> seconds
.SetOrigin(event.origin())
.SetServiceWorkerRegistrationId(
base::NumberToString(event.service_worker_registration_id()))
......
......@@ -13,12 +13,16 @@ Resources.BackgroundServiceModel = class extends SDK.SDKModel {
super(target);
this._backgroundServiceAgent = target.backgroundServiceAgent();
target.registerBackgroundServiceDispatcher(this);
/** @const {!Map<!Protocol.BackgroundService.ServiceName, Array<!Protocol.BackgroundService.BackgroundServiceEvent>>} */
this._events = new Map();
}
/**
* @param {!Protocol.BackgroundService.ServiceName} serviceName
*/
enable(serviceName) {
this._events.set(serviceName, []);
this._backgroundServiceAgent.startObserving(serviceName);
}
......@@ -34,9 +38,18 @@ Resources.BackgroundServiceModel = class extends SDK.SDKModel {
* @param {!Protocol.BackgroundService.ServiceName} serviceName
*/
clearEvents(serviceName) {
this._events.set(serviceName, []);
this._backgroundServiceAgent.clearEvents(serviceName);
}
/**
* @param {!Protocol.BackgroundService.ServiceName} serviceName
* @return {!Array<!Protocol.BackgroundService.BackgroundServiceEvent>}
*/
getEvents(serviceName) {
return this._events.get(serviceName) || [];
}
/**
* @override
* @param {boolean} isRecording
......@@ -52,6 +65,7 @@ Resources.BackgroundServiceModel = class extends SDK.SDKModel {
* @param {!Protocol.BackgroundService.BackgroundServiceEvent} backgroundServiceEvent
*/
backgroundServiceEventReceived(backgroundServiceEvent) {
this._events.get(backgroundServiceEvent.service).push(backgroundServiceEvent);
this.dispatchEventToListeners(
Resources.BackgroundServiceModel.Events.BackgroundServiceEventReceived, backgroundServiceEvent);
}
......
......@@ -28,6 +28,10 @@ Resources.BackgroundServiceView = class extends UI.VBox {
/** @const {!UI.Toolbar} */
this._toolbar = new UI.Toolbar('background-service-toolbar', this.contentElement);
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 {
this._toolbar.appendToolbarItem(this._recordButton);
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);
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.appendSeparator();
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);
}
......@@ -62,6 +66,31 @@ Resources.BackgroundServiceView = class extends UI.VBox {
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
*/
......@@ -77,7 +106,127 @@ Resources.BackgroundServiceView = class extends UI.VBox {
*/
_onEventReceived(event) {
const serviceEvent = /** @type {!Protocol.BackgroundService.BackgroundServiceEvent} */ (event.data);
if (serviceEvent.service !== this._serviceName)
if (!this._acceptEvent(serviceEvent))
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-color: var(--toolbar-bg-color);
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