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