1<!DOCTYPE html> 2<!-- 3Copyright (c) 2013 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7<link rel="import" href="/tracing/core/filter.html"> 8<link rel="import" href="/tracing/ui/base/overlay.html"> 9<link rel="import" href="/tracing/ui/base/dom_helpers.html"> 10<link rel="import" href="/tracing/ui/base/info_bar_group.html"> 11<link rel="import" href="/tracing/ui/base/utils.html"> 12 13<template id="record-selection-dialog-template"> 14 <style> 15 .categories-column-view { 16 display: -webkit-flex; 17 -webkit-flex-direction: column; 18 font-family: sans-serif; 19 max-width: 640px; 20 min-height: 0; 21 min-width: 0; 22 opacity: 1; 23 transition: max-height 1s ease, max-width 1s ease, opacity 1s ease; 24 will-change: opacity; 25 } 26 27 .categories-column-view-hidden { 28 max-height: 0; 29 max-width: 0; 30 opacity: 0; 31 overflow: hidden; 32 } 33 34 .categories-selection { 35 display: -webkit-flex; 36 -webkit-flex-direction: row; 37 } 38 39 .category-presets { 40 padding: 4px; 41 } 42 43 .category-description { 44 color: #aaa; 45 font-size: small; 46 max-height: 1em; 47 opacity: 1; 48 padding-left: 4px; 49 padding-right: 4px; 50 text-align: right; 51 transition: max-height 1s ease, opacity 1s ease; 52 will-change: opacity; 53 } 54 55 .category-description-hidden { 56 max-height: 0; 57 opacity: 0; 58 } 59 60 .default-enabled-categories, 61 .default-disabled-categories { 62 -webkit-flex: 1 1 auto; 63 display: -webkit-flex; 64 -webkit-flex-direction: column; 65 padding: 4px; 66 width: 300px; 67 } 68 69 .default-enabled-categories > div, 70 .default-disabled-categories > div { 71 padding: 4px; 72 } 73 74 .tracing-modes { 75 -webkit-flex: 1 0 auto; 76 display: -webkit-flex; 77 -webkit-flex-direction: reverse; 78 padding: 4px; 79 border-bottom: 2px solid #ddd; 80 border-top: 2px solid #ddd; 81 } 82 83 .default-disabled-categories { 84 border-left: 2px solid #ddd; 85 } 86 87 .warning-default-disabled-categories { 88 display: inline-block; 89 font-weight: bold; 90 text-align: center; 91 color: #BD2E2E; 92 width: 2.0ex; 93 height: 2.0ex; 94 border-radius: 2.0ex; 95 border: 1px solid #BD2E2E; 96 } 97 98 .categories { 99 font-size: 80%; 100 padding: 10px; 101 -webkit-flex: 1 1 auto; 102 } 103 104 .group-selectors { 105 font-size: 80%; 106 border-bottom: 1px solid #ddd; 107 padding-bottom: 6px; 108 -webkit-flex: 0 0 auto; 109 } 110 111 .group-selectors button { 112 padding: 1px; 113 } 114 115 .record-selection-dialog .labeled-option-group { 116 -webkit-flex: 0 0 auto; 117 -webkit-flex-direction: column; 118 display: -webkit-flex; 119 } 120 121 .record-selection-dialog .labeled-option { 122 border-top: 5px solid white; 123 border-bottom: 5px solid white; 124 } 125 126 .record-selection-dialog .edit-categories { 127 padding-left: 6px; 128 } 129 130 .record-selection-dialog .edit-categories:after { 131 padding-left: 15px; 132 font-size: 125%; 133 } 134 135 .record-selection-dialog .labeled-option-group:not(.categories-expanded) 136 .edit-categories:after { 137 content: '\25B8'; /* Right triangle */ 138 } 139 140 .record-selection-dialog .labeled-option-group.categories-expanded 141 .edit-categories:after { 142 content: '\25BE'; /* Down triangle */ 143 } 144 145 </style> 146 147 <div class="record-selection-dialog"> 148 <tr-ui-b-info-bar-group></tr-ui-b-info-bar-group> 149 <div class="category-presets"> 150 </div> 151 <div class="category-description"></div> 152 <div class="categories-column-view"> 153 <div class="tracing-modes"></div> 154 <div class="categories-selection"> 155 <div class="default-enabled-categories"> 156 <div>Record Categories</div> 157 <div class="group-selectors"> 158 Select 159 <button class="all-btn">All</button> 160 <button class="none-btn">None</button> 161 </div> 162 <div class="categories"></div> 163 </div> 164 <div class="default-disabled-categories"> 165 <div>Disabled by Default Categories 166 <a class="warning-default-disabled-categories">!</a> 167 </div> 168 <div class="group-selectors"> 169 Select 170 <button class="all-btn">All</button> 171 <button class="none-btn">None</button> 172 </div> 173 <div class="categories"></div> 174 </div> 175 </div> 176 </div> 177 </div> 178</template> 179 180<script> 181'use strict'; 182 183/** 184 * @fileoverview RecordSelectionDialog presents the available categories 185 * to be enabled/disabled during tr.c. 186 */ 187tr.exportTo('tr.ui.e.about_tracing', function() { 188 var THIS_DOC = document.currentScript.ownerDocument; 189 var RecordSelectionDialog = tr.ui.b.define('div'); 190 191 var DEFAULT_PRESETS = [ 192 {title: 'Web developer', 193 categoryFilter: ['blink', 'cc', 'netlog', 'renderer.scheduler', 194 'toplevel', 'v8']}, 195 {title: 'Input latency', 196 categoryFilter: ['benchmark', 'input', 'evdev', 'renderer.scheduler', 197 'toplevel']}, 198 {title: 'Rendering', 199 categoryFilter: ['blink', 'cc', 'gpu', 'toplevel']}, 200 {title: 'Javascript and rendering', 201 categoryFilter: ['blink', 'cc', 'gpu', 'renderer.scheduler', 'v8', 202 'toplevel']}, 203 {title: 'Frame Viewer', 204 categoryFilter: ['blink', 'cc', 'gpu', 'renderer.scheduler', 'v8', 205 'toplevel', 206 'disabled-by-default-cc.debug', 207 'disabled-by-default-cc.debug.picture', 208 'disabled-by-default-cc.debug.display_items']}, 209 {title: 'Manually select settings', 210 categoryFilter: []} 211 ]; 212 var RECORDING_MODES = [ 213 {'label': 'Record until full', 214 'value': 'record-until-full'}, 215 {'label': 'Record continuously', 216 'value': 'record-continuously'}, 217 {'label': 'Record as much as possible', 218 'value': 'record-as-much-as-possible'}]; 219 var DEFAULT_RECORD_MODE = 'record-until-full'; 220 var DEFAULT_CONTINUOUS_TRACING = true; 221 var DEFAULT_SYSTEM_TRACING = true; 222 var DEFAULT_SAMPLING_TRACING = false; 223 224 RecordSelectionDialog.prototype = { 225 __proto__: tr.ui.b.Overlay.prototype, 226 227 decorate: function() { 228 tr.ui.b.Overlay.prototype.decorate.call(this); 229 this.title = 'Record a new trace...'; 230 231 this.classList.add('record-dialog-overlay'); 232 233 var node = 234 tr.ui.b.instantiateTemplate('#record-selection-dialog-template', 235 THIS_DOC); 236 this.appendChild(node); 237 238 this.recordButtonEl_ = document.createElement('button'); 239 this.recordButtonEl_.textContent = 'Record'; 240 this.recordButtonEl_.addEventListener( 241 'click', 242 this.onRecordButtonClicked_.bind(this)); 243 this.recordButtonEl_.style.fontSize = '110%'; 244 this.buttons.appendChild(this.recordButtonEl_); 245 246 this.categoriesView_ = this.querySelector('.categories-column-view'); 247 this.presetsEl_ = this.querySelector('.category-presets'); 248 this.presetsEl_.appendChild(tr.ui.b.createOptionGroup( 249 this, 'currentlyChosenPreset', 250 'about_tracing.record_selection_dialog_preset', 251 DEFAULT_PRESETS[0].categoryFilter, 252 DEFAULT_PRESETS.map(function(p) { 253 return { label: p.title, value: p.categoryFilter }; 254 }))); 255 256 this.tracingRecordModeSltr_ = tr.ui.b.createSelector( 257 this, 'tracingRecordMode', 258 'recordSelectionDialog.tracingRecordMode', 259 DEFAULT_RECORD_MODE, RECORDING_MODES); 260 261 this.systemTracingBn_ = tr.ui.b.createCheckBox( 262 undefined, undefined, 263 'recordSelectionDialog.useSystemTracing', DEFAULT_SYSTEM_TRACING, 264 'System tracing'); 265 this.samplingTracingBn_ = tr.ui.b.createCheckBox( 266 undefined, undefined, 267 'recordSelectionDialog.useSampling', DEFAULT_SAMPLING_TRACING, 268 'State sampling'); 269 this.tracingModesContainerEl_ = this.querySelector('.tracing-modes'); 270 this.tracingModesContainerEl_.appendChild(this.tracingRecordModeSltr_); 271 this.tracingModesContainerEl_.appendChild(this.systemTracingBn_); 272 this.tracingModesContainerEl_.appendChild(this.samplingTracingBn_); 273 274 275 this.enabledCategoriesContainerEl_ = 276 this.querySelector('.default-enabled-categories .categories'); 277 278 this.disabledCategoriesContainerEl_ = 279 this.querySelector('.default-disabled-categories .categories'); 280 281 this.createGroupSelectButtons_( 282 this.querySelector('.default-enabled-categories')); 283 this.createGroupSelectButtons_( 284 this.querySelector('.default-disabled-categories')); 285 this.createDefaultDisabledWarningDialog_( 286 this.querySelector('.warning-default-disabled-categories')); 287 this.editCategoriesOpened_ = false; 288 289 // TODO(chrishenry): When used with tr.ui.b.Overlay (such as in 290 // chrome://tracing, this does not yet look quite right due to 291 // the 10px overlay content padding (but it's good enough). 292 this.infoBarGroup_ = this.querySelector('tr-ui-b-info-bar-group'); 293 294 this.addEventListener('visible-change', this.onVisibleChange_.bind(this)); 295 }, 296 297 set supportsSystemTracing(s) { 298 if (s) { 299 this.systemTracingBn_.style.display = undefined; 300 } else { 301 this.systemTracingBn_.style.display = 'none'; 302 this.useSystemTracing = false; 303 } 304 }, 305 306 get tracingRecordMode() { 307 return this.tracingRecordModeSltr_.selectedValue; 308 }, 309 set tracingRecordMode(value) { 310 this.tracingRecordMode_ = value; 311 }, 312 313 get useSystemTracing() { 314 return this.systemTracingBn_.checked; 315 }, 316 set useSystemTracing(value) { 317 this.systemTracingBn_.checked = !!value; 318 }, 319 320 get useSampling() { 321 return this.samplingTracingBn_.checked; 322 }, 323 set useSampling(value) { 324 this.samplingTracingBn_.checked = !!value; 325 }, 326 327 set categories(c) { 328 if (!(c instanceof Array)) 329 throw new Error('categories must be an array'); 330 this.categories_ = c; 331 332 for (var i = 0; i < this.categories_.length; i++) { 333 var split = this.categories_[i].split(','); 334 this.categories_[i] = split.shift(); 335 if (split.length > 0) 336 this.categories_ = this.categories_.concat(split); 337 } 338 }, 339 340 set settings_key(k) { 341 this.settings_key_ = k; 342 }, 343 344 set settings(s) { 345 throw new Error('Dont use this!'); 346 }, 347 348 usingPreset_: function() { 349 return this.currentlyChosenPreset_.length > 0 || 350 this.isPresetSelected_; 351 }, 352 353 get currentlyChosenPreset() { 354 return this.currentlyChosenPreset_; 355 }, 356 357 set currentlyChosenPreset(preset) { 358 if (!(preset instanceof Array)) 359 throw new Error('RecordSelectionDialog.currentlyChosenPreset:' + 360 ' preset must be an array.'); 361 this.currentlyChosenPreset_ = preset; 362 this.isPresetSelected_ = false; 363 364 if (this.currentlyChosenPreset_.length) { 365 this.isPresetSelected_ = true; 366 this.changeEditCategoriesState_(false); 367 } else { 368 this.updateCategoryColumnView_(true); 369 this.changeEditCategoriesState_(true); 370 } 371 this.updateManualSelectionView_(); 372 this.updatePresetDescription_(); 373 }, 374 375 updateManualSelectionView_: function() { 376 var classList = this.categoriesView_.classList; 377 if (!this.usingPreset_()) { 378 classList.remove('categories-column-view-hidden'); 379 } else { 380 if (this.editCategoriesOpened_) 381 classList.remove('categories-column-view-hidden'); 382 else 383 classList.add('categories-column-view-hidden'); 384 } 385 }, 386 387 updateCategoryColumnView_: function(shouldReadFromSettings) { 388 var categorySet = this.querySelectorAll('.categories'); 389 for (var i = 0; i < categorySet.length; ++i) { 390 var categoryGroup = categorySet[i].children; 391 for (var j = 0; j < categoryGroup.length; ++j) { 392 var categoryEl = categoryGroup[j].children[0]; 393 categoryEl.checked = shouldReadFromSettings ? 394 tr.b.Settings.get(categoryEl.value, false, this.settings_key_) : 395 false; 396 } 397 } 398 }, 399 400 onClickEditCategories: function() { 401 if (!this.usingPreset_()) 402 return; 403 404 if (!this.editCategoriesOpened_) { 405 this.updateCategoryColumnView_(false); 406 for (var i = 0; i < this.currentlyChosenPreset_.length; ++i) { 407 var categoryEl = document.getElementById( 408 this.currentlyChosenPreset_[i]); 409 if (!categoryEl) 410 continue; 411 categoryEl.checked = true; 412 } 413 } 414 415 this.changeEditCategoriesState_(!this.editCategoriesOpened_); 416 this.updateManualSelectionView_(); 417 this.recordButtonEl_.focus(); 418 }, 419 420 changeEditCategoriesState_: function(editCategoriesState) { 421 var presetOptionsGroup = this.querySelector('.labeled-option-group'); 422 if (!presetOptionsGroup) 423 return; 424 425 this.editCategoriesOpened_ = editCategoriesState; 426 if (this.editCategoriesOpened_) 427 presetOptionsGroup.classList.add('categories-expanded'); 428 else 429 presetOptionsGroup.classList.remove('categories-expanded'); 430 }, 431 432 updatePresetDescription_: function() { 433 var description = this.querySelector('.category-description'); 434 if (this.usingPreset_()) { 435 description.innerText = this.currentlyChosenPreset_; 436 description.classList.remove('category-description-hidden'); 437 } else { 438 description.innerText = ''; 439 if (!description.classList.contains('category-description-hidden')) 440 description.classList.add('category-description-hidden'); 441 } 442 }, 443 444 categoryFilter: function() { 445 if (this.usingPreset_()) { 446 var categories = []; 447 var allCategories = this.allCategories_(); 448 for (var category in allCategories) { 449 var disabled = category.indexOf('disabled-by-default-') == 0; 450 if (this.currentlyChosenPreset_.indexOf(category) >= 0) { 451 if (disabled) 452 categories.push(category); 453 } else { 454 if (!disabled) 455 categories.push('-' + category); 456 } 457 } 458 return categories.join(','); 459 } 460 461 var categories = this.unselectedCategories_(); 462 var categories_length = categories.length; 463 var negated_categories = []; 464 for (var i = 0; i < categories_length; ++i) { 465 // Skip any category with a , as it will cause issues when we negate. 466 // Both sides should have been added as separate categories, these can 467 // only come from settings. 468 if (categories[i].match(/,/)) 469 continue; 470 negated_categories.push('-' + categories[i]); 471 } 472 categories = negated_categories.join(','); 473 474 var disabledCategories = this.enabledDisabledByDefaultCategories_(); 475 disabledCategories = disabledCategories.join(','); 476 477 var results = []; 478 if (categories !== '') 479 results.push(categories); 480 if (disabledCategories !== '') 481 results.push(disabledCategories); 482 return results.join(','); 483 }, 484 485 clickRecordButton: function() { 486 this.recordButtonEl_.click(); 487 }, 488 489 onRecordButtonClicked_: function() { 490 this.visible = false; 491 tr.b.dispatchSimpleEvent(this, 'recordclick'); 492 return false; 493 }, 494 495 collectInputs_: function(inputs, isChecked) { 496 var inputs_length = inputs.length; 497 var categories = []; 498 for (var i = 0; i < inputs_length; ++i) { 499 var input = inputs[i]; 500 if (input.checked === isChecked) 501 categories.push(input.value); 502 } 503 return categories; 504 }, 505 506 unselectedCategories_: function() { 507 var inputs = 508 this.enabledCategoriesContainerEl_.querySelectorAll('input'); 509 return this.collectInputs_(inputs, false); 510 }, 511 512 enabledDisabledByDefaultCategories_: function() { 513 var inputs = 514 this.disabledCategoriesContainerEl_.querySelectorAll('input'); 515 return this.collectInputs_(inputs, true); 516 }, 517 518 onVisibleChange_: function() { 519 if (this.visible) 520 this.updateForm_(); 521 }, 522 523 buildInputs_: function(inputs, checkedDefault, parent) { 524 var inputs_length = inputs.length; 525 for (var i = 0; i < inputs_length; i++) { 526 var category = inputs[i]; 527 528 var inputEl = document.createElement('input'); 529 inputEl.type = 'checkbox'; 530 inputEl.id = category; 531 inputEl.value = category; 532 533 inputEl.checked = tr.b.Settings.get( 534 category, checkedDefault, this.settings_key_); 535 inputEl.onclick = this.updateSetting_.bind(this); 536 537 var labelEl = document.createElement('label'); 538 labelEl.textContent = category.replace('disabled-by-default-', ''); 539 labelEl.setAttribute('for', category); 540 541 var divEl = document.createElement('div'); 542 divEl.appendChild(inputEl); 543 divEl.appendChild(labelEl); 544 545 parent.appendChild(divEl); 546 } 547 }, 548 549 allCategories_: function() { 550 // Dedup the categories. We may have things in settings that are also 551 // returned when we query the category list. 552 var categorySet = {}; 553 var allCategories = 554 this.categories_.concat(tr.b.Settings.keys(this.settings_key_)); 555 var allCategoriesLength = allCategories.length; 556 for (var i = 0; i < allCategoriesLength; ++i) 557 categorySet[allCategories[i]] = true; 558 return categorySet; 559 }, 560 561 updateForm_: function() { 562 function ignoreCaseCompare(a, b) { 563 return a.toLowerCase().localeCompare(b.toLowerCase()); 564 } 565 566 this.enabledCategoriesContainerEl_.innerHTML = ''; // Clear old categories 567 this.disabledCategoriesContainerEl_.innerHTML = ''; 568 569 this.recordButtonEl_.focus(); 570 571 var allCategories = this.allCategories_(); 572 var categories = []; 573 var disabledCategories = []; 574 for (var category in allCategories) { 575 if (category.indexOf('disabled-by-default-') == 0) 576 disabledCategories.push(category); 577 else 578 categories.push(category); 579 } 580 disabledCategories = disabledCategories.sort(ignoreCaseCompare); 581 categories = categories.sort(ignoreCaseCompare); 582 583 if (this.categories_.length == 0) { 584 this.infoBarGroup_.addMessage( 585 'No categories found; recording will use default categories.'); 586 } 587 588 this.buildInputs_(categories, true, this.enabledCategoriesContainerEl_); 589 590 if (disabledCategories.length > 0) { 591 this.disabledCategoriesContainerEl_.hidden = false; 592 this.buildInputs_(disabledCategories, false, 593 this.disabledCategoriesContainerEl_); 594 } 595 }, 596 597 updateSetting_: function(e) { 598 var checkbox = e.target; 599 tr.b.Settings.set(checkbox.value, checkbox.checked, this.settings_key_); 600 601 // Change the current record mode to 'Manually select settings' from 602 // preset mode if and only if currently user is in preset record mode 603 // and user selects/deselects any category in 'Edit Categories' mode. 604 if (this.usingPreset_()) { 605 if (checkbox.checked) { 606 this.currentlyChosenPreset_.push(checkbox.value); 607 } else { 608 var pos = this.currentlyChosenPreset_.lastIndexOf(checkbox.value); 609 this.currentlyChosenPreset_.splice(pos, 1); 610 } 611 var categoryEl = document.getElementById( 612 'category-preset-Manually-select-settings'); 613 categoryEl.checked = true; 614 var description = this.querySelector('.category-description'); 615 description.innerText = ''; 616 description.classList.add('category-description-hidden'); 617 } 618 }, 619 620 createGroupSelectButtons_: function(parent) { 621 var flipInputs = function(dir) { 622 var inputs = parent.querySelectorAll('input'); 623 for (var i = 0; i < inputs.length; i++) { 624 if (inputs[i].checked === dir) 625 continue; 626 // click() is used so the settings will be correclty stored. Setting 627 // checked does not trigger the onclick (or onchange) callback. 628 inputs[i].click(); 629 } 630 }; 631 632 var allBtn = parent.querySelector('.all-btn'); 633 allBtn.onclick = function(evt) { 634 flipInputs(true); 635 evt.preventDefault(); 636 }; 637 638 var noneBtn = parent.querySelector('.none-btn'); 639 noneBtn.onclick = function(evt) { 640 flipInputs(false); 641 evt.preventDefault(); 642 }; 643 }, 644 645 setWarningDialogOverlayText_: function(messages) { 646 var contentDiv = document.createElement('div'); 647 648 for (var i = 0; i < messages.length; ++i) { 649 var messageDiv = document.createElement('div'); 650 messageDiv.textContent = messages[i]; 651 contentDiv.appendChild(messageDiv); 652 } 653 this.warningOverlay_.textContent = ''; 654 this.warningOverlay_.appendChild(contentDiv); 655 }, 656 657 createDefaultDisabledWarningDialog_: function(warningLink) { 658 function onClickHandler(evt) { 659 this.warningOverlay_ = tr.ui.b.Overlay(); 660 this.warningOverlay_.parentEl_ = this; 661 this.warningOverlay_.title = 'Warning...'; 662 this.warningOverlay_.userCanClose = true; 663 this.warningOverlay_.visible = true; 664 665 this.setWarningDialogOverlayText_([ 666 'Enabling the default disabled categories may have', 667 'performance and memory impact while tr.c.' 668 ]); 669 670 evt.preventDefault(); 671 } 672 warningLink.onclick = onClickHandler.bind(this); 673 } 674 }; 675 676 return { 677 RecordSelectionDialog: RecordSelectionDialog 678 }; 679}); 680</script> 681