OLD | NEW |
---|---|
1 // Copyright (c) 2017 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** @typedef {{startOffset: number, endOffset: number, count: number}} */ | 5 /** @typedef {{startOffset: number, endOffset: number, count: number}} */ |
6 Coverage.RangeUseCount; | 6 Coverage.RangeUseCount; |
7 | 7 |
8 /** @typedef {{end: number, count: (number|undefined), depth: number}} */ | 8 /** @typedef {{end: number, count: (number|undefined), depth: number}} */ |
9 Coverage.CoverageSegment; | 9 Coverage.CoverageSegment; |
10 | 10 |
11 /** @typedef {{ | |
12 * contentProvider: !Common.ContentProvider, | |
13 * size: number, | |
14 * unusedSize: number, | |
15 * usedSize: number, | |
16 * type: !Coverage.CoverageType, | |
17 * lineOffset: number, | |
18 * columnOffset: number, | |
19 * segments: !Array<!Coverage.CoverageSegment> | |
20 * }} | |
21 */ | |
22 Coverage.CoverageInfo; | |
23 | |
24 /** | 11 /** |
25 * @enum {number} | 12 * @enum {number} |
26 */ | 13 */ |
27 Coverage.CoverageType = { | 14 Coverage.CoverageType = { |
28 CSS: (1 << 0), | 15 CSS: (1 << 0), |
29 JavaScript: (1 << 1), | 16 JavaScript: (1 << 1), |
30 }; | 17 }; |
31 | 18 |
32 Coverage.CoverageModel = class extends SDK.SDKModel { | 19 Coverage.CoverageModel = class extends SDK.SDKModel { |
33 /** | 20 /** |
34 * @param {!SDK.Target} target | 21 * @param {!SDK.Target} target |
35 */ | 22 */ |
36 constructor(target) { | 23 constructor(target) { |
37 super(target); | 24 super(target); |
38 this._target = target; | 25 this._cpuProfilerModel = target.model(SDK.CPUProfilerModel); |
39 this._cpuProfilerModel = this._target.model(SDK.CPUProfilerModel); | 26 this._cssModel = target.model(SDK.CSSModel); |
40 this._cssModel = this._target.model(SDK.CSSModel); | 27 this._debuggerModel = target.model(SDK.DebuggerModel); |
28 | |
29 /** @type {!Map<string, !Coverage.URLCoverageInfo>} */ | |
30 this._coverageByURL = new Map(); | |
41 } | 31 } |
42 | 32 |
43 /** | 33 /** |
44 * @return {boolean} | 34 * @return {boolean} |
45 */ | 35 */ |
46 start() { | 36 start() { |
37 this._coverageByURL.clear(); | |
47 if (this._cssModel) | 38 if (this._cssModel) |
48 this._cssModel.startRuleUsageTracking(); | 39 this._cssModel.startRuleUsageTracking(); |
49 if (this._cpuProfilerModel) | 40 if (this._cpuProfilerModel) |
50 this._cpuProfilerModel.startPreciseCoverage(); | 41 this._cpuProfilerModel.startPreciseCoverage(); |
51 return !!(this._cssModel || this._cpuProfilerModel); | 42 return !!(this._cssModel || this._cpuProfilerModel); |
52 } | 43 } |
53 | 44 |
54 /** | 45 /** |
55 * @return {!Promise<!Array<!Coverage.CoverageInfo>>} | 46 * @return {!Promise<!Array<!Coverage.URLCoverageInfo>>} |
56 */ | 47 */ |
57 async stop() { | 48 async stop() { |
58 var cssCoverageInfoPromise = this._stopCSSCoverage(); | 49 await Promise.all([this._stopCSSCoverage(), this._stopJSCoverage()]); |
59 var jsCoverageInfoPromise = this._stopJSCoverage(); | 50 return Array.from(this._coverageByURL.values()); |
60 var cssCoverageInfo = await cssCoverageInfoPromise; | |
61 var jsCoverageInfo = await jsCoverageInfoPromise; | |
62 return Coverage.CoverageModel._coalesceByURL(cssCoverageInfo.concat(jsCovera geInfo)); | |
63 } | 51 } |
64 | 52 |
65 /** | |
66 * @param {!Array<!Coverage.CoverageInfo>} coverageInfo | |
67 * @return {!Array<!Coverage.CoverageInfo>} | |
68 */ | |
69 static _coalesceByURL(coverageInfo) { | |
70 coverageInfo.sort((a, b) => (a.contentProvider.contentURL() || '').localeCom pare(b.contentProvider.contentURL())); | |
71 var result = []; | |
72 for (var entry of coverageInfo) { | |
73 var url = entry.contentProvider.contentURL(); | |
74 if (!url) | |
75 continue; | |
76 if (result.length && result.peekLast().contentProvider.contentURL() === ur l) { | |
77 var lastEntry = result.peekLast(); | |
78 lastEntry.size += entry.size; | |
79 lastEntry.usedSize += entry.usedSize; | |
80 lastEntry.unusedSize += entry.unusedSize; | |
81 lastEntry.type |= entry.type; | |
82 } else { | |
83 result.push(entry); | |
84 } | |
85 } | |
86 return result; | |
87 } | |
88 | |
89 /** | |
90 * @return {!Promise<!Array<!Coverage.CoverageInfo>>} | |
91 */ | |
92 async _stopJSCoverage() { | 53 async _stopJSCoverage() { |
93 if (!this._cpuProfilerModel) | 54 if (!this._cpuProfilerModel) |
94 return []; | 55 return []; |
95 var coveragePromise = this._cpuProfilerModel.takePreciseCoverage(); | 56 var coveragePromise = this._cpuProfilerModel.takePreciseCoverage(); |
96 this._cpuProfilerModel.stopPreciseCoverage(); | 57 this._cpuProfilerModel.stopPreciseCoverage(); |
97 var rawCoverageData = await coveragePromise; | 58 var rawCoverageData = await coveragePromise; |
98 return Coverage.CoverageModel._processJSCoverage( | 59 this._processJSCoverage(rawCoverageData); |
99 /** @type !SDK.DebuggerModel */ (SDK.DebuggerModel.fromTarget(this.targe t())), rawCoverageData); | |
100 } | 60 } |
101 | 61 |
102 /** | 62 /** |
103 * @param {!SDK.DebuggerModel} debuggerModel | |
104 * @param {!Array<!Protocol.Profiler.ScriptCoverage>} scriptsCoverage | 63 * @param {!Array<!Protocol.Profiler.ScriptCoverage>} scriptsCoverage |
105 * @return {!Array<!Coverage.CoverageInfo>} | |
106 */ | 64 */ |
107 static _processJSCoverage(debuggerModel, scriptsCoverage) { | 65 _processJSCoverage(scriptsCoverage) { |
108 var result = []; | |
109 for (var entry of scriptsCoverage) { | 66 for (var entry of scriptsCoverage) { |
110 var script = debuggerModel.scriptForId(entry.scriptId); | 67 var script = this._debuggerModel.scriptForId(entry.scriptId); |
111 if (!script) | 68 if (!script) |
112 continue; | 69 continue; |
113 var ranges = []; | 70 var ranges = []; |
114 for (var func of entry.functions) { | 71 for (var func of entry.functions) { |
115 for (var range of func.ranges) | 72 for (var range of func.ranges) |
116 ranges.push(range); | 73 ranges.push(range); |
117 } | 74 } |
118 ranges.sort((a, b) => a.startOffset - b.startOffset); | 75 ranges.sort((a, b) => a.startOffset - b.startOffset); |
119 result.push(Coverage.CoverageModel._buildCoverageInfo( | 76 this._addCoverage(script, script.contentLength, script.lineOffset, script. columnOffset, ranges); |
120 script, script.contentLength, script.lineOffset, script.columnOffset, ranges)); | |
121 } | 77 } |
122 return result; | |
123 } | 78 } |
124 | 79 |
125 /** | 80 /** |
126 * @param {!Array<!Coverage.RangeUseCount>} ranges | 81 * @param {!Array<!Coverage.RangeUseCount>} ranges |
127 * @return {!Array<!Coverage.CoverageSegment>} | 82 * @return {!Array<!Coverage.CoverageSegment>} |
128 */ | 83 */ |
129 static _convertToDisjointSegments(ranges) { | 84 static _convertToDisjointSegments(ranges) { |
130 var result = []; | 85 var result = []; |
131 | 86 |
132 var stack = []; | 87 var stack = []; |
(...skipping 28 matching lines...) Expand all Loading... | |
161 last.end = end; | 116 last.end = end; |
162 return; | 117 return; |
163 } | 118 } |
164 } | 119 } |
165 result.push({end: end, count: count, depth: depth}); | 120 result.push({end: end, count: count, depth: depth}); |
166 } | 121 } |
167 | 122 |
168 return result; | 123 return result; |
169 } | 124 } |
170 | 125 |
171 /** | |
172 * @return {!Promise<!Array<!Coverage.CoverageInfo>>} | |
173 */ | |
174 async _stopCSSCoverage() { | 126 async _stopCSSCoverage() { |
175 if (!this._cssModel) | 127 if (!this._cssModel) |
176 return []; | 128 return []; |
alph
2017/03/13 20:59:07
return
| |
177 | 129 |
178 var rawCoverageData = await this._cssModel.ruleListPromise(); | 130 var rawCoverageData = await this._cssModel.ruleListPromise(); |
179 return Coverage.CoverageModel._processCSSCoverage( | 131 this._processCSSCoverage(rawCoverageData); |
180 /** @type !SDK.CSSModel */ (this._cssModel), rawCoverageData); | |
181 } | 132 } |
182 | 133 |
183 /** | 134 /** |
184 * @param {!SDK.CSSModel} cssModel | |
185 * @param {!Array<!Protocol.CSS.RuleUsage>} ruleUsageList | 135 * @param {!Array<!Protocol.CSS.RuleUsage>} ruleUsageList |
186 * @return {!Array<!Coverage.CoverageInfo>} | |
187 */ | 136 */ |
188 static _processCSSCoverage(cssModel, ruleUsageList) { | 137 _processCSSCoverage(ruleUsageList) { |
189 /** @type {!Map<?SDK.CSSStyleSheetHeader, !Array<!Coverage.RangeUseCount>>} */ | 138 /** @type {!Map<!SDK.CSSStyleSheetHeader, !Array<!Coverage.RangeUseCount>>} */ |
190 var rulesByStyleSheet = new Map(); | 139 var rulesByStyleSheet = new Map(); |
191 for (var rule of ruleUsageList) { | 140 for (var rule of ruleUsageList) { |
192 var styleSheetHeader = cssModel.styleSheetHeaderForId(rule.styleSheetId); | 141 var styleSheetHeader = this._cssModel.styleSheetHeaderForId(rule.styleShee tId); |
142 if (!styleSheetHeader) | |
143 continue; | |
193 var ranges = rulesByStyleSheet.get(styleSheetHeader); | 144 var ranges = rulesByStyleSheet.get(styleSheetHeader); |
194 if (!ranges) { | 145 if (!ranges) { |
195 ranges = []; | 146 ranges = []; |
196 rulesByStyleSheet.set(styleSheetHeader, ranges); | 147 rulesByStyleSheet.set(styleSheetHeader, ranges); |
197 } | 148 } |
198 ranges.push({startOffset: rule.startOffset, endOffset: rule.endOffset, cou nt: Number(rule.used)}); | 149 ranges.push({startOffset: rule.startOffset, endOffset: rule.endOffset, cou nt: Number(rule.used)}); |
199 } | 150 } |
200 return Array.from( | 151 for (var entry of rulesByStyleSheet) { |
201 rulesByStyleSheet.entries(), | 152 var styleSheetHeader = /** @type {!SDK.CSSStyleSheetHeader} */ (entry[0]); |
202 entry => Coverage.CoverageModel._buildCoverageInfo( | 153 var ranges = /** @type {!Array<!Coverage.RangeUseCount>} */ (entry[1]); |
203 entry[0], entry[0].contentLength, entry[0].startLine, entry[0].start Column, entry[1])); | 154 this._addCoverage( |
155 styleSheetHeader, styleSheetHeader.contentLength, styleSheetHeader.sta rtLine, styleSheetHeader.startColumn, | |
156 ranges); | |
157 } | |
204 } | 158 } |
205 | 159 |
206 /** | 160 /** |
207 * @param {!Common.ContentProvider} contentProvider | 161 * @param {!Common.ContentProvider} contentProvider |
208 * @param {number} contentLength | 162 * @param {number} contentLength |
209 * @param {number} startLine | 163 * @param {number} startLine |
210 * @param {number} startColumn | 164 * @param {number} startColumn |
211 * @param {!Array<!Coverage.RangeUseCount>} ranges | 165 * @param {!Array<!Coverage.RangeUseCount>} ranges |
212 * @return {!Coverage.CoverageInfo} | 166 */ |
213 */ | 167 _addCoverage(contentProvider, contentLength, startLine, startColumn, ranges) { |
214 static _buildCoverageInfo(contentProvider, contentLength, startLine, startColu mn, ranges) { | |
215 /** @type Coverage.CoverageType */ | |
216 var coverageType; | |
217 var url = contentProvider.contentURL(); | 168 var url = contentProvider.contentURL(); |
218 if (contentProvider.contentType().isScript()) | 169 if (!url) |
219 coverageType = Coverage.CoverageType.JavaScript; | 170 return; |
220 else if (contentProvider.contentType().isStyleSheet()) | 171 var entry = this._coverageByURL.get(url); |
221 coverageType = Coverage.CoverageType.CSS; | 172 if (!entry) { |
222 else | 173 entry = new Coverage.URLCoverageInfo(url); |
223 console.assert(false, `Unexpected resource type ${contentProvider.contentT ype().name} for ${url}`); | 174 this._coverageByURL.set(url, entry); |
224 | 175 } |
225 var segments = Coverage.CoverageModel._convertToDisjointSegments(ranges); | 176 var segments = Coverage.CoverageModel._convertToDisjointSegments(ranges); |
226 var usedSize = 0; | 177 entry.update(contentProvider, contentLength, startLine, startColumn, segment s); |
227 var unusedSize = 0; | 178 } |
179 }; | |
180 | |
181 Coverage.URLCoverageInfo = class { | |
182 /** | |
183 * @param {string} url | |
184 */ | |
185 constructor(url) { | |
186 this._url = url; | |
187 /** @type {!Map<string, !Coverage.CoverageInfo>} */ | |
188 this._coverageInfoByLocation = new Map(); | |
189 this._size = 0; | |
190 this._unusedSize = 0; | |
191 this._usedSize = 0; | |
192 /** @type {!Coverage.CoverageType} */ | |
193 this._type; | |
194 } | |
195 | |
196 /** | |
197 * @param {!Common.ContentProvider} contentProvider | |
198 * @param {number} contentLength | |
199 * @param {number} lineOffset | |
200 * @param {number} columnOffset | |
201 * @param {!Array<!Coverage.CoverageSegment>} segments | |
202 */ | |
203 update(contentProvider, contentLength, lineOffset, columnOffset, segments) { | |
204 var key = `${lineOffset}:${columnOffset}`; | |
205 var entry = this._coverageInfoByLocation.get(key); | |
206 | |
207 if (!entry) { | |
208 entry = new Coverage.CoverageInfo(contentProvider, lineOffset, columnOffse t); | |
209 this._coverageInfoByLocation.set(key, entry); | |
210 this._size += contentLength; | |
211 this._type |= entry.type(); | |
212 } | |
213 this._usedSize -= entry._usedSize; | |
214 this._unusedSize -= entry._unusedSize; | |
215 entry.mergeCoverage(segments); | |
216 this._usedSize += entry._usedSize; | |
217 this._unusedSize += entry._unusedSize; | |
218 } | |
219 | |
220 /** | |
221 * @return {string} | |
222 */ | |
223 url() { | |
224 return this._url; | |
225 } | |
226 | |
227 /** | |
228 * @return {!Coverage.CoverageType} | |
229 */ | |
230 type() { | |
231 return this._type; | |
232 } | |
233 | |
234 /** | |
235 * @return {number} | |
236 */ | |
237 size() { | |
238 return this._size; | |
239 } | |
240 | |
241 /** | |
242 * @return {number} | |
243 */ | |
244 unusedSize() { | |
245 return this._unusedSize; | |
246 } | |
247 | |
248 /** | |
249 * @return {number} | |
250 */ | |
251 usedSize() { | |
252 return this._usedSize; | |
253 } | |
254 | |
255 /** | |
256 * @return {!Promise<!Array<!{range: !Common.TextRange, count: number}>>} | |
257 */ | |
258 async buildTextRanges() { | |
259 var textRangePromises = []; | |
260 for (var coverageInfo of this._coverageInfoByLocation.values()) | |
261 textRangePromises.push(coverageInfo.buildTextRanges()); | |
262 var allTextRanges = await Promise.all(textRangePromises); | |
263 return [].concat(...allTextRanges); | |
264 } | |
265 }; | |
266 | |
267 Coverage.CoverageInfo = class { | |
268 /** | |
269 * @param {!Common.ContentProvider} contentProvider | |
270 * @param {number} lineOffset | |
271 * @param {number} columnOffset | |
272 */ | |
273 constructor(contentProvider, lineOffset, columnOffset) { | |
274 this._contentProvider = contentProvider; | |
275 this._lineOffset = lineOffset; | |
276 this._columnOffset = columnOffset; | |
277 this._usedSize = 0; | |
278 this._unusedSize = 0; | |
279 | |
280 if (contentProvider.contentType().isScript()) { | |
281 this._coverageType = Coverage.CoverageType.JavaScript; | |
282 } else if (contentProvider.contentType().isStyleSheet()) { | |
283 this._coverageType = Coverage.CoverageType.CSS; | |
284 } else { | |
285 console.assert( | |
286 false, `Unexpected resource type ${contentProvider.contentType().name} for ${contentProvider.contentURL()}`); | |
287 } | |
288 /** !Array<!Coverage.CoverageSegment> */ | |
289 this._segments = []; | |
290 } | |
291 | |
292 /** | |
293 * @return {!Coverage.CoverageType} | |
294 */ | |
295 type() { | |
296 return this._coverageType; | |
297 } | |
298 | |
299 /** | |
300 * @param {!Array<!Coverage.CoverageSegment>} segments | |
301 */ | |
302 mergeCoverage(segments) { | |
303 this._segments = Coverage.CoverageInfo._mergeCoverage(this._segments, segmen ts); | |
304 this._updateStats(); | |
305 } | |
306 | |
307 /** | |
308 * @param {!Array<!Coverage.CoverageSegment>} segmentsA | |
309 * @param {!Array<!Coverage.CoverageSegment>} segmentsB | |
310 */ | |
311 static _mergeCoverage(segmentsA, segmentsB) { | |
312 var result = []; | |
313 | |
314 var indexA = 0; | |
315 var indexB = 0; | |
316 while (indexA < segmentsA.length && indexB < segmentsB.length) { | |
317 var a = segmentsA[indexA]; | |
318 var b = segmentsB[indexB]; | |
319 var count = | |
320 typeof a.count === 'number' || typeof b.count === 'number' ? (a.count || 0) + (b.count || 0) : undefined; | |
321 var depth = Math.max(a.depth, b.depth); | |
322 var end = Math.min(a.end, b.end); | |
323 var last = result.peekLast(); | |
324 if (!last || last.count !== count || last.depth !== depth) | |
325 result.push({end: end, count: count, depth: depth}); | |
326 else | |
327 last.end = end; | |
328 if (a.end <= b.end) | |
329 indexA++; | |
330 if (a.end >= b.end) | |
331 indexB++; | |
332 } | |
333 | |
334 for (; indexA < segmentsA.length; indexA++) | |
335 result.push(segmentsA[indexA]); | |
336 for (; indexB < segmentsB.length; indexB++) | |
337 result.push(segmentsB[indexB]); | |
338 return result; | |
339 } | |
340 | |
341 /** | |
342 * @return {!Promise<!Array<!{range: !Common.TextRange, count: number}>>} | |
343 */ | |
344 async buildTextRanges() { | |
345 var contents = await this._contentProvider.requestContent(); | |
346 if (!contents) | |
347 return; | |
alph
2017/03/13 20:59:07
return [];
| |
348 var text = new Common.Text(contents); | |
349 var lastOffset = 0; | |
350 var rangesByDepth = []; | |
351 for (var segment of this._segments) { | |
352 if (typeof segment.count !== 'number') { | |
353 lastOffset = segment.end; | |
354 continue; | |
355 } | |
356 var startPosition = text.positionFromOffset(lastOffset); | |
357 var endPosition = text.positionFromOffset(segment.end); | |
358 if (!startPosition.lineNumber) | |
359 startPosition.columnNumber += this._columnOffset; | |
360 startPosition.lineNumber += this._lineOffset; | |
361 if (!endPosition.lineNumber) | |
362 endPosition.columnNumber += this._columnOffset; | |
363 endPosition.lineNumber += this._lineOffset; | |
364 | |
365 var ranges = rangesByDepth[segment.depth - 1]; // depth === 0 => count == = undefined | |
366 if (!ranges) { | |
367 ranges = []; | |
368 rangesByDepth[segment.depth - 1] = ranges; | |
369 } | |
370 ranges.push({ | |
371 count: segment.count, | |
372 range: new Common.TextRange( | |
373 startPosition.lineNumber, startPosition.columnNumber, endPosition.li neNumber, endPosition.columnNumber) | |
374 }); | |
375 lastOffset = segment.end; | |
376 } | |
377 var result = []; | |
378 for (var ranges of rangesByDepth) { | |
379 for (var r of ranges) | |
380 result.push({count: r.count, range: r.range}); | |
381 } | |
382 return result; | |
383 } | |
384 | |
385 _updateStats() { | |
386 this._usedSize = 0; | |
387 this._unusedSize = 0; | |
388 | |
228 var last = 0; | 389 var last = 0; |
229 for (var segment of segments) { | 390 for (var segment of this._segments) { |
230 if (typeof segment.count === 'number') { | 391 if (typeof segment.count === 'number') { |
231 if (segment.count) | 392 if (segment.count) |
232 usedSize += segment.end - last; | 393 this._usedSize += segment.end - last; |
233 else | 394 else |
234 unusedSize += segment.end - last; | 395 this._unusedSize += segment.end - last; |
235 } | 396 } |
236 last = segment.end; | 397 last = segment.end; |
237 } | 398 } |
238 var coverageInfo = { | |
239 contentProvider: contentProvider, | |
240 segments: segments, | |
241 type: coverageType, | |
242 size: contentLength, | |
243 usedSize: usedSize, | |
244 unusedSize: unusedSize, | |
245 lineOffset: startLine, | |
246 columnOffset: startColumn | |
247 }; | |
248 return coverageInfo; | |
249 } | 399 } |
250 }; | 400 }; |
OLD | NEW |