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 8<link rel="import" href="/tracing/base/base64.html"> 9<link rel="import" href="/tracing/extras/chrome/cc/picture.html"> 10<link rel="import" href="/tracing/ui/analysis/generic_object_view.html"> 11<link rel="import" href="/tracing/ui/base/drag_handle.html"> 12<link rel="import" href="/tracing/ui/base/info_bar.html"> 13<link rel="import" href="/tracing/ui/base/hotkey_controller.html"> 14<link rel="import" href="/tracing/ui/base/list_view.html"> 15<link rel="import" href="/tracing/ui/base/mouse_mode_selector.html"> 16<link rel="import" href="/tracing/ui/base/overlay.html"> 17<link rel="import" href="/tracing/ui/base/utils.html"> 18<link rel="import" 19 href="/tracing/ui/extras/chrome/cc/picture_ops_chart_summary_view.html"> 20<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_ops_chart_view.html"> 21<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_ops_list_view.html"> 22 23<template id="tr-ui-e-chrome-cc-picture-debugger-template"> 24 <style> 25 * /deep/ tr-ui-e-chrome-cc-picture-debugger { 26 -webkit-flex: 1 1 auto; 27 -webkit-flex-direction: row; 28 display: -webkit-flex; 29 } 30 31 * /deep/ tr-ui-e-chrome-cc-picture-debugger > tr-ui-a-generic-object-view { 32 -webkit-flex-direction: column; 33 display: -webkit-flex; 34 width: 400px; 35 } 36 37 * /deep/ tr-ui-e-chrome-cc-picture-debugger > left-panel { 38 -webkit-flex-direction: column; 39 display: -webkit-flex; 40 min-width: 300px; 41 } 42 43 * /deep/ tr-ui-e-chrome-cc-picture-debugger > left-panel > picture-info { 44 -webkit-flex: 0 0 auto; 45 padding-top: 2px; 46 } 47 48 * /deep/ tr-ui-e-chrome-cc-picture-debugger > left-panel > 49 picture-info .title { 50 font-weight: bold; 51 margin-left: 5px; 52 margin-right: 5px; 53 } 54 55 * /deep/ tr-ui-e-chrome-cc-picture-debugger > tr-ui-b-drag-handle { 56 -webkit-flex: 0 0 auto; 57 } 58 59 * /deep/ tr-ui-e-chrome-cc-picture-debugger .filename { 60 -webkit-user-select: text; 61 margin-left: 5px; 62 } 63 64 * /deep/ tr-ui-e-chrome-cc-picture-debugger > right-panel { 65 -webkit-flex: 1 1 auto; 66 -webkit-flex-direction: column; 67 display: -webkit-flex; 68 } 69 70 * /deep/ tr-ui-e-chrome-cc-picture-debugger > right-panel > 71 tr-ui-e-chrome-cc-picture-ops-chart-view { 72 min-height: 150px; 73 min-width : 0; 74 overflow-x: auto; 75 overflow-y: hidden; 76 } 77 78 /*************************************************/ 79 80 * /deep/ tr-ui-e-chrome-cc-picture-debugger raster-area { 81 background-color: #ddd; 82 min-height: 200px; 83 min-width: 200px; 84 overflow-y: auto; 85 padding-left: 5px; 86 } 87 </style> 88 89 <left-panel> 90 <picture-info> 91 <div> 92 <span class='title'>Skia Picture</span> 93 <span class='size'></span> 94 </div> 95 <div> 96 <input class='filename' type='text' value='skpicture.skp' /> 97 <button class='export'>Export</button> 98 </div> 99 </picture-info> 100 </left-panel> 101 <right-panel> 102 <tr-ui-e-chrome-cc-picture-ops-chart-view> 103 </tr-ui-e-chrome-cc-picture-ops-chart-view> 104 <raster-area><canvas></canvas></raster-area> 105 </right-panel> 106</template> 107 108<script> 109'use strict'; 110 111tr.exportTo('tr.ui.e.chrome.cc', function() { 112 var THIS_DOC = document.currentScript.ownerDocument; 113 114 /** 115 * PictureDebugger is a view of a PictureSnapshot for inspecting 116 * the picture in detail. (e.g., timing information, etc.) 117 * 118 * @constructor 119 */ 120 var PictureDebugger = tr.ui.b.define('tr-ui-e-chrome-cc-picture-debugger'); 121 122 PictureDebugger.prototype = { 123 __proto__: HTMLUnknownElement.prototype, 124 125 decorate: function() { 126 var node = tr.ui.b.instantiateTemplate( 127 '#tr-ui-e-chrome-cc-picture-debugger-template', THIS_DOC); 128 129 this.appendChild(node); 130 131 this.pictureAsImageData_ = undefined; 132 this.showOverdraw_ = false; 133 this.zoomScaleValue_ = 1; 134 135 this.sizeInfo_ = this.querySelector('.size'); 136 this.rasterArea_ = this.querySelector('raster-area'); 137 this.rasterCanvas_ = this.rasterArea_.querySelector('canvas'); 138 this.rasterCtx_ = this.rasterCanvas_.getContext('2d'); 139 140 this.filename_ = this.querySelector('.filename'); 141 142 this.drawOpsChartSummaryView_ = 143 new tr.ui.e.chrome.cc.PictureOpsChartSummaryView(); 144 this.drawOpsChartView_ = new tr.ui.e.chrome.cc.PictureOpsChartView(); 145 this.drawOpsChartView_.addEventListener( 146 'selection-changed', this.onChartBarClicked_.bind(this)); 147 148 this.exportButton_ = this.querySelector('.export'); 149 this.exportButton_.addEventListener( 150 'click', this.onSaveAsSkPictureClicked_.bind(this)); 151 152 this.trackMouse_(); 153 154 var overdrawCheckbox = tr.ui.b.createCheckBox( 155 this, 'showOverdraw', 156 'pictureView.showOverdraw', false, 157 'Show overdraw'); 158 159 var chartCheckbox = tr.ui.b.createCheckBox( 160 this, 'showSummaryChart', 161 'pictureView.showSummaryChart', false, 162 'Show timing summary'); 163 164 var pictureInfo = this.querySelector('picture-info'); 165 pictureInfo.appendChild(overdrawCheckbox); 166 pictureInfo.appendChild(chartCheckbox); 167 168 this.drawOpsView_ = new tr.ui.e.chrome.cc.PictureOpsListView(); 169 this.drawOpsView_.addEventListener( 170 'selection-changed', this.onChangeDrawOps_.bind(this)); 171 172 var leftPanel = this.querySelector('left-panel'); 173 leftPanel.appendChild(this.drawOpsChartSummaryView_); 174 leftPanel.appendChild(this.drawOpsView_); 175 176 var middleDragHandle = document.createElement('tr-ui-b-drag-handle'); 177 middleDragHandle.horizontal = false; 178 middleDragHandle.target = leftPanel; 179 180 var rightPanel = this.querySelector('right-panel'); 181 rightPanel.replaceChild( 182 this.drawOpsChartView_, 183 rightPanel.querySelector('tr-ui-e-chrome-cc-picture-ops-chart-view')); 184 185 this.infoBar_ = document.createElement('tr-ui-b-info-bar'); 186 this.rasterArea_.appendChild(this.infoBar_); 187 188 this.insertBefore(middleDragHandle, rightPanel); 189 190 this.picture_ = undefined; 191 192 var hkc = document.createElement('tv-ui-b-hotkey-controller'); 193 hkc.addHotKey(new tr.ui.b.HotKey({ 194 eventType: 'keypress', 195 thisArg: this, 196 keyCode: 'h'.charCodeAt(0), 197 callback: function(e) { 198 this.moveSelectedOpBy(-1); 199 e.stopPropagation(); 200 } 201 })); 202 hkc.addHotKey(new tr.ui.b.HotKey({ 203 eventType: 'keypress', 204 thisArg: this, 205 keyCode: 'l'.charCodeAt(0), 206 callback: function(e) { 207 this.moveSelectedOpBy(1); 208 e.stopPropagation(); 209 } 210 })); 211 this.appendChild(hkc); 212 213 // Add a mutation observer so that when the view is resized we can 214 // update the chart summary view. 215 this.mutationObserver_ = new MutationObserver( 216 this.onMutation_.bind(this)); 217 this.mutationObserver_.observe(leftPanel, { attributes: true }); 218 }, 219 220 onMutation_: function(mutations) { 221 222 for (var m = 0; m < mutations.length; m++) { 223 // A style change would indicate that the element has resized 224 // so we should re-render the chart. 225 if (mutations[m].attributeName === 'style') { 226 this.drawOpsChartSummaryView_.requiresRedraw = true; 227 this.drawOpsChartSummaryView_.updateChartContents(); 228 229 this.drawOpsChartView_.dimensionsHaveChanged = true; 230 this.drawOpsChartView_.updateChartContents(); 231 break; 232 } 233 } 234 }, 235 236 onSaveAsSkPictureClicked_: function() { 237 // Decode base64 data into a String 238 var rawData = tr.b.Base64.atob(this.picture_.getBase64SkpData()); 239 240 // Convert this String into an Uint8Array 241 var length = rawData.length; 242 var arrayBuffer = new ArrayBuffer(length); 243 var uint8Array = new Uint8Array(arrayBuffer); 244 for (var c = 0; c < length; c++) 245 uint8Array[c] = rawData.charCodeAt(c); 246 247 // Create a blob URL from the binary array. 248 var blob = new Blob([uint8Array], {type: 'application/octet-binary'}); 249 var blobUrl = window.webkitURL.createObjectURL(blob); 250 251 // Create a link and click on it. BEST API EVAR! 252 var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); 253 link.href = blobUrl; 254 link.download = this.filename_.value; 255 var event = document.createEvent('MouseEvents'); 256 event.initMouseEvent( 257 'click', true, false, window, 0, 0, 0, 0, 0, 258 false, false, false, false, 0, null); 259 link.dispatchEvent(event); 260 }, 261 262 get picture() { 263 return this.picture_; 264 }, 265 266 set picture(picture) { 267 this.drawOpsView_.picture = picture; 268 this.drawOpsChartView_.picture = picture; 269 this.drawOpsChartSummaryView_.picture = picture; 270 this.picture_ = picture; 271 272 this.exportButton_.disabled = !this.picture_.canSave; 273 274 if (picture) { 275 var size = this.getRasterCanvasSize_(); 276 this.rasterCanvas_.width = size.width; 277 this.rasterCanvas_.height = size.height; 278 } 279 280 var bounds = this.rasterArea_.getBoundingClientRect(); 281 var selectorBounds = this.mouseModeSelector_.getBoundingClientRect(); 282 this.mouseModeSelector_.pos = { 283 x: (bounds.right - selectorBounds.width - 10), 284 y: bounds.top 285 }; 286 287 this.rasterize_(); 288 289 this.scheduleUpdateContents_(); 290 }, 291 292 getRasterCanvasSize_: function() { 293 var style = window.getComputedStyle(this.rasterArea_); 294 var width = 295 Math.max(parseInt(style.width), this.picture_.layerRect.width); 296 var height = 297 Math.max(parseInt(style.height), this.picture_.layerRect.height); 298 299 return { 300 width: width, 301 height: height 302 }; 303 }, 304 305 scheduleUpdateContents_: function() { 306 if (this.updateContentsPending_) 307 return; 308 this.updateContentsPending_ = true; 309 tr.b.requestAnimationFrameInThisFrameIfPossible( 310 this.updateContents_.bind(this) 311 ); 312 }, 313 314 updateContents_: function() { 315 this.updateContentsPending_ = false; 316 317 if (this.picture_) { 318 this.sizeInfo_.textContent = '(' + 319 this.picture_.layerRect.width + ' x ' + 320 this.picture_.layerRect.height + ')'; 321 } 322 323 this.drawOpsChartView_.updateChartContents(); 324 this.drawOpsChartView_.scrollSelectedItemIntoViewIfNecessary(); 325 326 // Return if picture hasn't finished rasterizing. 327 if (!this.pictureAsImageData_) 328 return; 329 330 this.infoBar_.visible = false; 331 this.infoBar_.removeAllButtons(); 332 if (this.pictureAsImageData_.error) { 333 this.infoBar_.message = 'Cannot rasterize...'; 334 this.infoBar_.addButton('More info...', function(e) { 335 var overlay = new tr.ui.b.Overlay(); 336 overlay.textContent = this.pictureAsImageData_.error; 337 overlay.visible = true; 338 e.stopPropagation(); 339 return false; 340 }.bind(this)); 341 this.infoBar_.visible = true; 342 } 343 344 this.drawPicture_(); 345 }, 346 347 drawPicture_: function() { 348 var size = this.getRasterCanvasSize_(); 349 if (size.width !== this.rasterCanvas_.width) 350 this.rasterCanvas_.width = size.width; 351 if (size.height !== this.rasterCanvas_.height) 352 this.rasterCanvas_.height = size.height; 353 354 this.rasterCtx_.clearRect(0, 0, size.width, size.height); 355 356 if (!this.pictureAsImageData_.imageData) 357 return; 358 359 var imgCanvas = this.pictureAsImageData_.asCanvas(); 360 var w = imgCanvas.width; 361 var h = imgCanvas.height; 362 this.rasterCtx_.drawImage(imgCanvas, 0, 0, w, h, 363 0, 0, w * this.zoomScaleValue_, 364 h * this.zoomScaleValue_); 365 }, 366 367 rasterize_: function() { 368 if (this.picture_) { 369 this.picture_.rasterize( 370 { 371 stopIndex: this.drawOpsView_.selectedOpIndex, 372 showOverdraw: this.showOverdraw_ 373 }, 374 this.onRasterComplete_.bind(this)); 375 } 376 }, 377 378 onRasterComplete_: function(pictureAsImageData) { 379 this.pictureAsImageData_ = pictureAsImageData; 380 this.scheduleUpdateContents_(); 381 }, 382 383 moveSelectedOpBy: function(increment) { 384 if (this.selectedOpIndex === undefined) { 385 this.selectedOpIndex = 0; 386 return; 387 } 388 this.selectedOpIndex = tr.b.clamp( 389 this.selectedOpIndex + increment, 390 0, this.numOps); 391 }, 392 393 get numOps() { 394 return this.drawOpsView_.numOps; 395 }, 396 397 get selectedOpIndex() { 398 return this.drawOpsView_.selectedOpIndex; 399 }, 400 401 set selectedOpIndex(index) { 402 this.drawOpsView_.selectedOpIndex = index; 403 this.drawOpsChartView_.selectedOpIndex = index; 404 }, 405 406 onChartBarClicked_: function(e) { 407 this.drawOpsView_.selectedOpIndex = 408 this.drawOpsChartView_.selectedOpIndex; 409 }, 410 411 onChangeDrawOps_: function(e) { 412 this.rasterize_(); 413 this.scheduleUpdateContents_(); 414 415 this.drawOpsChartView_.selectedOpIndex = 416 this.drawOpsView_.selectedOpIndex; 417 }, 418 419 set showOverdraw(v) { 420 this.showOverdraw_ = v; 421 this.rasterize_(); 422 }, 423 424 set showSummaryChart(chartShouldBeVisible) { 425 if (chartShouldBeVisible) 426 this.drawOpsChartSummaryView_.show(); 427 else 428 this.drawOpsChartSummaryView_.hide(); 429 }, 430 431 trackMouse_: function() { 432 this.mouseModeSelector_ = document.createElement( 433 'tr-ui-b-mouse-mode-selector'); 434 this.mouseModeSelector_.targetElement = this.rasterArea_; 435 this.rasterArea_.appendChild(this.mouseModeSelector_); 436 437 this.mouseModeSelector_.supportedModeMask = 438 tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM; 439 this.mouseModeSelector_.mode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM; 440 this.mouseModeSelector_.defaultMode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM; 441 this.mouseModeSelector_.settingsKey = 'pictureDebugger.mouseModeSelector'; 442 443 this.mouseModeSelector_.addEventListener('beginzoom', 444 this.onBeginZoom_.bind(this)); 445 this.mouseModeSelector_.addEventListener('updatezoom', 446 this.onUpdateZoom_.bind(this)); 447 this.mouseModeSelector_.addEventListener('endzoom', 448 this.onEndZoom_.bind(this)); 449 }, 450 451 onBeginZoom_: function(e) { 452 this.isZooming_ = true; 453 454 this.lastMouseViewPos_ = this.extractRelativeMousePosition_(e); 455 456 e.preventDefault(); 457 }, 458 459 onUpdateZoom_: function(e) { 460 if (!this.isZooming_) 461 return; 462 463 var currentMouseViewPos = this.extractRelativeMousePosition_(e); 464 465 // Take the distance the mouse has moved and we want to zoom at about 466 // 1/1000th of that speed. 0.01 feels jumpy. This could possibly be tuned 467 // more if people feel it's too slow. 468 this.zoomScaleValue_ += 469 ((this.lastMouseViewPos_.y - currentMouseViewPos.y) * 0.001); 470 this.zoomScaleValue_ = Math.max(this.zoomScaleValue_, 0.1); 471 472 this.drawPicture_(); 473 474 this.lastMouseViewPos_ = currentMouseViewPos; 475 }, 476 477 onEndZoom_: function(e) { 478 this.lastMouseViewPos_ = undefined; 479 this.isZooming_ = false; 480 e.preventDefault(); 481 }, 482 483 extractRelativeMousePosition_: function(e) { 484 return { 485 x: e.clientX - this.rasterArea_.offsetLeft, 486 y: e.clientY - this.rasterArea_.offsetTop 487 }; 488 } 489 }; 490 491 return { 492 PictureDebugger: PictureDebugger 493 }; 494}); 495</script> 496