Index: third_party/WebKit/Source/devtools/front_end/coverage/CoverageModel.js |
diff --git a/third_party/WebKit/Source/devtools/front_end/coverage/CoverageModel.js b/third_party/WebKit/Source/devtools/front_end/coverage/CoverageModel.js |
index 61d9482b40daf348f5a0937b65f88a3eda4c482a..5b9421f3caef37376e6c2f409d5a3629529cc3c3 100644 |
--- a/third_party/WebKit/Source/devtools/front_end/coverage/CoverageModel.js |
+++ b/third_party/WebKit/Source/devtools/front_end/coverage/CoverageModel.js |
@@ -8,19 +8,6 @@ Coverage.RangeUseCount; |
/** @typedef {{end: number, count: (number|undefined), depth: number}} */ |
Coverage.CoverageSegment; |
-/** @typedef {{ |
- * contentProvider: !Common.ContentProvider, |
- * size: number, |
- * unusedSize: number, |
- * usedSize: number, |
- * type: !Coverage.CoverageType, |
- * lineOffset: number, |
- * columnOffset: number, |
- * segments: !Array<!Coverage.CoverageSegment> |
- * }} |
- */ |
-Coverage.CoverageInfo; |
- |
/** |
* @enum {number} |
*/ |
@@ -35,15 +22,19 @@ Coverage.CoverageModel = class extends SDK.SDKModel { |
*/ |
constructor(target) { |
super(target); |
- this._target = target; |
- this._cpuProfilerModel = this._target.model(SDK.CPUProfilerModel); |
- this._cssModel = this._target.model(SDK.CSSModel); |
+ this._cpuProfilerModel = target.model(SDK.CPUProfilerModel); |
+ this._cssModel = target.model(SDK.CSSModel); |
+ this._debuggerModel = target.model(SDK.DebuggerModel); |
+ |
+ /** @type {!Map<string, !Coverage.URLCoverageInfo>} */ |
+ this._coverageByURL = new Map(); |
} |
/** |
* @return {boolean} |
*/ |
start() { |
+ this._coverageByURL.clear(); |
if (this._cssModel) |
this._cssModel.startRuleUsageTracking(); |
if (this._cpuProfilerModel) |
@@ -52,62 +43,28 @@ Coverage.CoverageModel = class extends SDK.SDKModel { |
} |
/** |
- * @return {!Promise<!Array<!Coverage.CoverageInfo>>} |
+ * @return {!Promise<!Array<!Coverage.URLCoverageInfo>>} |
*/ |
async stop() { |
- var cssCoverageInfoPromise = this._stopCSSCoverage(); |
- var jsCoverageInfoPromise = this._stopJSCoverage(); |
- var cssCoverageInfo = await cssCoverageInfoPromise; |
- var jsCoverageInfo = await jsCoverageInfoPromise; |
- return Coverage.CoverageModel._coalesceByURL(cssCoverageInfo.concat(jsCoverageInfo)); |
+ await Promise.all([this._stopCSSCoverage(), this._stopJSCoverage()]); |
+ return Array.from(this._coverageByURL.values()); |
} |
- /** |
- * @param {!Array<!Coverage.CoverageInfo>} coverageInfo |
- * @return {!Array<!Coverage.CoverageInfo>} |
- */ |
- static _coalesceByURL(coverageInfo) { |
- coverageInfo.sort((a, b) => (a.contentProvider.contentURL() || '').localeCompare(b.contentProvider.contentURL())); |
- var result = []; |
- for (var entry of coverageInfo) { |
- var url = entry.contentProvider.contentURL(); |
- if (!url) |
- continue; |
- if (result.length && result.peekLast().contentProvider.contentURL() === url) { |
- var lastEntry = result.peekLast(); |
- lastEntry.size += entry.size; |
- lastEntry.usedSize += entry.usedSize; |
- lastEntry.unusedSize += entry.unusedSize; |
- lastEntry.type |= entry.type; |
- } else { |
- result.push(entry); |
- } |
- } |
- return result; |
- } |
- |
- /** |
- * @return {!Promise<!Array<!Coverage.CoverageInfo>>} |
- */ |
async _stopJSCoverage() { |
if (!this._cpuProfilerModel) |
- return []; |
+ return; |
var coveragePromise = this._cpuProfilerModel.takePreciseCoverage(); |
this._cpuProfilerModel.stopPreciseCoverage(); |
var rawCoverageData = await coveragePromise; |
- return Coverage.CoverageModel._processJSCoverage( |
- /** @type !SDK.DebuggerModel */ (SDK.DebuggerModel.fromTarget(this.target())), rawCoverageData); |
+ this._processJSCoverage(rawCoverageData); |
} |
/** |
- * @param {!SDK.DebuggerModel} debuggerModel |
* @param {!Array<!Protocol.Profiler.ScriptCoverage>} scriptsCoverage |
- * @return {!Array<!Coverage.CoverageInfo>} |
*/ |
- static _processJSCoverage(debuggerModel, scriptsCoverage) { |
- var result = []; |
+ _processJSCoverage(scriptsCoverage) { |
for (var entry of scriptsCoverage) { |
- var script = debuggerModel.scriptForId(entry.scriptId); |
+ var script = this._debuggerModel.scriptForId(entry.scriptId); |
if (!script) |
continue; |
var ranges = []; |
@@ -116,10 +73,8 @@ Coverage.CoverageModel = class extends SDK.SDKModel { |
ranges.push(range); |
} |
ranges.sort((a, b) => a.startOffset - b.startOffset); |
- result.push(Coverage.CoverageModel._buildCoverageInfo( |
- script, script.contentLength, script.lineOffset, script.columnOffset, ranges)); |
+ this._addCoverage(script, script.contentLength, script.lineOffset, script.columnOffset, ranges); |
} |
- return result; |
} |
/** |
@@ -168,28 +123,24 @@ Coverage.CoverageModel = class extends SDK.SDKModel { |
return result; |
} |
- /** |
- * @return {!Promise<!Array<!Coverage.CoverageInfo>>} |
- */ |
async _stopCSSCoverage() { |
if (!this._cssModel) |
return []; |
var rawCoverageData = await this._cssModel.ruleListPromise(); |
- return Coverage.CoverageModel._processCSSCoverage( |
- /** @type !SDK.CSSModel */ (this._cssModel), rawCoverageData); |
+ this._processCSSCoverage(rawCoverageData); |
} |
/** |
- * @param {!SDK.CSSModel} cssModel |
* @param {!Array<!Protocol.CSS.RuleUsage>} ruleUsageList |
- * @return {!Array<!Coverage.CoverageInfo>} |
*/ |
- static _processCSSCoverage(cssModel, ruleUsageList) { |
- /** @type {!Map<?SDK.CSSStyleSheetHeader, !Array<!Coverage.RangeUseCount>>} */ |
+ _processCSSCoverage(ruleUsageList) { |
+ /** @type {!Map<!SDK.CSSStyleSheetHeader, !Array<!Coverage.RangeUseCount>>} */ |
var rulesByStyleSheet = new Map(); |
for (var rule of ruleUsageList) { |
- var styleSheetHeader = cssModel.styleSheetHeaderForId(rule.styleSheetId); |
+ var styleSheetHeader = this._cssModel.styleSheetHeaderForId(rule.styleSheetId); |
+ if (!styleSheetHeader) |
+ continue; |
var ranges = rulesByStyleSheet.get(styleSheetHeader); |
if (!ranges) { |
ranges = []; |
@@ -197,10 +148,13 @@ Coverage.CoverageModel = class extends SDK.SDKModel { |
} |
ranges.push({startOffset: rule.startOffset, endOffset: rule.endOffset, count: Number(rule.used)}); |
} |
- return Array.from( |
- rulesByStyleSheet.entries(), |
- entry => Coverage.CoverageModel._buildCoverageInfo( |
- entry[0], entry[0].contentLength, entry[0].startLine, entry[0].startColumn, entry[1])); |
+ for (var entry of rulesByStyleSheet) { |
+ var styleSheetHeader = /** @type {!SDK.CSSStyleSheetHeader} */ (entry[0]); |
+ var ranges = /** @type {!Array<!Coverage.RangeUseCount>} */ (entry[1]); |
+ this._addCoverage( |
+ styleSheetHeader, styleSheetHeader.contentLength, styleSheetHeader.startLine, styleSheetHeader.startColumn, |
+ ranges); |
+ } |
} |
/** |
@@ -209,42 +163,238 @@ Coverage.CoverageModel = class extends SDK.SDKModel { |
* @param {number} startLine |
* @param {number} startColumn |
* @param {!Array<!Coverage.RangeUseCount>} ranges |
- * @return {!Coverage.CoverageInfo} |
*/ |
- static _buildCoverageInfo(contentProvider, contentLength, startLine, startColumn, ranges) { |
- /** @type Coverage.CoverageType */ |
- var coverageType; |
+ _addCoverage(contentProvider, contentLength, startLine, startColumn, ranges) { |
var url = contentProvider.contentURL(); |
- if (contentProvider.contentType().isScript()) |
- coverageType = Coverage.CoverageType.JavaScript; |
- else if (contentProvider.contentType().isStyleSheet()) |
- coverageType = Coverage.CoverageType.CSS; |
- else |
- console.assert(false, `Unexpected resource type ${contentProvider.contentType().name} for ${url}`); |
- |
+ if (!url) |
+ return; |
+ var entry = this._coverageByURL.get(url); |
+ if (!entry) { |
+ entry = new Coverage.URLCoverageInfo(url); |
+ this._coverageByURL.set(url, entry); |
+ } |
var segments = Coverage.CoverageModel._convertToDisjointSegments(ranges); |
- var usedSize = 0; |
- var unusedSize = 0; |
+ entry.update(contentProvider, contentLength, startLine, startColumn, segments); |
+ } |
+}; |
+ |
+Coverage.URLCoverageInfo = class { |
+ /** |
+ * @param {string} url |
+ */ |
+ constructor(url) { |
+ this._url = url; |
+ /** @type {!Map<string, !Coverage.CoverageInfo>} */ |
+ this._coverageInfoByLocation = new Map(); |
+ this._size = 0; |
+ this._unusedSize = 0; |
+ this._usedSize = 0; |
+ /** @type {!Coverage.CoverageType} */ |
+ this._type; |
+ } |
+ |
+ /** |
+ * @param {!Common.ContentProvider} contentProvider |
+ * @param {number} contentLength |
+ * @param {number} lineOffset |
+ * @param {number} columnOffset |
+ * @param {!Array<!Coverage.CoverageSegment>} segments |
+ */ |
+ update(contentProvider, contentLength, lineOffset, columnOffset, segments) { |
+ var key = `${lineOffset}:${columnOffset}`; |
+ var entry = this._coverageInfoByLocation.get(key); |
+ |
+ if (!entry) { |
+ entry = new Coverage.CoverageInfo(contentProvider, lineOffset, columnOffset); |
+ this._coverageInfoByLocation.set(key, entry); |
+ this._size += contentLength; |
+ this._type |= entry.type(); |
+ } |
+ this._usedSize -= entry._usedSize; |
+ this._unusedSize -= entry._unusedSize; |
+ entry.mergeCoverage(segments); |
+ this._usedSize += entry._usedSize; |
+ this._unusedSize += entry._unusedSize; |
+ } |
+ |
+ /** |
+ * @return {string} |
+ */ |
+ url() { |
+ return this._url; |
+ } |
+ |
+ /** |
+ * @return {!Coverage.CoverageType} |
+ */ |
+ type() { |
+ return this._type; |
+ } |
+ |
+ /** |
+ * @return {number} |
+ */ |
+ size() { |
+ return this._size; |
+ } |
+ |
+ /** |
+ * @return {number} |
+ */ |
+ unusedSize() { |
+ return this._unusedSize; |
+ } |
+ |
+ /** |
+ * @return {number} |
+ */ |
+ usedSize() { |
+ return this._usedSize; |
+ } |
+ |
+ /** |
+ * @return {!Promise<!Array<!{range: !Common.TextRange, count: number}>>} |
+ */ |
+ async buildTextRanges() { |
+ var textRangePromises = []; |
+ for (var coverageInfo of this._coverageInfoByLocation.values()) |
+ textRangePromises.push(coverageInfo.buildTextRanges()); |
+ var allTextRanges = await Promise.all(textRangePromises); |
+ return [].concat(...allTextRanges); |
+ } |
+}; |
+ |
+Coverage.CoverageInfo = class { |
+ /** |
+ * @param {!Common.ContentProvider} contentProvider |
+ * @param {number} lineOffset |
+ * @param {number} columnOffset |
+ */ |
+ constructor(contentProvider, lineOffset, columnOffset) { |
+ this._contentProvider = contentProvider; |
+ this._lineOffset = lineOffset; |
+ this._columnOffset = columnOffset; |
+ this._usedSize = 0; |
+ this._unusedSize = 0; |
+ |
+ if (contentProvider.contentType().isScript()) { |
+ this._coverageType = Coverage.CoverageType.JavaScript; |
+ } else if (contentProvider.contentType().isStyleSheet()) { |
+ this._coverageType = Coverage.CoverageType.CSS; |
+ } else { |
+ console.assert( |
+ false, `Unexpected resource type ${contentProvider.contentType().name} for ${contentProvider.contentURL()}`); |
+ } |
+ /** !Array<!Coverage.CoverageSegment> */ |
+ this._segments = []; |
+ } |
+ |
+ /** |
+ * @return {!Coverage.CoverageType} |
+ */ |
+ type() { |
+ return this._coverageType; |
+ } |
+ |
+ /** |
+ * @param {!Array<!Coverage.CoverageSegment>} segments |
+ */ |
+ mergeCoverage(segments) { |
+ this._segments = Coverage.CoverageInfo._mergeCoverage(this._segments, segments); |
+ this._updateStats(); |
+ } |
+ |
+ /** |
+ * @param {!Array<!Coverage.CoverageSegment>} segmentsA |
+ * @param {!Array<!Coverage.CoverageSegment>} segmentsB |
+ */ |
+ static _mergeCoverage(segmentsA, segmentsB) { |
+ var result = []; |
+ |
+ var indexA = 0; |
+ var indexB = 0; |
+ while (indexA < segmentsA.length && indexB < segmentsB.length) { |
+ var a = segmentsA[indexA]; |
+ var b = segmentsB[indexB]; |
+ var count = |
+ typeof a.count === 'number' || typeof b.count === 'number' ? (a.count || 0) + (b.count || 0) : undefined; |
+ var depth = Math.max(a.depth, b.depth); |
+ var end = Math.min(a.end, b.end); |
+ var last = result.peekLast(); |
+ if (!last || last.count !== count || last.depth !== depth) |
+ result.push({end: end, count: count, depth: depth}); |
+ else |
+ last.end = end; |
+ if (a.end <= b.end) |
+ indexA++; |
+ if (a.end >= b.end) |
+ indexB++; |
+ } |
+ |
+ for (; indexA < segmentsA.length; indexA++) |
+ result.push(segmentsA[indexA]); |
+ for (; indexB < segmentsB.length; indexB++) |
+ result.push(segmentsB[indexB]); |
+ return result; |
+ } |
+ |
+ /** |
+ * @return {!Promise<!Array<!{range: !Common.TextRange, count: number}>>} |
+ */ |
+ async buildTextRanges() { |
+ var contents = await this._contentProvider.requestContent(); |
+ if (!contents) |
+ return []; |
+ var text = new Common.Text(contents); |
+ var lastOffset = 0; |
+ var rangesByDepth = []; |
+ for (var segment of this._segments) { |
+ if (typeof segment.count !== 'number') { |
+ lastOffset = segment.end; |
+ continue; |
+ } |
+ var startPosition = text.positionFromOffset(lastOffset); |
+ var endPosition = text.positionFromOffset(segment.end); |
+ if (!startPosition.lineNumber) |
+ startPosition.columnNumber += this._columnOffset; |
+ startPosition.lineNumber += this._lineOffset; |
+ if (!endPosition.lineNumber) |
+ endPosition.columnNumber += this._columnOffset; |
+ endPosition.lineNumber += this._lineOffset; |
+ |
+ var ranges = rangesByDepth[segment.depth - 1]; // depth === 0 => count === undefined |
+ if (!ranges) { |
+ ranges = []; |
+ rangesByDepth[segment.depth - 1] = ranges; |
+ } |
+ ranges.push({ |
+ count: segment.count, |
+ range: new Common.TextRange( |
+ startPosition.lineNumber, startPosition.columnNumber, endPosition.lineNumber, endPosition.columnNumber) |
+ }); |
+ lastOffset = segment.end; |
+ } |
+ var result = []; |
+ for (var ranges of rangesByDepth) { |
+ for (var r of ranges) |
+ result.push({count: r.count, range: r.range}); |
+ } |
+ return result; |
+ } |
+ |
+ _updateStats() { |
+ this._usedSize = 0; |
+ this._unusedSize = 0; |
+ |
var last = 0; |
- for (var segment of segments) { |
+ for (var segment of this._segments) { |
if (typeof segment.count === 'number') { |
if (segment.count) |
- usedSize += segment.end - last; |
+ this._usedSize += segment.end - last; |
else |
- unusedSize += segment.end - last; |
+ this._unusedSize += segment.end - last; |
} |
last = segment.end; |
} |
- var coverageInfo = { |
- contentProvider: contentProvider, |
- segments: segments, |
- type: coverageType, |
- size: contentLength, |
- usedSize: usedSize, |
- unusedSize: unusedSize, |
- lineOffset: startLine, |
- columnOffset: startColumn |
- }; |
- return coverageInfo; |
} |
}; |