1<!DOCTYPE html> 2<!-- 3Copyright (c) 2015 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/hotkey_controller.html"> 13<link rel="import" href="/tracing/ui/base/info_bar.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" href="/tracing/ui/extras/chrome/cc/display_item_list_item.html"> 19<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_ops_list_view.html"> 20 21<template id="tr-ui-e-chrome-cc-display-item-debugger-template"> 22 <style> 23 * /deep/ tr-ui-e-chrome-cc-display-item-debugger { 24 -webkit-flex: 1 1 auto; 25 display: -webkit-flex; 26 } 27 28 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > left-panel { 29 -webkit-flex-direction: column; 30 display: -webkit-flex; 31 min-width: 300px; 32 overflow-y: auto; 33 } 34 35 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > left-panel > 36 display-item-info { 37 -webkit-flex: 1 1 auto; 38 padding-top: 2px; 39 } 40 41 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > left-panel > 42 display-item-info .title { 43 font-weight: bold; 44 margin-left: 5px; 45 margin-right: 5px; 46 } 47 48 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > left-panel > 49 display-item-info .export { 50 margin: 5px; 51 } 52 53 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > tr-ui-b-drag-handle { 54 -webkit-flex: 0 0 auto; 55 } 56 57 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > right-panel { 58 -webkit-flex: 1 1 auto; 59 display: -webkit-flex; 60 } 61 62 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > left-panel > 63 display-item-info > header { 64 border-bottom: 1px solid #555; 65 } 66 67 /*************************************************/ 68 69 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > right-panel > 70 tr-ui-e-chrome-cc-picture-ops-list-view.hasPictureOps { 71 display: block; 72 } 73 74 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > right-panel > 75 tr-ui-b-drag-handle.hasPictureOps { 76 display: block; 77 } 78 79 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > right-panel > 80 tr-ui-e-chrome-cc-picture-ops-list-view { 81 display: none; 82 overflow-y: auto; 83 } 84 85 * /deep/ tr-ui-e-chrome-cc-display-item-debugger > right-panel > 86 tr-ui-b-drag-handle { 87 display: none; 88 } 89 90 * /deep/ tr-ui-e-chrome-cc-display-item-debugger raster-area { 91 -webkit-flex: 1 1 auto; 92 background-color: #ddd; 93 min-height: 200px; 94 min-width: 200px; 95 overflow-y: auto; 96 padding-left: 5px; 97 } 98 </style> 99 100 <left-panel> 101 <display-item-info> 102 <header> 103 <span class='title'>Display Item List</span> 104 <span class='size'></span> 105 <div class='export'> 106 <input class='dlfilename' type='text' value='displayitemlist.json' /> 107 <button class='dlexport'>Export display item list</button> 108 </div> 109 <div class='export'> 110 <input class='skpfilename' type='text' value='skpicture.skp' /> 111 <button class='skpexport'>Export list as SkPicture</button> 112 </div> 113 </header> 114 </display-item-info> 115 </left-panel> 116 <right-panel> 117 <raster-area><canvas></canvas></raster-area> 118 </right-panel> 119</template> 120 121<script> 122'use strict'; 123 124tr.exportTo('tr.ui.e.chrome.cc', function() { 125 var THIS_DOC = document.currentScript.ownerDocument; 126 127 /** 128 * DisplayItemDebugger is a view of a DisplayItemListSnapshot for inspecting 129 * a display item list and the pictures within it. 130 * 131 * @constructor 132 */ 133 var DisplayItemDebugger = tr.ui.b.define( 134 'tr-ui-e-chrome-cc-display-item-debugger'); 135 136 DisplayItemDebugger.prototype = { 137 __proto__: HTMLUnknownElement.prototype, 138 139 decorate: function() { 140 var node = tr.ui.b.instantiateTemplate( 141 '#tr-ui-e-chrome-cc-display-item-debugger-template', THIS_DOC); 142 143 this.appendChild(node); 144 145 this.pictureAsImageData_ = undefined; 146 this.zoomScaleValue_ = 1; 147 148 this.sizeInfo_ = this.querySelector('.size'); 149 this.rasterArea_ = this.querySelector('raster-area'); 150 this.rasterCanvas_ = this.rasterArea_.querySelector('canvas'); 151 this.rasterCtx_ = this.rasterCanvas_.getContext('2d'); 152 153 this.trackMouse_(); 154 155 this.displayItemInfo_ = this.querySelector('display-item-info'); 156 this.displayItemInfo_.addEventListener( 157 'click', this.onDisplayItemInfoClick_.bind(this), false); 158 159 this.displayItemListView_ = new tr.ui.b.ListView(); 160 this.displayItemListView_.addEventListener('selection-changed', 161 this.onDisplayItemListSelection_.bind(this)); 162 this.displayItemInfo_.appendChild(this.displayItemListView_); 163 164 this.displayListFilename_ = this.querySelector('.dlfilename'); 165 this.displayListExportButton_ = this.querySelector('.dlexport'); 166 this.displayListExportButton_.addEventListener( 167 'click', this.onExportDisplayListClicked_.bind(this)); 168 169 this.skpFilename_ = this.querySelector('.skpfilename'); 170 this.skpExportButton_ = this.querySelector('.skpexport'); 171 this.skpExportButton_.addEventListener( 172 'click', this.onExportSkPictureClicked_.bind(this)); 173 174 var leftPanel = this.querySelector('left-panel'); 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 182 this.infoBar_ = document.createElement('tr-ui-b-info-bar'); 183 this.rasterArea_.insertBefore(this.infoBar_, this.rasterCanvas_); 184 185 this.insertBefore(middleDragHandle, rightPanel); 186 187 this.picture_ = undefined; 188 189 this.pictureOpsListView_ = new tr.ui.e.chrome.cc.PictureOpsListView(); 190 rightPanel.insertBefore(this.pictureOpsListView_, this.rasterArea_); 191 192 this.pictureOpsListDragHandle_ = 193 document.createElement('tr-ui-b-drag-handle'); 194 this.pictureOpsListDragHandle_.horizontal = false; 195 this.pictureOpsListDragHandle_.target = this.pictureOpsListView_; 196 rightPanel.insertBefore(this.pictureOpsListDragHandle_, this.rasterArea_); 197 }, 198 199 get picture() { 200 return this.picture_; 201 }, 202 203 set displayItemList(displayItemList) { 204 this.displayItemList_ = displayItemList; 205 this.picture = this.displayItemList_; 206 207 this.displayItemListView_.clear(); 208 this.displayItemList_.items.forEach(function(item) { 209 var listItem = document.createElement( 210 'tr-ui-e-chrome-cc-display-item-list-item'); 211 listItem.data = item; 212 this.displayItemListView_.appendChild(listItem); 213 }.bind(this)); 214 }, 215 216 set picture(picture) { 217 this.picture_ = picture; 218 219 // Hide the ops list if we are showing the "main" display item list. 220 var showOpsList = picture && picture !== this.displayItemList_; 221 this.updateDrawOpsList_(showOpsList); 222 223 if (picture) { 224 var size = this.getRasterCanvasSize_(); 225 this.rasterCanvas_.width = size.width; 226 this.rasterCanvas_.height = size.height; 227 } 228 229 var bounds = this.rasterArea_.getBoundingClientRect(); 230 var selectorBounds = this.mouseModeSelector_.getBoundingClientRect(); 231 this.mouseModeSelector_.pos = { 232 x: (bounds.right - selectorBounds.width - 10), 233 y: bounds.top 234 }; 235 236 this.rasterize_(); 237 238 this.scheduleUpdateContents_(); 239 }, 240 241 getRasterCanvasSize_: function() { 242 var style = window.getComputedStyle(this.rasterArea_); 243 var width = parseInt(style.width); 244 var height = parseInt(style.height); 245 if (this.picture_) { 246 width = Math.max(width, this.picture_.layerRect.width); 247 height = Math.max(height, this.picture_.layerRect.height); 248 } 249 250 return { 251 width: width, 252 height: height 253 }; 254 }, 255 256 scheduleUpdateContents_: function() { 257 if (this.updateContentsPending_) 258 return; 259 this.updateContentsPending_ = true; 260 tr.b.requestAnimationFrameInThisFrameIfPossible( 261 this.updateContents_.bind(this) 262 ); 263 }, 264 265 updateContents_: function() { 266 this.updateContentsPending_ = false; 267 268 if (this.picture_) { 269 this.sizeInfo_.textContent = '(' + 270 this.picture_.layerRect.width + ' x ' + 271 this.picture_.layerRect.height + ')'; 272 } 273 274 // Return if picture hasn't finished rasterizing. 275 if (!this.pictureAsImageData_) 276 return; 277 278 this.infoBar_.visible = false; 279 this.infoBar_.removeAllButtons(); 280 if (this.pictureAsImageData_.error) { 281 this.infoBar_.message = 'Cannot rasterize...'; 282 this.infoBar_.addButton('More info...', function(e) { 283 var overlay = new tr.ui.b.Overlay(); 284 overlay.textContent = this.pictureAsImageData_.error; 285 overlay.visible = true; 286 e.stopPropagation(); 287 return false; 288 }.bind(this)); 289 this.infoBar_.visible = true; 290 } 291 292 this.drawPicture_(); 293 }, 294 295 drawPicture_: function() { 296 var size = this.getRasterCanvasSize_(); 297 if (size.width !== this.rasterCanvas_.width) 298 this.rasterCanvas_.width = size.width; 299 if (size.height !== this.rasterCanvas_.height) 300 this.rasterCanvas_.height = size.height; 301 302 this.rasterCtx_.clearRect(0, 0, size.width, size.height); 303 304 if (!this.picture_ || !this.pictureAsImageData_.imageData) 305 return; 306 307 var imgCanvas = this.pictureAsImageData_.asCanvas(); 308 var w = imgCanvas.width; 309 var h = imgCanvas.height; 310 this.rasterCtx_.drawImage(imgCanvas, 0, 0, w, h, 311 0, 0, w * this.zoomScaleValue_, 312 h * this.zoomScaleValue_); 313 }, 314 315 rasterize_: function() { 316 if (this.picture_) { 317 this.picture_.rasterize( 318 { 319 showOverdraw: false 320 }, 321 this.onRasterComplete_.bind(this)); 322 } 323 }, 324 325 onRasterComplete_: function(pictureAsImageData) { 326 this.pictureAsImageData_ = pictureAsImageData; 327 this.scheduleUpdateContents_(); 328 }, 329 330 onDisplayItemListSelection_: function(e) { 331 var selected = this.displayItemListView_.selectedElement; 332 333 if (!selected) { 334 this.picture = this.displayItemList_; 335 return; 336 } 337 338 var index = Array.prototype.indexOf.call( 339 this.displayItemListView_.children, selected); 340 var displayItem = this.displayItemList_.items[index]; 341 if (displayItem && displayItem.skp64) 342 this.picture = new tr.e.cc.Picture( 343 displayItem.skp64, this.displayItemList_.layerRect); 344 else 345 this.picture = undefined; 346 }, 347 348 onDisplayItemInfoClick_: function(e) { 349 if (e && e.target == this.displayItemInfo_) { 350 this.displayItemListView_.selectedElement = undefined; 351 } 352 }, 353 354 updateDrawOpsList_: function(showOpsList) { 355 if (showOpsList) { 356 this.pictureOpsListView_.picture = this.picture_; 357 if (this.pictureOpsListView_.numOps > 0) { 358 this.pictureOpsListView_.classList.add('hasPictureOps'); 359 this.pictureOpsListDragHandle_.classList.add('hasPictureOps'); 360 } 361 } else { 362 this.pictureOpsListView_.classList.remove('hasPictureOps'); 363 this.pictureOpsListDragHandle_.classList.remove('hasPictureOps'); 364 } 365 }, 366 367 trackMouse_: function() { 368 this.mouseModeSelector_ = document.createElement( 369 'tr-ui-b-mouse-mode-selector'); 370 this.mouseModeSelector_.targetElement = this.rasterArea_; 371 this.rasterArea_.appendChild(this.mouseModeSelector_); 372 373 this.mouseModeSelector_.supportedModeMask = 374 tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM; 375 this.mouseModeSelector_.mode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM; 376 this.mouseModeSelector_.defaultMode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM; 377 this.mouseModeSelector_.settingsKey = 'pictureDebugger.mouseModeSelector'; 378 379 this.mouseModeSelector_.addEventListener('beginzoom', 380 this.onBeginZoom_.bind(this)); 381 this.mouseModeSelector_.addEventListener('updatezoom', 382 this.onUpdateZoom_.bind(this)); 383 this.mouseModeSelector_.addEventListener('endzoom', 384 this.onEndZoom_.bind(this)); 385 }, 386 387 onBeginZoom_: function(e) { 388 this.isZooming_ = true; 389 390 this.lastMouseViewPos_ = this.extractRelativeMousePosition_(e); 391 392 e.preventDefault(); 393 }, 394 395 onUpdateZoom_: function(e) { 396 if (!this.isZooming_) 397 return; 398 399 var currentMouseViewPos = this.extractRelativeMousePosition_(e); 400 401 // Take the distance the mouse has moved and we want to zoom at about 402 // 1/1000th of that speed. 0.01 feels jumpy. This could possibly be tuned 403 // more if people feel it's too slow. 404 this.zoomScaleValue_ += 405 ((this.lastMouseViewPos_.y - currentMouseViewPos.y) * 0.001); 406 this.zoomScaleValue_ = Math.max(this.zoomScaleValue_, 0.1); 407 408 this.drawPicture_(); 409 410 this.lastMouseViewPos_ = currentMouseViewPos; 411 }, 412 413 onEndZoom_: function(e) { 414 this.lastMouseViewPos_ = undefined; 415 this.isZooming_ = false; 416 e.preventDefault(); 417 }, 418 419 extractRelativeMousePosition_: function(e) { 420 return { 421 x: e.clientX - this.rasterArea_.offsetLeft, 422 y: e.clientY - this.rasterArea_.offsetTop 423 }; 424 }, 425 426 saveFile_: function(filename, rawData) { 427 if (!rawData) 428 return; 429 430 // Convert this String into an Uint8Array 431 var length = rawData.length; 432 var arrayBuffer = new ArrayBuffer(length); 433 var uint8Array = new Uint8Array(arrayBuffer); 434 for (var c = 0; c < length; c++) 435 uint8Array[c] = rawData.charCodeAt(c); 436 437 // Create a blob URL from the binary array. 438 var blob = new Blob([uint8Array], {type: 'application/octet-binary'}); 439 var blobUrl = window.URL.createObjectURL(blob); 440 441 // Create a link and click on it. 442 var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); 443 link.href = blobUrl; 444 link.download = filename; 445 var event = document.createEvent('MouseEvents'); 446 event.initMouseEvent( 447 'click', true, false, window, 0, 0, 0, 0, 0, 448 false, false, false, false, 0, null); 449 link.dispatchEvent(event); 450 }, 451 452 onExportDisplayListClicked_: function() { 453 var rawData = JSON.stringify(this.displayItemList_.items); 454 this.saveFile_(this.displayListFilename_.value, rawData); 455 }, 456 457 onExportSkPictureClicked_: function() { 458 var rawData = tr.b.Base64.atob(this.picture_.getBase64SkpData()); 459 this.saveFile_(this.skpFilename_.value, rawData); 460 } 461 }; 462 463 return { 464 DisplayItemDebugger: DisplayItemDebugger 465 }; 466}); 467</script> 468