1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5'use strict';
6
7base.requireStylesheet('cc.picture_debugger');
8
9base.require('cc.picture');
10base.require('cc.picture_ops_list_view');
11base.require('tracing.analysis.generic_object_view');
12base.require('ui.drag_handle');
13base.require('ui.info_bar');
14base.require('ui.list_view');
15base.require('ui.overlay');
16
17base.exportTo('cc', function() {
18
19  /**
20   * PictureDebugger is a view of a PictureSnapshot for inspecting
21   * the picture in detail. (e.g., timing information, etc.)
22   *
23   * @constructor
24   */
25  var PictureDebugger = ui.define('picture-debugger');
26
27  PictureDebugger.prototype = {
28    __proto__: HTMLUnknownElement.prototype,
29
30    decorate: function() {
31      this.pictureAsCanvas_ = undefined;
32
33      this.leftPanel_ = document.createElement('left-panel');
34
35      this.pictureInfo_ = document.createElement('picture-info');
36
37      this.title_ = document.createElement('span');
38      this.title_.textContent = 'Skia Picture';
39      this.title_.classList.add('title');
40      this.sizeInfo_ = document.createElement('span');
41      this.sizeInfo_.classList.add('size');
42      this.filename_ = document.createElement('input');
43      this.filename_.classList.add('filename');
44      this.filename_.type = 'text';
45      this.filename_.value = 'skpicture.skp';
46      var exportButton = document.createElement('button');
47      exportButton.textContent = 'Export';
48      exportButton.addEventListener(
49          'click', this.onSaveAsSkPictureClicked_.bind(this));
50      this.pictureInfo_.appendChild(this.title_);
51      this.pictureInfo_.appendChild(this.sizeInfo_);
52      this.pictureInfo_.appendChild(document.createElement('br'));
53      this.pictureInfo_.appendChild(this.filename_);
54      this.pictureInfo_.appendChild(exportButton);
55
56      this.titleDragHandle_ = new ui.DragHandle();
57      this.titleDragHandle_.horizontal = true;
58      this.titleDragHandle_.target = this.pictureInfo_;
59
60      this.drawOpsView_ = new cc.PictureOpsListView();
61      this.drawOpsView_.addEventListener(
62          'selection-changed', this.onChangeDrawOps_.bind(this));
63
64      this.leftPanel_.appendChild(this.pictureInfo_);
65      this.leftPanel_.appendChild(this.titleDragHandle_);
66      this.leftPanel_.appendChild(this.drawOpsView_);
67
68      this.middleDragHandle_ = new ui.DragHandle();
69      this.middleDragHandle_.horizontal = false;
70      this.middleDragHandle_.target = this.leftPanel_;
71
72      this.infoBar_ = new ui.InfoBar();
73      this.rasterArea_ = document.createElement('raster-area');
74
75      this.appendChild(this.leftPanel_);
76      this.appendChild(this.middleDragHandle_);
77      this.rasterArea_.appendChild(this.infoBar_);
78      this.appendChild(this.rasterArea_);
79
80      this.picture_ = undefined;
81    },
82
83    onSaveAsSkPictureClicked_: function() {
84      // Decode base64 data into a String
85      var rawData = atob(this.picture_.getBase64SkpData());
86
87      // Convert this String into an Uint8Array
88      var length = rawData.length;
89      var arrayBuffer = new ArrayBuffer(length);
90      var uint8Array = new Uint8Array(arrayBuffer);
91      for (var c = 0; c < length; c++)
92        uint8Array[c] = rawData.charCodeAt(c);
93
94      // Create a blob URL from the binary array.
95      var blob = new Blob([uint8Array], {type: 'application/octet-binary'});
96      var blobUrl = window.webkitURL.createObjectURL(blob);
97
98      // Create a link and click on it. BEST API EVAR!
99      var link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
100      link.href = blobUrl;
101      link.download = this.filename_.value;
102      var event = document.createEvent('MouseEvents');
103      event.initMouseEvent(
104          'click', true, false, window, 0, 0, 0, 0, 0,
105          false, false, false, false, 0, null);
106      link.dispatchEvent(event);
107    },
108
109    get picture() {
110      return this.picture_;
111    },
112
113    set picture(picture) {
114      this.drawOpsView_.picture = picture;
115      this.picture_ = picture;
116      this.rasterize_();
117
118      this.scheduleUpdateContents_();
119    },
120
121    scheduleUpdateContents_: function() {
122      if (this.updateContentsPending_)
123        return;
124      this.updateContentsPending_ = true;
125      base.requestAnimationFrameInThisFrameIfPossible(
126          this.updateContents_.bind(this)
127      );
128    },
129
130    updateContents_: function() {
131      this.updateContentsPending_ = false;
132
133      if (this.picture_) {
134        this.sizeInfo_.textContent = '(' +
135            this.picture_.layerRect.width + ' x ' +
136            this.picture_.layerRect.height + ')';
137      }
138
139      // Return if picture hasn't finished rasterizing.
140      if (!this.pictureAsCanvas_)
141        return;
142
143      this.infoBar_.visible = false;
144      this.infoBar_.removeAllButtons();
145      if (this.pictureAsCanvas_.error) {
146        this.infoBar_.message = 'Cannot rasterize...';
147        this.infoBar_.addButton('More info...', function() {
148          var overlay = new ui.Overlay();
149          overlay.textContent = this.pictureAsCanvas_.error;
150          overlay.visible = true;
151          overlay.obeyCloseEvents = true;
152        }.bind(this));
153        this.infoBar_.visible = true;
154      }
155
156      // FIXME(pdr): Append the canvas instead of using a background image.
157      if (this.pictureAsCanvas_.canvas) {
158        var imageUrl = this.pictureAsCanvas_.canvas.toDataURL();
159        this.rasterArea_.style.backgroundImage = 'url("' + imageUrl + '")';
160      } else {
161        this.rasterArea_.style.backgroundImage = '';
162      }
163    },
164
165    rasterize_: function() {
166      if (this.picture_) {
167        this.picture_.rasterize(
168            {stopIndex: this.drawOpsView_.selectedOpIndex},
169            this.onRasterComplete_.bind(this));
170      }
171    },
172
173    onRasterComplete_: function(pictureAsCanvas) {
174      this.pictureAsCanvas_ = pictureAsCanvas;
175      this.scheduleUpdateContents_();
176    },
177
178    onChangeDrawOps_: function() {
179      this.rasterize_();
180      this.scheduleUpdateContents_();
181    }
182  };
183
184  return {
185    PictureDebugger: PictureDebugger
186  };
187});
188