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