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

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

Issue 2865573003: DevTools: support live coverage (Closed)
Patch Set: minor polish on corner cases Created 3 years, 7 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)}} */ 8 /** @typedef {{end: number, count: (number|undefined)}} */
9 Coverage.CoverageSegment; 9 Coverage.CoverageSegment;
10 10
(...skipping 10 matching lines...) Expand all
21 * @param {!SDK.Target} target 21 * @param {!SDK.Target} target
22 */ 22 */
23 constructor(target) { 23 constructor(target) {
24 super(target); 24 super(target);
25 this._cpuProfilerModel = target.model(SDK.CPUProfilerModel); 25 this._cpuProfilerModel = target.model(SDK.CPUProfilerModel);
26 this._cssModel = target.model(SDK.CSSModel); 26 this._cssModel = target.model(SDK.CSSModel);
27 this._debuggerModel = target.model(SDK.DebuggerModel); 27 this._debuggerModel = target.model(SDK.DebuggerModel);
28 28
29 /** @type {!Map<string, !Coverage.URLCoverageInfo>} */ 29 /** @type {!Map<string, !Coverage.URLCoverageInfo>} */
30 this._coverageByURL = new Map(); 30 this._coverageByURL = new Map();
31 /** @type {!Map<!Common.ContentProvider, !Coverage.CoverageInfo>} */
32 this._coverageByContentProvider = new Map();
31 } 33 }
32 34
33 /** 35 /**
34 * @return {boolean} 36 * @return {boolean}
35 */ 37 */
36 start() { 38 start() {
37 this._coverageByURL.clear(); 39 this._coverageByURL.clear();
38 if (this._cssModel) 40 if (this._cssModel)
39 this._cssModel.startRuleUsageTracking(); 41 this._cssModel.startCoverage();
40 if (this._cpuProfilerModel) 42 if (this._cpuProfilerModel)
41 this._cpuProfilerModel.startPreciseCoverage(); 43 this._cpuProfilerModel.startPreciseCoverage();
42 return !!(this._cssModel || this._cpuProfilerModel); 44 return !!(this._cssModel || this._cpuProfilerModel);
43 } 45 }
44 46
45 /** 47 /**
46 * @return {!Promise<!Array<!Coverage.URLCoverageInfo>>} 48 * @return {!Promise<!Array<!Coverage.CoverageInfo>>}
47 */ 49 */
48 async stop() { 50 stop() {
49 await Promise.all([this._stopCSSCoverage(), this._stopJSCoverage()]); 51 var pollPromise = this.poll();
52 if (this._cpuProfilerModel)
53 this._cpuProfilerModel.stopPreciseCoverage();
54 if (this._cssModel)
55 this._cssModel.stopCoverage();
56 return pollPromise;
57 }
58
59 /**
60 * @return {!Promise<!Array<!Coverage.CoverageInfo>>}
61 */
62 async poll() {
63 var updates = await Promise.all([this._takeCSSCoverage(), this._takeJSCovera ge()]);
64 return updates[0].concat(updates[1]);
65 }
66
67 /**
68 * @return {!Array<!Coverage.URLCoverageInfo>}
69 */
70 entries() {
50 return Array.from(this._coverageByURL.values()); 71 return Array.from(this._coverageByURL.values());
51 } 72 }
52 73
53 async _stopJSCoverage() { 74 /**
75 * @param {!Common.ContentProvider} contentProvider
76 * @param {number} startOffset
77 * @param {number} endOffset
78 * @return {boolean|undefined}
79 */
80 usageForRange(contentProvider, startOffset, endOffset) {
81 var coverageInfo = this._coverageByContentProvider.get(contentProvider);
82 return coverageInfo && coverageInfo.usageForRange(startOffset, endOffset);
83 }
84
85 /**
86 * @return {!Promise<!Array<!Coverage.CoverageInfo>>}
87 */
88 async _takeJSCoverage() {
54 if (!this._cpuProfilerModel) 89 if (!this._cpuProfilerModel)
55 return; 90 return [];
56 var coveragePromise = this._cpuProfilerModel.takePreciseCoverage(); 91 var rawCoverageData = await this._cpuProfilerModel.takePreciseCoverage();
57 this._cpuProfilerModel.stopPreciseCoverage(); 92 return this._processJSCoverage(rawCoverageData);
58 var rawCoverageData = await coveragePromise;
59 this._processJSCoverage(rawCoverageData);
60 } 93 }
61 94
62 /** 95 /**
63 * @param {!Array<!Protocol.Profiler.ScriptCoverage>} scriptsCoverage 96 * @param {!Array<!Protocol.Profiler.ScriptCoverage>} scriptsCoverage
97 * @return {!Array<!Coverage.CoverageInfo>}
64 */ 98 */
65 _processJSCoverage(scriptsCoverage) { 99 _processJSCoverage(scriptsCoverage) {
100 var updatedEntries = [];
66 for (var entry of scriptsCoverage) { 101 for (var entry of scriptsCoverage) {
67 var script = this._debuggerModel.scriptForId(entry.scriptId); 102 var script = this._debuggerModel.scriptForId(entry.scriptId);
68 if (!script) 103 if (!script)
69 continue; 104 continue;
70 var ranges = []; 105 var ranges = [];
71 for (var func of entry.functions) { 106 for (var func of entry.functions) {
72 for (var range of func.ranges) 107 for (var range of func.ranges)
73 ranges.push(range); 108 ranges.push(range);
74 } 109 }
75 this._addCoverage(script, script.contentLength, script.lineOffset, script. columnOffset, ranges); 110 var entry = this._addCoverage(script, script.contentLength, script.lineOff set, script.columnOffset, ranges);
111 if (entry)
112 updatedEntries.push(entry);
76 } 113 }
114 return updatedEntries;
77 } 115 }
78 116
79 /** 117 /**
118 * @return {!Promise<!Array<!Coverage.CoverageInfo>>}
119 */
120 async _takeCSSCoverage() {
121 if (!this._cssModel)
122 return [];
123 var rawCoverageData = await this._cssModel.takeCoverageDelta();
124 return this._processCSSCoverage(rawCoverageData);
125 }
126
127 /**
128 * @param {!Array<!Protocol.CSS.RuleUsage>} ruleUsageList
129 * @return {!Array<!Coverage.CoverageInfo>}
130 */
131 _processCSSCoverage(ruleUsageList) {
132 var updatedEntries = [];
133 /** @type {!Map<!SDK.CSSStyleSheetHeader, !Array<!Coverage.RangeUseCount>>} */
134 var rulesByStyleSheet = new Map();
135 for (var rule of ruleUsageList) {
136 var styleSheetHeader = this._cssModel.styleSheetHeaderForId(rule.styleShee tId);
137 if (!styleSheetHeader)
138 continue;
139 var ranges = rulesByStyleSheet.get(styleSheetHeader);
140 if (!ranges) {
141 ranges = [];
142 rulesByStyleSheet.set(styleSheetHeader, ranges);
143 }
144 ranges.push({startOffset: rule.startOffset, endOffset: rule.endOffset, cou nt: Number(rule.used)});
145 }
146 for (var entry of rulesByStyleSheet) {
147 var styleSheetHeader = /** @type {!SDK.CSSStyleSheetHeader} */ (entry[0]);
148 var ranges = /** @type {!Array<!Coverage.RangeUseCount>} */ (entry[1]);
149 var entry = this._addCoverage(
150 styleSheetHeader, styleSheetHeader.contentLength, styleSheetHeader.sta rtLine, styleSheetHeader.startColumn,
151 ranges);
152 if (entry)
153 updatedEntries.push(entry);
154 }
155 return updatedEntries;
156 }
157
158 /**
80 * @param {!Array<!Coverage.RangeUseCount>} ranges 159 * @param {!Array<!Coverage.RangeUseCount>} ranges
81 * @return {!Array<!Coverage.CoverageSegment>} 160 * @return {!Array<!Coverage.CoverageSegment>}
82 */ 161 */
83 static _convertToDisjointSegments(ranges) { 162 static _convertToDisjointSegments(ranges) {
84 ranges.sort((a, b) => a.startOffset - b.startOffset); 163 ranges.sort((a, b) => a.startOffset - b.startOffset);
85 164
86 var result = []; 165 var result = [];
87 var stack = []; 166 var stack = [];
88 for (var entry of ranges) { 167 for (var entry of ranges) {
89 var top = stack.peekLast(); 168 var top = stack.peekLast();
(...skipping 24 matching lines...) Expand all
114 last.end = end; 193 last.end = end;
115 return; 194 return;
116 } 195 }
117 } 196 }
118 result.push({end: end, count: count}); 197 result.push({end: end, count: count});
119 } 198 }
120 199
121 return result; 200 return result;
122 } 201 }
123 202
124 async _stopCSSCoverage() {
125 if (!this._cssModel)
126 return [];
127
128 var rawCoverageData = await this._cssModel.ruleListPromise();
129 this._processCSSCoverage(rawCoverageData);
130 }
131
132 /**
133 * @param {!Array<!Protocol.CSS.RuleUsage>} ruleUsageList
134 */
135 _processCSSCoverage(ruleUsageList) {
136 /** @type {!Map<!SDK.CSSStyleSheetHeader, !Array<!Coverage.RangeUseCount>>} */
137 var rulesByStyleSheet = new Map();
138 for (var rule of ruleUsageList) {
139 var styleSheetHeader = this._cssModel.styleSheetHeaderForId(rule.styleShee tId);
140 if (!styleSheetHeader)
141 continue;
142 var ranges = rulesByStyleSheet.get(styleSheetHeader);
143 if (!ranges) {
144 ranges = [];
145 rulesByStyleSheet.set(styleSheetHeader, ranges);
146 }
147 ranges.push({startOffset: rule.startOffset, endOffset: rule.endOffset, cou nt: Number(rule.used)});
148 }
149 for (var entry of rulesByStyleSheet) {
150 var styleSheetHeader = /** @type {!SDK.CSSStyleSheetHeader} */ (entry[0]);
151 var ranges = /** @type {!Array<!Coverage.RangeUseCount>} */ (entry[1]);
152 this._addCoverage(
153 styleSheetHeader, styleSheetHeader.contentLength, styleSheetHeader.sta rtLine, styleSheetHeader.startColumn,
154 ranges);
155 }
156 }
157
158 /** 203 /**
159 * @param {!Common.ContentProvider} contentProvider 204 * @param {!Common.ContentProvider} contentProvider
160 * @param {number} contentLength 205 * @param {number} contentLength
161 * @param {number} startLine 206 * @param {number} startLine
162 * @param {number} startColumn 207 * @param {number} startColumn
163 * @param {!Array<!Coverage.RangeUseCount>} ranges 208 * @param {!Array<!Coverage.RangeUseCount>} ranges
209 * @return {?Coverage.CoverageInfo}
164 */ 210 */
165 _addCoverage(contentProvider, contentLength, startLine, startColumn, ranges) { 211 _addCoverage(contentProvider, contentLength, startLine, startColumn, ranges) {
166 var url = contentProvider.contentURL(); 212 var url = contentProvider.contentURL();
167 if (!url) 213 if (!url)
168 return; 214 return null;
169 var entry = this._coverageByURL.get(url); 215 var urlCoverage = this._coverageByURL.get(url);
170 if (!entry) { 216 if (!urlCoverage) {
171 entry = new Coverage.URLCoverageInfo(url); 217 urlCoverage = new Coverage.URLCoverageInfo(url);
172 this._coverageByURL.set(url, entry); 218 this._coverageByURL.set(url, urlCoverage);
173 } 219 }
220 var coverageInfo = urlCoverage._ensureEntry(contentProvider, contentLength, startLine, startColumn);
221 this._coverageByContentProvider.set(contentProvider, coverageInfo);
174 var segments = Coverage.CoverageModel._convertToDisjointSegments(ranges); 222 var segments = Coverage.CoverageModel._convertToDisjointSegments(ranges);
175 if (segments.length && segments.peekLast().end < contentLength) 223 if (segments.length && segments.peekLast().end < contentLength)
176 segments.push({end: contentLength}); 224 segments.push({end: contentLength});
177 entry.update(contentProvider, contentLength, startLine, startColumn, segment s); 225 var oldUsedSize = coverageInfo._usedSize;
226 coverageInfo.mergeCoverage(segments);
227 if (coverageInfo._usedSize === oldUsedSize)
228 return null;
229 urlCoverage._usedSize += coverageInfo._usedSize - oldUsedSize;
230 return coverageInfo;
178 } 231 }
179 }; 232 };
180 233
181 Coverage.URLCoverageInfo = class { 234 Coverage.URLCoverageInfo = class {
182 /** 235 /**
183 * @param {string} url 236 * @param {string} url
184 */ 237 */
185 constructor(url) { 238 constructor(url) {
186 this._url = url; 239 this._url = url;
187 /** @type {!Map<string, !Coverage.CoverageInfo>} */ 240 /** @type {!Map<string, !Coverage.CoverageInfo>} */
188 this._coverageInfoByLocation = new Map(); 241 this._coverageInfoByLocation = new Map();
189 this._size = 0; 242 this._size = 0;
190 this._usedSize = 0; 243 this._usedSize = 0;
191 /** @type {!Coverage.CoverageType} */ 244 /** @type {!Coverage.CoverageType} */
192 this._type; 245 this._type;
193 } 246 }
194 247
195 /** 248 /**
196 * @param {!Common.ContentProvider} contentProvider
197 * @param {number} contentLength
198 * @param {number} lineOffset
199 * @param {number} columnOffset
200 * @param {!Array<!Coverage.CoverageSegment>} segments
201 */
202 update(contentProvider, contentLength, lineOffset, columnOffset, segments) {
203 var key = `${lineOffset}:${columnOffset}`;
204 var entry = this._coverageInfoByLocation.get(key);
205
206 if (!entry) {
207 entry = new Coverage.CoverageInfo(contentProvider, lineOffset, columnOffse t);
208 this._coverageInfoByLocation.set(key, entry);
209 this._size += contentLength;
210 this._type |= entry.type();
211 }
212 this._usedSize -= entry._usedSize;
213 entry.mergeCoverage(segments);
214 this._usedSize += entry._usedSize;
215 }
216
217 /**
218 * @return {string} 249 * @return {string}
219 */ 250 */
220 url() { 251 url() {
221 return this._url; 252 return this._url;
222 } 253 }
223 254
224 /** 255 /**
225 * @return {!Coverage.CoverageType} 256 * @return {!Coverage.CoverageType}
226 */ 257 */
227 type() { 258 type() {
(...skipping 15 matching lines...) Expand all
243 } 274 }
244 275
245 /** 276 /**
246 * @return {number} 277 * @return {number}
247 */ 278 */
248 unusedSize() { 279 unusedSize() {
249 return this._size - this._usedSize; 280 return this._size - this._usedSize;
250 } 281 }
251 282
252 /** 283 /**
253 * @return {!Promise<!Array<!{range: !TextUtils.TextRange, count: number}>>} 284 * @param {!Common.ContentProvider} contentProvider
285 * @param {number} contentLength
286 * @param {number} lineOffset
287 * @param {number} columnOffset
288 * @return {!Coverage.CoverageInfo}
254 */ 289 */
255 async buildTextRanges() { 290 _ensureEntry(contentProvider, contentLength, lineOffset, columnOffset) {
256 var textRangePromises = []; 291 var key = `${lineOffset}:${columnOffset}`;
257 for (var coverageInfo of this._coverageInfoByLocation.values()) 292 var entry = this._coverageInfoByLocation.get(key);
258 textRangePromises.push(coverageInfo.buildTextRanges()); 293
259 var allTextRanges = await Promise.all(textRangePromises); 294 if (!entry) {
260 return [].concat(...allTextRanges); 295 entry = new Coverage.CoverageInfo(contentProvider, lineOffset, columnOffse t);
296 this._coverageInfoByLocation.set(key, entry);
297 this._size += contentLength;
298 this._type |= entry.type();
299 }
300 return entry;
261 } 301 }
262 }; 302 };
263 303
264 Coverage.CoverageInfo = class { 304 Coverage.CoverageInfo = class {
265 /** 305 /**
266 * @param {!Common.ContentProvider} contentProvider 306 * @param {!Common.ContentProvider} contentProvider
267 * @param {number} lineOffset 307 * @param {number} lineOffset
268 * @param {number} columnOffset 308 * @param {number} columnOffset
269 */ 309 */
270 constructor(contentProvider, lineOffset, columnOffset) { 310 constructor(contentProvider, lineOffset, columnOffset) {
271 this._contentProvider = contentProvider; 311 this._contentProvider = contentProvider;
272 this._lineOffset = lineOffset; 312 this._lineOffset = lineOffset;
273 this._columnOffset = columnOffset; 313 this._columnOffset = columnOffset;
274 this._usedSize = 0; 314 this._usedSize = 0;
275 315
276 if (contentProvider.contentType().isScript()) { 316 if (contentProvider.contentType().isScript()) {
277 this._coverageType = Coverage.CoverageType.JavaScript; 317 this._coverageType = Coverage.CoverageType.JavaScript;
278 } else if (contentProvider.contentType().isStyleSheet()) { 318 } else if (contentProvider.contentType().isStyleSheet()) {
279 this._coverageType = Coverage.CoverageType.CSS; 319 this._coverageType = Coverage.CoverageType.CSS;
280 } else { 320 } else {
281 console.assert( 321 console.assert(
282 false, `Unexpected resource type ${contentProvider.contentType().name} for ${contentProvider.contentURL()}`); 322 false, `Unexpected resource type ${contentProvider.contentType().name} for ${contentProvider.contentURL()}`);
283 } 323 }
284 /** !Array<!Coverage.CoverageSegment> */ 324 /** !Array<!Coverage.CoverageSegment> */
285 this._segments = []; 325 this._segments = [];
286 } 326 }
287 327
288 /** 328 /**
329 * @return {!Common.ContentProvider}
330 */
331 contentProvider() {
332 return this._contentProvider;
333 }
334
335 /**
336 * @return {string}
337 */
338 url() {
339 return this._contentProvider.contentURL();
340 }
341
342 /**
289 * @return {!Coverage.CoverageType} 343 * @return {!Coverage.CoverageType}
290 */ 344 */
291 type() { 345 type() {
292 return this._coverageType; 346 return this._coverageType;
293 } 347 }
294 348
295 /** 349 /**
296 * @param {!Array<!Coverage.CoverageSegment>} segments 350 * @param {!Array<!Coverage.CoverageSegment>} segments
297 */ 351 */
298 mergeCoverage(segments) { 352 mergeCoverage(segments) {
299 this._segments = Coverage.CoverageInfo._mergeCoverage(this._segments, segmen ts); 353 this._segments = Coverage.CoverageInfo._mergeCoverage(this._segments, segmen ts);
300 this._updateStats(); 354 this._updateStats();
301 } 355 }
302 356
303 /** 357 /**
358 * @param {number} start
359 * @param {number} end
360 * @return {boolean}
361 */
362 usageForRange(start, end) {
363 var index = this._segments.upperBound(start, (position, segment) => position - segment.end);
364 for (; index < this._segments.length && this._segments[index].end < end; ++i ndex) {
365 if (this._segments[index].count)
366 return true;
367 }
368 return index < this._segments.length && this._segments[index].count;
369 }
370
371 /**
304 * @param {!Array<!Coverage.CoverageSegment>} segmentsA 372 * @param {!Array<!Coverage.CoverageSegment>} segmentsA
305 * @param {!Array<!Coverage.CoverageSegment>} segmentsB 373 * @param {!Array<!Coverage.CoverageSegment>} segmentsB
306 */ 374 */
307 static _mergeCoverage(segmentsA, segmentsB) { 375 static _mergeCoverage(segmentsA, segmentsB) {
308 var result = []; 376 var result = [];
309 377
310 var indexA = 0; 378 var indexA = 0;
311 var indexB = 0; 379 var indexB = 0;
312 while (indexA < segmentsA.length && indexB < segmentsB.length) { 380 while (indexA < segmentsA.length && indexB < segmentsB.length) {
313 var a = segmentsA[indexA]; 381 var a = segmentsA[indexA];
(...skipping 12 matching lines...) Expand all
326 indexB++; 394 indexB++;
327 } 395 }
328 396
329 for (; indexA < segmentsA.length; indexA++) 397 for (; indexA < segmentsA.length; indexA++)
330 result.push(segmentsA[indexA]); 398 result.push(segmentsA[indexA]);
331 for (; indexB < segmentsB.length; indexB++) 399 for (; indexB < segmentsB.length; indexB++)
332 result.push(segmentsB[indexB]); 400 result.push(segmentsB[indexB]);
333 return result; 401 return result;
334 } 402 }
335 403
336 /**
337 * @return {!Promise<!Array<!{range: !TextUtils.TextRange, count: number}>>}
338 */
339 async buildTextRanges() {
340 var contents = await this._contentProvider.requestContent();
341 if (!contents)
342 return [];
343 var text = new TextUtils.Text(contents);
344 var lastOffset = 0;
345 var result = [];
346 for (var segment of this._segments) {
347 if (!segment.end)
348 continue;
349 var startPosition = text.positionFromOffset(lastOffset);
350 var endPosition = text.positionFromOffset(segment.end);
351 if (!startPosition.lineNumber)
352 startPosition.columnNumber += this._columnOffset;
353 startPosition.lineNumber += this._lineOffset;
354 if (!endPosition.lineNumber)
355 endPosition.columnNumber += this._columnOffset;
356 endPosition.lineNumber += this._lineOffset;
357 var range = new TextUtils.TextRange(
358 startPosition.lineNumber, startPosition.columnNumber, endPosition.line Number, endPosition.columnNumber);
359 result.push({count: segment.count || 0, range: range});
360 lastOffset = segment.end;
361 }
362 return result;
363 }
364
365 _updateStats() { 404 _updateStats() {
366 this._usedSize = 0; 405 this._usedSize = 0;
367 406
368 var last = 0; 407 var last = 0;
369 for (var segment of this._segments) { 408 for (var segment of this._segments) {
370 if (segment.count) 409 if (segment.count)
371 this._usedSize += segment.end - last; 410 this._usedSize += segment.end - last;
372 last = segment.end; 411 last = segment.end;
373 } 412 }
374 } 413 }
375 }; 414 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698