blob: d52c39142251de25ffc365940d564254aa28a51d [file] [log] [blame]
// Copyright (c) 2016 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.
Coverage.CoverageView = class extends UI.VBox {
constructor() {
super(true);
/** @type {?Coverage.CoverageModel} */
this._model = null;
this.registerRequiredCSS('coverage/coverageView.css');
var toolbarContainer = this.contentElement.createChild('div', 'coverage-toolbar-container');
var topToolbar = new UI.Toolbar('coverage-toolbar', toolbarContainer);
this._toggleRecordAction =
/** @type {!UI.Action }*/ (UI.actionRegistry.action('coverage.toggle-recording'));
topToolbar.appendToolbarItem(UI.Toolbar.createActionButton(this._toggleRecordAction));
var clearButton = new UI.ToolbarButton(Common.UIString('Clear all'), 'largeicon-clear');
clearButton.addEventListener(UI.ToolbarButton.Events.Click, this._reset.bind(this));
topToolbar.appendToolbarItem(clearButton);
this._coverageResultsElement = this.contentElement.createChild('div', 'coverage-results');
this._progressElement = this._coverageResultsElement.createChild('div', 'progress-view');
this._listView = new Coverage.CoverageListView();
this._statusToolbarElement = this.contentElement.createChild('div', 'coverage-toolbar-summary');
this._statusMessageElement = this._statusToolbarElement.createChild('div', 'coverage-message');
}
_reset() {
Workspace.workspace.uiSourceCodes().forEach(
uiSourceCode => uiSourceCode.removeDecorationsForType(Coverage.CoverageView.LineDecorator.type));
this._listView.detach();
this._coverageResultsElement.removeChildren();
this._progressElement.textContent = '';
this._coverageResultsElement.appendChild(this._progressElement);
this._statusMessageElement.textContent = '';
}
_toggleRecording() {
var enable = !this._toggleRecordAction.toggled();
if (enable)
this._startRecording();
else
this._stopRecording();
}
_startRecording() {
this._reset();
var mainTarget = SDK.targetManager.mainTarget();
if (!mainTarget)
return;
console.assert(!this._model, 'Attempting to start coverage twice');
var model = new Coverage.CoverageModel(mainTarget);
if (!model.start())
return;
this._model = model;
this._toggleRecordAction.setToggled(true);
this._progressElement.textContent = Common.UIString('Recording...');
}
async _stopRecording() {
this._toggleRecordAction.setToggled(false);
this._progressElement.textContent = Common.UIString('Fetching results...');
var coverageInfo = await this._model.stop();
this._model = null;
this._updateViews(coverageInfo);
}
/**
* @param {!Array<!Coverage.URLCoverageInfo>} coverageInfo
*/
async _updateViews(coverageInfo) {
this._updateStats(coverageInfo);
this._coverageResultsElement.removeChildren();
this._listView.update(coverageInfo);
this._listView.show(this._coverageResultsElement);
await Promise.all(coverageInfo.map(entry => Coverage.CoverageView._updateGutter(entry)));
}
/**
* @param {!Array<!Coverage.URLCoverageInfo>} coverageInfo
*/
_updateStats(coverageInfo) {
var total = 0;
var unused = 0;
for (var info of coverageInfo) {
total += info.size();
unused += info.unusedSize();
}
var percentUnused = total ? Math.round(100 * unused / total) : 0;
this._statusMessageElement.textContent = Common.UIString(
'%s of %s bytes are not used. (%d%%)', Number.bytesToString(unused), Number.bytesToString(total),
percentUnused);
}
/**
* @param {!Coverage.URLCoverageInfo} coverageInfo
*/
static async _updateGutter(coverageInfo) {
var uiSourceCode = Workspace.workspace.uiSourceCodeForURL(coverageInfo.url());
if (!uiSourceCode)
return;
// FIXME: gutter should be set in terms of offsets and therefore should not require contents.
var ranges = await coverageInfo.buildTextRanges();
for (var r of ranges)
uiSourceCode.addDecoration(r.range, Coverage.CoverageView.LineDecorator.type, r.count);
}
};
/**
* @implements {SourceFrame.UISourceCodeFrame.LineDecorator}
*/
Coverage.CoverageView.LineDecorator = class {
/**
* @override
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {!TextEditor.CodeMirrorTextEditor} textEditor
*/
decorate(uiSourceCode, textEditor) {
var gutterType = 'CodeMirror-gutter-coverage';
var decorations = uiSourceCode.decorationsForType(Coverage.CoverageView.LineDecorator.type);
textEditor.uninstallGutter(gutterType);
if (!decorations || !decorations.size)
return;
textEditor.installGutter(gutterType, false);
var lastLine = 0;
var lastData = undefined;
for (var decoration of decorations) {
var range = decoration.range();
var startLine = range.startLine;
if (lastLine && lastLine === startLine && lastData !== !!decoration.data()) {
var element = createElementWithClass('div', 'text-editor-coverage-mixed-marker');
textEditor.setGutterDecoration(startLine, gutterType, element);
startLine++;
} else {
startLine = Math.max(startLine, lastLine);
}
lastLine = range.endLine;
lastData = !!decoration.data();
var className = lastData ? 'text-editor-coverage-used-marker' : 'text-editor-coverage-unused-marker';
for (var line = startLine; line <= lastLine; ++line) {
var element = createElementWithClass('div', className);
textEditor.setGutterDecoration(line, gutterType, element);
}
}
}
};
Coverage.CoverageView.LineDecorator.type = 'coverage';
/**
* @implements {UI.ActionDelegate}
*/
Coverage.CoverageView.RecordActionDelegate = class {
/**
* @override
* @param {!UI.Context} context
* @param {string} actionId
* @return {boolean}
*/
handleAction(context, actionId) {
var coverageViewId = 'coverage';
UI.viewManager.showView(coverageViewId)
.then(() => UI.viewManager.view(coverageViewId).widget())
.then(widget => /** @type !Coverage.CoverageView} */ (widget)._toggleRecording());
return true;
}
};