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 this._recordButton.setTitle(Common.UIString('Stop recording')); | |
68 SDK.CSSModel.fromTarget(mainTarget).startRuleUsageTracking(); | |
69 | |
70 this._progressElement.textContent = Common.UIString('Recording...'); | |
71 } | |
72 | |
73 _stopRecording() { | |
74 var mainTarget = SDK.targetManager.mainTarget(); | |
75 if (!mainTarget) | |
76 return; | |
77 | |
78 this._recordButton.setTitle(Common.UIString('Start recording')); | |
79 this._progressElement.textContent = Common.UIString('Fetching results...'); | |
80 | |
81 var cssModel = SDK.CSSModel.fromTarget(mainTarget); | |
82 if (!cssModel) | |
83 return; | |
84 | |
85 cssModel.ruleListPromise() | |
86 .then(processRuleList.bind(this)) | |
87 .then(updateViews.bind(this)); | |
88 | |
89 /** | |
90 * @param {!Array<!SDK.CSSModel.RuleUsage>} ruleUsageList | |
91 * @this {!CSSTracker.CSSTrackerView} | |
92 * @return {!Promise<!Array<!CSSTracker.StyleSheetUsage>>} | |
93 */ | |
94 function processRuleList(ruleUsageList) { | |
95 /** @type {!Map<?SDK.CSSStyleSheetHeader, !CSSTracker.StyleSheetUsage>} */ | |
96 var rulesByStyleSheet = new Map(); | |
97 for (var rule of ruleUsageList) { | |
98 var styleSheetHeader = cssModel.styleSheetHeaderForId(rule.styleSheetId)
; | |
99 var entry = rulesByStyleSheet.get(styleSheetHeader); | |
100 if (!entry) { | |
101 entry = {styleSheetHeader: styleSheetHeader, rules: []}; | |
102 rulesByStyleSheet.set(styleSheetHeader, entry); | |
103 } | |
104 entry.rules.push(rule); | |
105 } | |
106 var styleSheetUsages = Array.from(rulesByStyleSheet.values()); | |
107 return Promise.all(styleSheetUsages.map(entry => this._populateSourceInfo(
entry))).then(() => styleSheetUsages); | |
108 } | |
109 | |
110 /** | |
111 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsages | |
112 * @this {!CSSTracker.CSSTrackerView} | |
113 */ | |
114 function updateViews(styleSheetUsages) { | |
115 this._updateStats(styleSheetUsages); | |
116 this._updateGutter(styleSheetUsages); | |
117 this._updateTree(styleSheetUsages); | |
118 } | |
119 } | |
120 | |
121 /** | |
122 * @param {!CSSTracker.StyleSheetUsage} styleSheetUsage | |
123 */ | |
124 _populateSourceInfo(styleSheetUsage) { | |
125 if (!styleSheetUsage.styleSheetHeader) | |
126 return Promise.resolve(); | |
127 var ruleIndex = new Map(styleSheetUsage.rules.map( | |
128 rule => [`${rule.range.startLine}.${rule.range.startColumn}`, rule])); | |
129 | |
130 return new Promise(fulfill => { | |
131 styleSheetUsage.styleSheetHeader.requestContent() | |
132 .then(content => Common.formatterWorkerPool.parseCSS(content || '', on
Rules)); | |
133 | |
134 /** | |
135 * @param {boolean} isLastChunk | |
136 * @param {!Array<!Common.FormatterWorkerPool.CSSStyleRule>} rules | |
137 */ | |
138 function onRules(isLastChunk, rules) { | |
139 for (var rule of rules) { | |
140 if (!rule.styleRange) | |
141 continue; | |
142 var entry = ruleIndex.get(`${rule.styleRange.startLine}.${rule.styleRa
nge.startColumn}`); | |
143 if (entry) | |
144 entry.selector = rule.selectorText; | |
145 } | |
146 if (isLastChunk) | |
147 fulfill(); | |
148 } | |
149 }); | |
150 } | |
151 | |
152 /** | |
153 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsage | |
154 */ | |
155 _updateStats(styleSheetUsage) { | |
156 var total = 0; | |
157 var unused = 0; | |
158 for (var styleSheet of styleSheetUsage) { | |
159 total += styleSheet.length; | |
160 unused += styleSheet.rules.reduce((count, rule) => rule.wasUsed ? count :
count + 1); | |
161 } | |
162 var percentUnused = Math.round(100 * unused / total); | |
163 if (unused === 1) { | |
164 this._statusMessageElement.textContent = | |
165 Common.UIString('%d CSS rule is not used. (%d%%)', unused, percentUnus
ed); | |
166 } else { | |
167 this._statusMessageElement.textContent = | |
168 Common.UIString('%d CSS rules are not used. (%d%%)', unused, percentUn
used); | |
169 } | |
170 } | 11 } |
171 | 12 |
172 /** | 13 /** |
173 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsages | 14 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsages |
174 */ | 15 */ |
175 _updateGutter(styleSheetUsages) { | 16 update(styleSheetUsages) { |
176 for (var styleSheet of styleSheetUsages) { | 17 this._treeOutline.removeChildren(); |
177 if (!styleSheet.styleSheetHeader) | |
178 continue; | |
179 var url = styleSheet.styleSheetHeader.sourceURL; | |
180 var uiSourceCode = url && Workspace.workspace.uiSourceCodeForURL(url); | |
181 if (!uiSourceCode) | |
182 continue; | |
183 for (var rule of styleSheet.rules) { | |
184 var gutterRange = Common.TextRange.fromObject(rule.range); | |
185 if (gutterRange.startColumn) | |
186 gutterRange.startColumn--; | |
187 uiSourceCode.addDecoration(gutterRange, CSSTracker.CSSTrackerView.LineDe
corator.type, rule.wasUsed); | |
188 } | |
189 } | |
190 } | |
191 | |
192 /** | |
193 * @param {!Array<!CSSTracker.StyleSheetUsage>} styleSheetUsages | |
194 */ | |
195 _updateTree(styleSheetUsages) { | |
196 this._cssResultsElement.removeChildren(); | |
197 this._cssResultsElement.appendChild(this._treeOutline.element); | |
198 | 18 |
199 for (var sheet of styleSheetUsages) { | 19 for (var sheet of styleSheetUsages) { |
200 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); |
201 if (!sheet.styleSheetHeader) { | 21 if (!sheet.styleSheetHeader) { |
202 if (!unusedRuleCount) | 22 if (!unusedRuleCount) |
203 continue; | 23 continue; |
204 if (unusedRuleCount === 1) { | 24 if (unusedRuleCount === 1) { |
205 var removedStyleSheetStats = Common.UIString('1 unused rule in a remov
ed style sheet.'); | 25 var removedStyleSheetStats = Common.UIString('1 unused rule in a remov
ed style sheet.'); |
206 } else { | 26 } else { |
207 var removedStyleSheetStats = | 27 var removedStyleSheetStats = |
208 Common.UIString('%d unused rules in removed style sheets.', unused
RuleCount); | 28 Common.UIString('%d unused rules in removed style sheets.', unused
RuleCount); |
209 } | 29 } |
210 var treeElement = new UI.TreeElement(Common.UIString('Unknown style shee
ts'), true); | 30 var treeElement = new UI.TreeElement(Common.UIString('Unknown style shee
ts'), true); |
211 treeElement.toggleOnClick = true; | 31 treeElement.toggleOnClick = true; |
212 treeElement.selectable = false; | 32 treeElement.selectable = false; |
213 | 33 |
214 var stats = new UI.TreeElement(removedStyleSheetStats, false); | 34 var stats = new UI.TreeElement(removedStyleSheetStats, false); |
215 stats.selectable = false; | 35 stats.selectable = false; |
216 treeElement.appendChild(stats); | 36 treeElement.appendChild(stats); |
217 this._treeOutline.appendChild(treeElement); | 37 this._treeOutline.appendChild(treeElement); |
218 continue; | 38 continue; |
219 } | 39 } |
220 var url = sheet.styleSheetHeader.sourceURL; | 40 var url = sheet.styleSheetHeader.sourceURL; |
221 if (!url) | 41 if (!url) |
222 continue; | 42 continue; |
223 | 43 |
224 var styleSheetTreeElement = | 44 var styleSheetTreeElement = |
225 new CSSTracker.CSSTrackerView.StyleSheetTreeElement(url, sheet.rules); | 45 new CSSTracker.CSSTrackerListView.StyleSheetTreeElement(url, sheet.rul
es); |
226 this._treeOutline.appendChild(styleSheetTreeElement); | 46 this._treeOutline.appendChild(styleSheetTreeElement); |
227 } | 47 } |
228 } | 48 } |
229 }; | 49 }; |
230 | 50 |
231 /** @typedef {{range: !Protocol.CSS.SourceRange, | 51 CSSTracker.CSSTrackerListView._rulesShownAtOnce = 20; |
232 * selector: (string|undefined), | |
233 * wasUsed: boolean}} | |
234 */ | |
235 CSSTracker.RuleUsage; | |
236 | 52 |
237 /** @typedef {{styleSheetHeader: ?SDK.CSSStyleSheetHeader, rules: !Array<!CSSTra
cker.RuleUsage>}} */ | 53 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 /** | 54 /** |
244 * @param {string} url | 55 * @param {string} url |
245 * @param {!Array<!CSSTracker.RuleUsage>} ruleList | 56 * @param {!Array<!CSSTracker.RuleUsage>} ruleList |
246 */ | 57 */ |
247 constructor(url, ruleList) { | 58 constructor(url, ruleList) { |
248 super('', true); | 59 super('', true); |
249 | 60 |
250 this._uiSourceCode = Workspace.workspace.uiSourceCodeForURL(url); | 61 this._uiSourceCode = Workspace.workspace.uiSourceCodeForURL(url); |
251 | 62 |
252 /** @type {!Array<!CSSTracker.RuleUsage>} */ | 63 /** @type {!Array<!CSSTracker.RuleUsage>} */ |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
286 rulesCountSpan.textContent = | 97 rulesCountSpan.textContent = |
287 Common.UIString('(%d unused rules : %d%%)', this._unusedRules.length,
this._percentUnused); | 98 Common.UIString('(%d unused rules : %d%%)', this._unusedRules.length,
this._percentUnused); |
288 } | 99 } |
289 this.title = title; | 100 this.title = title; |
290 } | 101 } |
291 | 102 |
292 /** | 103 /** |
293 * @override | 104 * @override |
294 */ | 105 */ |
295 onpopulate() { | 106 onpopulate() { |
296 var toIndex = Math.min(this._unusedRules.length, CSSTracker.CSSTrackerView._
rulesShownAtOnce); | 107 var toIndex = Math.min(this._unusedRules.length, CSSTracker.CSSTrackerListVi
ew._rulesShownAtOnce); |
297 this._appendRules(0, toIndex); | 108 this._appendRules(0, toIndex); |
298 if (toIndex < this._unusedRules.length) | 109 if (toIndex < this._unusedRules.length) |
299 this._appendShowAllRulesButton(toIndex); | 110 this._appendShowAllRulesButton(toIndex); |
300 } | 111 } |
301 | 112 |
302 /** | 113 /** |
303 * @param {number} fromIndex | 114 * @param {number} fromIndex |
304 * @param {number} toIndex | 115 * @param {number} toIndex |
305 */ | 116 */ |
306 _appendRules(fromIndex, toIndex) { | 117 _appendRules(fromIndex, toIndex) { |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
351 * @param {number} startMatchIndex | 162 * @param {number} startMatchIndex |
352 */ | 163 */ |
353 _showMoreRulesElementSelected(startMatchIndex) { | 164 _showMoreRulesElementSelected(startMatchIndex) { |
354 if (!this._showAllRulesTreeElement) | 165 if (!this._showAllRulesTreeElement) |
355 return; | 166 return; |
356 this.removeChild(this._showAllRulesTreeElement); | 167 this.removeChild(this._showAllRulesTreeElement); |
357 this._appendRules(startMatchIndex, this._unusedRules.length); | 168 this._appendRules(startMatchIndex, this._unusedRules.length); |
358 } | 169 } |
359 }; | 170 }; |
360 | 171 |
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 |