| 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 |