Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(15)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/coverage/CoverageModel.js

Issue 2745283002: DevTools: merge coverage segments from different instances of same URL (Closed)
Patch Set: cleanup Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698