OLD | NEW |
1 // Copyright (c) 2016 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 CSSTracker.CSSTrackerView = class extends UI.VBox { | 5 CSSTracker.CSSTrackerListView = class extends UI.VBox { |
6 constructor() { | 6 constructor() { |
7 super(true); | 7 super(true); |
8 | |
9 this.registerRequiredCSS('css_tracker/cssTrackerView.css'); | |
10 | |
11 var toolbarContainer = this.contentElement.createChild('div', 'css-tracker-t
oolbar-container'); | |
12 var topToolbar = new UI.Toolbar('css-tracker-toolbar', toolbarContainer); | |
13 | |
14 this._recordButton = | |
15 new UI.ToolbarToggle(Common.UIString('Start recording'), 'largeicon-resu
me', 'largeicon-pause'); | |
16 this._recordButton.addEventListener(UI.ToolbarButton.Events.Click, () => thi
s._toggleRecording(!this._isRecording)); | |
17 topToolbar.appendToolbarItem(this._recordButton); | |
18 | |
19 var clearButton = new UI.ToolbarButton(Common.UIString('Clear all'), 'largei
con-clear'); | |
20 clearButton.addEventListener(UI.ToolbarButton.Events.Click, this._reset.bind
(this)); | |
21 topToolbar.appendToolbarItem(clearButton); | |
22 | |
23 this._cssResultsElement = this.contentElement.createChild('div', 'css-result
s'); | |
24 this._progressElement = this._cssResultsElement.createChild('div', 'progress
-view'); | |
25 this._treeOutline = new UI.TreeOutlineInShadow(); | 8 this._treeOutline = new UI.TreeOutlineInShadow(); |
26 this._treeOutline.registerRequiredCSS('css_tracker/unusedRulesTree.css'); | 9 this._treeOutline.registerRequiredCSS('css_tracker/unusedRulesTree.css'); |
27 | 10 this.contentElement.appendChild(this._treeOutline.element); |
28 this._statusToolbarElement = this.contentElement.createChild('div', 'css-too
lbar-summary'); | |
29 this._statusMessageElement = this._statusToolbarElement.createChild('div', '
css-message'); | |
30 | |
31 this._isRecording = false; | |
32 } | |
33 | |
34 _reset() { | |
35 Workspace.workspace.uiSourceCodes().forEach( | |
36 uiSourceCode => uiSourceCode.removeDecorationsForType(CSSTracker.CSSTrac
kerView.LineDecorator.type)); | |
37 | |
38 this._cssResultsElement.removeChildren(); | |
39 this._progressElement.textContent = ''; | |
40 this._cssResultsElement.appendChild(this._progressElement); | |
41 | |
42 this._treeOutline.removeChildren(); | |
43 this._statusMessageElement.textContent = ''; | |
44 } | |
45 | |
46 /** | |
47 * @param {boolean} enable | |
48 */ | |
49 _toggleRecording(enable) { | |
50 if (enable === this._isRecording) | |
51 return; | |
52 | |
53 this._isRecording = enable; | |
54 this._recordButton.setToggled(this._isRecording); | |
55 | |
56 if (this._isRecording) | |
57 this._startRecording(); | |
58 else | |
59 this._stopRecording(); | |
60 } | |
61 | |
62 _startRecording() { | |
63 this._reset(); | |
64 var mainTarget = SDK.targetManager.mainTarget(); | |
65 if (!mainTarget) | |
66 return; | |
67 var cssModel = mainTarget.model(SDK.CSSModel); | |
68 if (!cssModel) | |
69 return; | |
70 this._recordButton.setTitle(Common.UIString('Stop recording')); | |
71 cssModel.startRuleUsageTracking(); | |
72 | |
73 this._progressElement.textContent = Common.UIString('Recording...'); | |
74 } | |
75 | |
76 _stopRecording() { | |
77 var mainTarget = SDK.targetManager.mainTarget(); | |
78 if (!mainTarget) | |
79 return; | |
80 | |
81 this._recordButton.setTitle(Common.UIString('Start recording')); | |
82 this._progressElement.textContent = Common.UIString('Fetching results...'); | |
83 | |
84 var cssModel = mainTarget.model(SDK.CSSModel); | |
85 if (!cssModel) | |
86 return; | |
87 | |
88 cssModel.ruleListPromise().then(processRuleList.bind(this)).then(updateViews
.bind(this)); | |
89 | |
90 /** | |
91 * @param {!Array<!SDK.CSSModel.RuleUsage>} ruleUsageList | |
92 * @this {!CSSTracker.CSSTrackerView} | |
93 * @return {!Promise<!Array<!CSSTracker.StyleSheetUsage>>} | |
94 */ | |
95 function processRuleList(ruleUsageList) { | |
96 /** @type {!Map<?SDK.CSSStyleSheetHeader, !CSSTracker.StyleSheetUsage>} */ | |
97 var rulesByStyleSheet = new Map(); | |
98 for (var rule of ruleUsageList) { | |
99 var styleSheetHeader = cssModel.styleSheetHeaderForId(rule.styleSheetId)
; | |
100 var entry = rulesByStyleSheet.get(styleSheetHeader); | |
101 if (!entry) { | |
102 entry = {styleSheetHeader: styleSheetHeader, rules: []}; | |
103 rulesByStyleSheet.set(styleSheetHeader, entry); | |
104 } | |
105 entry.rules.push(rule); | |
106 } | |
107 return Promise.all(Array.from( | |
108 rulesByStyleSheet.values(), | |
109 entry => this._populateSourceInfo(/** @type {!CSSTracker.StyleSheetUsa
ge} */ (entry)))); | |
110 } | |
111 | |
112 /** | |
113 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsages | |
114 * @this {!CSSTracker.CSSTrackerView} | |
115 */ | |
116 function updateViews(styleSheetUsages) { | |
117 this._updateStats(styleSheetUsages); | |
118 this._updateGutter(styleSheetUsages); | |
119 this._updateTree(styleSheetUsages); | |
120 } | |
121 } | |
122 | |
123 /** | |
124 * @param {!CSSTracker.StyleSheetUsage} styleSheetUsage | |
125 * @return {!Promise<!CSSTracker.StyleSheetUsage>} | |
126 */ | |
127 _populateSourceInfo(styleSheetUsage) { | |
128 if (!styleSheetUsage.styleSheetHeader) | |
129 return Promise.resolve(styleSheetUsage); | |
130 var ruleIndex = | |
131 new Map(styleSheetUsage.rules.map(rule => [`${rule.range.startLine}.${ru
le.range.startColumn}`, rule])); | |
132 | |
133 return new Promise(fulfill => { | |
134 styleSheetUsage.styleSheetHeader.requestContent().then( | |
135 content => Common.formatterWorkerPool.parseCSS(content || '', onRules)
); | |
136 | |
137 /** | |
138 * @param {boolean} isLastChunk | |
139 * @param {!Array<!Common.FormatterWorkerPool.CSSStyleRule>} rules | |
140 */ | |
141 function onRules(isLastChunk, rules) { | |
142 for (var rule of rules) { | |
143 if (!rule.styleRange) | |
144 continue; | |
145 var entry = ruleIndex.get(`${rule.styleRange.startLine}.${rule.styleRa
nge.startColumn}`); | |
146 if (entry) | |
147 entry.selector = rule.selectorText; | |
148 } | |
149 if (isLastChunk) | |
150 fulfill(styleSheetUsage); | |
151 } | |
152 }); | |
153 } | |
154 | |
155 /** | |
156 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsage | |
157 */ | |
158 _updateStats(styleSheetUsage) { | |
159 var total = 0; | |
160 var unused = 0; | |
161 for (var styleSheet of styleSheetUsage) { | |
162 total += styleSheet.rules.length; | |
163 unused += styleSheet.rules.reduce((count, rule) => rule.wasUsed ? count :
count + 1, 0); | |
164 } | |
165 var percentUnused = total ? Math.round(100 * unused / total) : 0; | |
166 if (unused === 1) { | |
167 this._statusMessageElement.textContent = | |
168 Common.UIString('%d CSS rule is not used. (%d%%)', unused, percentUnus
ed); | |
169 } else { | |
170 this._statusMessageElement.textContent = | |
171 Common.UIString('%d CSS rules are not used. (%d%%)', unused, percentUn
used); | |
172 } | |
173 } | 11 } |
174 | 12 |
175 /** | 13 /** |
176 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsages | 14 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsages |
177 */ | 15 */ |
178 _updateGutter(styleSheetUsages) { | 16 update(styleSheetUsages) { |
179 for (var styleSheet of styleSheetUsages) { | 17 this._treeOutline.removeChildren(); |
180 if (!styleSheet.styleSheetHeader) | |
181 continue; | |
182 var url = styleSheet.styleSheetHeader.sourceURL; | |
183 var uiSourceCode = url && Workspace.workspace.uiSourceCodeForURL(url); | |
184 if (!uiSourceCode) | |
185 continue; | |
186 for (var rule of styleSheet.rules) { | |
187 var gutterRange = Common.TextRange.fromObject(rule.range); | |
188 if (gutterRange.startColumn) | |
189 gutterRange.startColumn--; | |
190 uiSourceCode.addDecoration(gutterRange, CSSTracker.CSSTrackerView.LineDe
corator.type, rule.wasUsed); | |
191 } | |
192 } | |
193 } | |
194 | |
195 /** | |
196 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsages | |
197 */ | |
198 _updateTree(styleSheetUsages) { | |
199 this._cssResultsElement.removeChildren(); | |
200 this._cssResultsElement.appendChild(this._treeOutline.element); | |
201 | 18 |
202 for (var sheet of styleSheetUsages) { | 19 for (var sheet of styleSheetUsages) { |
203 var unusedRuleCount = sheet.rules.reduce((count, rule) => rule.wasUsed ? c
ount : count + 1, 0); | 20 var unusedRuleCount = sheet.rules.reduce((count, rule) => rule.wasUsed ? c
ount : count + 1, 0); |
204 if (sheet.styleSheetHeader) { | 21 if (sheet.styleSheetHeader) { |
205 var url = sheet.styleSheetHeader.sourceURL; | 22 var url = sheet.styleSheetHeader.sourceURL; |
206 if (!url) | 23 if (!url) |
207 continue; | 24 continue; |
208 | 25 |
209 var styleSheetTreeElement = new CSSTracker.CSSTrackerView.StyleSheetTree
Element(url, sheet.rules); | 26 var styleSheetTreeElement = new CSSTracker.CSSTrackerListView.StyleSheet
TreeElement(url, sheet.rules); |
210 this._treeOutline.appendChild(styleSheetTreeElement); | 27 this._treeOutline.appendChild(styleSheetTreeElement); |
211 continue; | 28 continue; |
212 } | 29 } |
213 if (!unusedRuleCount) | 30 if (!unusedRuleCount) |
214 continue; | 31 continue; |
215 var removedStyleSheetStats = unusedRuleCount === 1 ? | 32 var removedStyleSheetStats = unusedRuleCount === 1 ? |
216 Common.UIString('1 unused rule in a removed style sheet.') : | 33 Common.UIString('1 unused rule in a removed style sheet.') : |
217 Common.UIString('%d unused rules in removed style sheets.', unusedRule
Count); | 34 Common.UIString('%d unused rules in removed style sheets.', unusedRule
Count); |
218 | 35 |
219 var treeElement = new UI.TreeElement(Common.UIString('Unknown style sheets
'), true); | 36 var treeElement = new UI.TreeElement(Common.UIString('Unknown style sheets
'), true); |
220 treeElement.toggleOnClick = true; | 37 treeElement.toggleOnClick = true; |
221 treeElement.selectable = false; | 38 treeElement.selectable = false; |
222 | 39 |
223 var stats = new UI.TreeElement(removedStyleSheetStats, false); | 40 var stats = new UI.TreeElement(removedStyleSheetStats, false); |
224 stats.selectable = false; | 41 stats.selectable = false; |
225 treeElement.appendChild(stats); | 42 treeElement.appendChild(stats); |
226 this._treeOutline.appendChild(treeElement); | 43 this._treeOutline.appendChild(treeElement); |
227 } | 44 } |
228 } | 45 } |
229 }; | 46 }; |
230 | 47 |
231 /** @typedef {{range: !Protocol.CSS.SourceRange, | 48 CSSTracker.CSSTrackerListView._rulesShownAtOnce = 20; |
232 * selector: (string|undefined), | |
233 * wasUsed: boolean}} | |
234 */ | |
235 CSSTracker.RuleUsage; | |
236 | 49 |
237 /** @typedef {{styleSheetHeader: ?SDK.CSSStyleSheetHeader, rules: !Array<!CSSTra
cker.RuleUsage>}} */ | 50 CSSTracker.CSSTrackerListView.StyleSheetTreeElement = class extends UI.TreeEleme
nt { |
238 CSSTracker.StyleSheetUsage; | |
239 | |
240 CSSTracker.CSSTrackerView._rulesShownAtOnce = 20; | |
241 | |
242 CSSTracker.CSSTrackerView.StyleSheetTreeElement = class extends UI.TreeElement { | |
243 /** | 51 /** |
244 * @param {string} url | 52 * @param {string} url |
245 * @param {!Array<!CSSTracker.RuleUsage>} ruleList | 53 * @param {!Array<!CSSTracker.RuleUsage>} ruleList |
246 */ | 54 */ |
247 constructor(url, ruleList) { | 55 constructor(url, ruleList) { |
248 super('', true); | 56 super('', true); |
249 | 57 |
250 this._uiSourceCode = Workspace.workspace.uiSourceCodeForURL(url); | 58 this._uiSourceCode = Workspace.workspace.uiSourceCodeForURL(url); |
251 | 59 |
252 /** @type {!Array<!CSSTracker.RuleUsage>} */ | 60 /** @type {!Array<!CSSTracker.RuleUsage>} */ |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
286 rulesCountSpan.textContent = | 94 rulesCountSpan.textContent = |
287 Common.UIString('(%d unused rules : %d%%)', this._unusedRules.length,
this._percentUnused); | 95 Common.UIString('(%d unused rules : %d%%)', this._unusedRules.length,
this._percentUnused); |
288 } | 96 } |
289 this.title = title; | 97 this.title = title; |
290 } | 98 } |
291 | 99 |
292 /** | 100 /** |
293 * @override | 101 * @override |
294 */ | 102 */ |
295 onpopulate() { | 103 onpopulate() { |
296 var toIndex = Math.min(this._unusedRules.length, CSSTracker.CSSTrackerView._
rulesShownAtOnce); | 104 var toIndex = Math.min(this._unusedRules.length, CSSTracker.CSSTrackerListVi
ew._rulesShownAtOnce); |
297 this._appendRules(0, toIndex); | 105 this._appendRules(0, toIndex); |
298 if (toIndex < this._unusedRules.length) | 106 if (toIndex < this._unusedRules.length) |
299 this._appendShowAllRulesButton(toIndex); | 107 this._appendShowAllRulesButton(toIndex); |
300 } | 108 } |
301 | 109 |
302 /** | 110 /** |
303 * @param {number} fromIndex | 111 * @param {number} fromIndex |
304 * @param {number} toIndex | 112 * @param {number} toIndex |
305 */ | 113 */ |
306 _appendRules(fromIndex, toIndex) { | 114 _appendRules(fromIndex, toIndex) { |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
350 /** | 158 /** |
351 * @param {number} startMatchIndex | 159 * @param {number} startMatchIndex |
352 */ | 160 */ |
353 _showMoreRulesElementSelected(startMatchIndex) { | 161 _showMoreRulesElementSelected(startMatchIndex) { |
354 if (!this._showAllRulesTreeElement) | 162 if (!this._showAllRulesTreeElement) |
355 return; | 163 return; |
356 this.removeChild(this._showAllRulesTreeElement); | 164 this.removeChild(this._showAllRulesTreeElement); |
357 this._appendRules(startMatchIndex, this._unusedRules.length); | 165 this._appendRules(startMatchIndex, this._unusedRules.length); |
358 } | 166 } |
359 }; | 167 }; |
360 | |
361 /** | |
362 * @implements {SourceFrame.UISourceCodeFrame.LineDecorator} | |
363 */ | |
364 CSSTracker.CSSTrackerView.LineDecorator = class { | |
365 /** | |
366 * @override | |
367 * @param {!Workspace.UISourceCode} uiSourceCode | |
368 * @param {!TextEditor.CodeMirrorTextEditor} textEditor | |
369 */ | |
370 decorate(uiSourceCode, textEditor) { | |
371 var gutterType = 'CodeMirror-gutter-coverage'; | |
372 | |
373 var decorations = uiSourceCode.decorationsForType(CSSTracker.CSSTrackerView.
LineDecorator.type); | |
374 textEditor.uninstallGutter(gutterType); | |
375 if (!decorations || !decorations.size) | |
376 return; | |
377 | |
378 textEditor.installGutter(gutterType, false); | |
379 | |
380 for (var decoration of decorations) { | |
381 for (var line = decoration.range().startLine; line <= decoration.range().e
ndLine; ++line) { | |
382 var element = createElementWithClass('div'); | |
383 if (decoration.data()) | |
384 element.className = 'text-editor-css-rule-used-marker'; | |
385 else | |
386 element.className = 'text-editor-css-rule-unused-marker'; | |
387 | |
388 textEditor.setGutterDecoration(line, gutterType, element); | |
389 } | |
390 } | |
391 } | |
392 }; | |
393 | |
394 CSSTracker.CSSTrackerView.LineDecorator.type = 'coverage'; | |
OLD | NEW |