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