Index: third_party/WebKit/Source/devtools/front_end/coverage/CoverageDecorationManager.js |
diff --git a/third_party/WebKit/Source/devtools/front_end/coverage/CoverageDecorationManager.js b/third_party/WebKit/Source/devtools/front_end/coverage/CoverageDecorationManager.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..47f8e1924e0a8752ca89b1f74012b0f89d3eef35 |
--- /dev/null |
+++ b/third_party/WebKit/Source/devtools/front_end/coverage/CoverageDecorationManager.js |
@@ -0,0 +1,179 @@ |
+// Copyright (c) 2017 The Chromium Authors. All rights reserved. |
dgozman
2017/05/05 19:21:19
no (c)
|
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+Coverage.CoverageDecorationManager = class { |
+ /** |
+ * @param {!Coverage.CoverageModel} coverageModel |
+ */ |
+ constructor(coverageModel) { |
+ this._coverageModel = coverageModel; |
+ /** @type !Map<!Common.ContentProvider, ?TextUtils.Text> */ |
+ this._textByProvider = new Map(); |
+ /** @type !Multimap<!Common.ContentProvider, !Workspace.UISourceCode> */ |
+ this._uiSourceCodeByContentProvider = new Multimap(); |
+ |
+ for (var project of Workspace.workspace.projects()) { |
dgozman
2017/05/05 19:21:19
Use Workspace.uiSourceCodes().
|
+ for (var uiSourceCode of project.uiSourceCodes()) |
+ uiSourceCode.addLineDecoration(0, Coverage.CoverageDecorationManager._decoratorType, this); |
+ } |
+ Workspace.workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeAdded, this._onUISourceCodeAdded, this); |
+ } |
+ |
+ dispose() { |
+ for (var project of Workspace.workspace.projects()) { |
+ for (var uiSourceCode of project.uiSourceCodes()) |
dgozman
2017/05/05 19:21:19
ditto
|
+ uiSourceCode.removeDecorationsForType(Coverage.CoverageDecorationManager._decoratorType); |
+ } |
+ Workspace.workspace.removeEventListener( |
+ Workspace.Workspace.Events.UISourceCodeAdded, this._onUISourceCodeAdded, this); |
+ } |
+ |
+ /** |
+ * @param {!Array<!Coverage.CoverageInfo>} updatedEntries |
+ */ |
+ update(updatedEntries) { |
+ for (var entry of updatedEntries) { |
+ for (var uiSourceCode of this._uiSourceCodeByContentProvider.get(entry.contentProvider())) { |
+ uiSourceCode.removeDecorationsForType(Coverage.CoverageDecorationManager._decoratorType); |
+ uiSourceCode.addLineDecoration(0, Coverage.CoverageDecorationManager._decoratorType, this); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * @param {!Workspace.UISourceCode} uiSourceCode |
+ * @return {!Promise<!Array<boolean>>} |
+ */ |
+ async usageByLine(uiSourceCode) { |
+ var result = []; |
+ var sourceText = new TextUtils.Text(uiSourceCode.content() || ''); |
+ await this._updateTexts(uiSourceCode, sourceText); |
+ var lineEndings = sourceText.lineEndings(); |
+ for (var line = 0; line < sourceText.lineCount(); ++line) { |
+ var lineLength = lineEndings[line] - (line ? lineEndings[line - 1] : 0); |
+ var startLocations = this._rawLocationsForSourceLocation(uiSourceCode, line, 0); |
+ var endLocations = this._rawLocationsForSourceLocation(uiSourceCode, line, lineLength - 1); |
+ var startLocationsByProvider = new Map(startLocations.map(location => [location.contentProvider, location])); |
alph
2017/05/05 19:18:08
can it have several locations with he same provide
|
+ var used = false; |
+ for (var end of endLocations) { |
alph
2017/05/05 19:18:08
nit: var used = endLocations.some(...);
;-)
|
+ var start = startLocationsByProvider.get(end.contentProvider); |
+ var text = this._textByProvider.get(end.contentProvider); |
+ if (!start || !text) |
+ continue; |
+ var textValue = text.value(); |
+ var startOffset = text.offsetFromPosition(start.line, start.column); |
+ var endOffset = text.offsetFromPosition(end.line, end.column); |
+ while (startOffset < endOffset && /\s/.test(textValue[startOffset])) |
alph
2017/05/05 19:18:08
Have you considered using substr and match with /^
alph
2017/05/05 19:18:08
<=
|
+ ++startOffset; |
+ while (startOffset < endOffset && /\s/.test(textValue[endOffset])) |
alph
2017/05/05 19:18:08
ditto
|
+ --endOffset; |
+ used = this._coverageModel.usageForRange(end.contentProvider, startOffset, endOffset); |
+ if (used) |
+ break; |
+ } |
+ result.push(used); |
+ } |
+ return result; |
+ } |
+ |
+ /** |
+ * @param {!Workspace.UISourceCode} uiSourceCode |
+ * @param {!TextUtils.Text} text |
+ * @return !Promise |
alph
2017/05/05 19:18:08
I'm ok with using the clean syntax, but I know cou
|
+ */ |
+ _updateTexts(uiSourceCode, text) { |
+ var promises = []; |
+ for (var line = 0; line < text.lineCount(); ++line) { |
+ for (var entry of this._rawLocationsForSourceLocation(uiSourceCode, line, 0)) { |
+ if (!this._textByProvider.has(entry.contentProvider)) { |
alph
2017/05/05 19:18:08
if (...) continue;
|
+ this._textByProvider.set(entry.contentProvider, null); |
+ this._uiSourceCodeByContentProvider.set(entry.contentProvider, uiSourceCode); |
+ promises.push(this._updateTextForProvider(entry.contentProvider)); |
+ } |
+ } |
+ } |
+ return Promise.all(promises); |
+ } |
+ |
+ /** |
+ * @param {!Common.ContentProvider} contentProvider |
alph
2017/05/05 19:18:08
@return
|
+ */ |
+ async _updateTextForProvider(contentProvider) { |
+ var content = await contentProvider.requestContent(); |
+ this._textByProvider.set(contentProvider, new TextUtils.Text(content)); |
+ } |
+ |
+ /** |
+ * @param {!Workspace.UISourceCode} uiSourceCode |
+ * @param {number} line |
+ * @param {number} column |
+ * @return {!Array<!{contentProvider: !Common.ContentProvider, line: number, column: number}>} |
+ */ |
+ _rawLocationsForSourceLocation(uiSourceCode, line, column) { |
+ if (uiSourceCode.contentType().hasScripts()) { |
+ var location = Bindings.debuggerWorkspaceBinding.uiLocationToRawLocation(uiSourceCode, line, column); |
+ return location && location.script() ? |
dgozman
2017/05/05 19:21:19
html file could have both css and js locations.
|
+ [{contentProvider: location.script(), line: location.lineNumber, column: location.columnNumber}] : |
+ []; |
+ } |
+ var rawStyleLocations = |
+ Bindings.cssWorkspaceBinding.uiLocationToRawLocations(new Workspace.UILocation(uiSourceCode, line, 0)); |
+ var result = []; |
+ for (var location of rawStyleLocations) { |
+ if (!location.header()) |
+ continue; |
+ result.push({contentProvider: location.header(), line: location.lineNumber, column: location.columnNumber}); |
+ } |
+ return result; |
+ } |
+ |
+ /** |
+ * @param {!Common.Event} event |
+ */ |
+ _onUISourceCodeAdded(event) { |
+ var uiSourceCode = /** @type !Workspace.UISourceCode */ (event.data); |
+ uiSourceCode.addLineDecoration(0, Coverage.CoverageDecorationManager._decoratorType, this); |
+ } |
+}; |
+ |
+Coverage.CoverageDecorationManager._decoratorType = 'coverage'; |
+ |
+/** |
+ * @implements {SourceFrame.UISourceCodeFrame.LineDecorator} |
+ */ |
+Coverage.CoverageView.LineDecorator = class { |
+ /** |
+ * @override |
+ * @param {!Workspace.UISourceCode} uiSourceCode |
+ * @param {!TextEditor.CodeMirrorTextEditor} textEditor |
+ */ |
+ decorate(uiSourceCode, textEditor) { |
+ var decorations = uiSourceCode.decorationsForType(Coverage.CoverageDecorationManager._decoratorType); |
+ if (!decorations.size) { |
+ textEditor.uninstallGutter(Coverage.CoverageView.LineDecorator._gutterType); |
+ return; |
+ } |
+ var decorationManager = |
+ /** @type {!Coverage.CoverageDecorationManager} */ (decorations.values().next().value.data()); |
+ decorationManager.usageByLine(uiSourceCode).then(lineUsage => { |
+ textEditor.operation(() => this._innerDecorate(textEditor, lineUsage)); |
+ }); |
+ } |
+ |
+ /** |
+ * @param {!TextEditor.CodeMirrorTextEditor} textEditor |
+ * @param {!Array<boolean>} lineUsage |
+ */ |
+ _innerDecorate(textEditor, lineUsage) { |
+ var gutterType = Coverage.CoverageView.LineDecorator._gutterType; |
+ textEditor.uninstallGutter(gutterType); |
+ textEditor.installGutter(gutterType, false); |
+ for (var line = 0; line < lineUsage.length; ++line) { |
+ var className = lineUsage[line] ? 'text-editor-coverage-used-marker' : 'text-editor-coverage-unused-marker'; |
+ textEditor.setGutterDecoration(line, gutterType, createElementWithClass('div', className)); |
+ } |
+ } |
+}; |
+ |
+Coverage.CoverageView.LineDecorator._gutterType = 'CodeMirror-gutter-coverage'; |