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
7/**
8 * @fileoverview Graphical view of  LayerTreeImpl, with controls for
9 * type of layer content shown and info bar for content-loading warnings.
10 */
11
12base.requireStylesheet('cc.layer_tree_quad_stack_viewer');
13
14base.require('base.properties');
15base.require('base.raf');
16base.require('cc.constants');
17base.require('cc.picture');
18base.require('ui.quad_stack_viewer');
19base.require('ui.info_bar');
20
21
22base.exportTo('cc', function() {
23  var constants = {
24    // The ratio of quad stack pixels to world pixels before CSS scaling.
25    QUAD_STACK_SCALE: 0.5
26  };
27
28  /**
29   * @constructor
30   */
31  var LayerTreeQuadStackViewer = ui.define('layer-tree-quad-stack-viewer');
32
33  LayerTreeQuadStackViewer.prototype = {
34    __proto__: HTMLDivElement.prototype,
35
36    decorate: function() {
37      this.pictureAsCanvas_ = {}; // Maps picture.guid to PictureAsCanvas.
38      this.quads_ = [];
39      this.messages_ = [];
40      this.controls_ = document.createElement('top-controls');
41      this.infoBar_ = new ui.InfoBar();
42      this.quadStackViewer_ = new ui.QuadStackViewer();
43
44      this.appendChild(this.controls_);
45      this.appendChild(this.infoBar_);
46      this.appendChild(this.quadStackViewer_);
47
48      var scaleSelector = ui.createSelector(
49          this.quadStackViewer_, 'scale',
50          'layerViewer.scale', 0.375,
51          [{label: '6.25%', value: 0.0625},
52           {label: '12.5%', value: 0.125},
53           {label: '25%', value: 0.25},
54           {label: '37.5%', value: 0.375},
55           {label: '50%', value: 0.5},
56           {label: '75%', value: 0.75},
57           {label: '100%', value: 1},
58           {label: '200%', value: 2}
59          ]);
60      this.controls_.appendChild(scaleSelector);
61
62      var showOtherLayersCheckbox = ui.createCheckBox(
63          this, 'showOtherLayers',
64          'layerViewer.showOtherLayers', true,
65          'Show other layers');
66      this.controls_.appendChild(showOtherLayersCheckbox);
67
68      var showInvalidationsCheckbox = ui.createCheckBox(
69          this, 'showInvalidations',
70          'layerViewer.showInvalidations', true,
71          'Show invalidations');
72      this.controls_.appendChild(showInvalidationsCheckbox);
73
74      var showContentsCheckbox = ui.createCheckBox(
75          this, 'showContents',
76          'layerViewer.showContents', true,
77          'Show contents');
78      this.controls_.appendChild(showContentsCheckbox);
79    },
80
81    get layerTreeImpl() {
82      return this.layerTreeImpl_;
83    },
84
85    set layerTreeImpl(layerTreeImpl) {
86      // FIXME(pdr): We may want to clear pictureAsCanvas_ here to save memory
87      //             at the cost of performance. Note that pictureAsCanvas_ will
88      //             be cleared when this is destructed, but this view might
89      //             live for several layerTreeImpls.
90      this.layerTreeImpl_ = layerTreeImpl;
91      this.selection = null;
92      this.updateContents_();
93    },
94
95    get showOtherLayers() {
96      return this.showOtherLayers_;
97    },
98
99    set showOtherLayers(show) {
100      this.showOtherLayers_ = show;
101      this.updateContents_();
102    },
103
104    get showContents() {
105      return this.showContents_;
106    },
107
108    set showContents(show) {
109      this.showContents_ = show;
110      this.updateContents_();
111    },
112
113    get showInvalidations() {
114      return this.showInvalidations_;
115    },
116
117    set showInvalidations(show) {
118      this.showInvalidations_ = show;
119      this.updateContents_();
120    },
121
122    get selection() {
123      return this.selection_;
124    },
125
126    set selection(selection) {
127      base.setPropertyAndDispatchChange(this, 'selection', selection);
128      this.updateContents_();
129    },
130
131    scheduleUpdateContents_: function() {
132      if (this.updateContentsPending_)
133        return;
134      this.updateContentsPending_ = true;
135      base.requestAnimationFrameInThisFrameIfPossible(
136          this.updateContents_, this);
137    },
138
139    updateContents_: function() {
140      if (!this.layerTreeImpl_)
141        return;
142
143      if (this.pictureLoadingComplete_())
144        this.generateQuads_();
145    },
146
147    pictureLoadingComplete_: function() {
148      // Figure out if we can draw the quads yet. While we're at it, figure out
149      // if we have any warnings we need to show.
150      var layers = this.layers;
151      var messages = [];
152      if (this.showContents) {
153        var hasPendingRasterizeImage = false;
154        var firstPictureError = undefined;
155        var hasMissingLayerRect = false;
156        var hasUnresolvedPictureRef = false;
157        for (var i = 0; i < layers.length; i++) {
158          var layer = layers[i];
159          for (var ir = layer.pictures.length - 1; ir >= 0; ir--) {
160            var picture = layer.pictures[ir];
161
162            if (picture.idRef) {
163              hasUnresolvedPictureRef = true;
164              continue;
165            }
166            if (!picture.layerRect) {
167              hasMissingLayerRect = true;
168              continue;
169            }
170
171            var pictureAsCanvas = this.pictureAsCanvas_[picture.guid];
172            if (!pictureAsCanvas) {
173              hasPendingRasterizeImage = true;
174              this.pictureAsCanvas_[picture.guid] =
175                  cc.PictureAsCanvas.Pending(this);
176              picture.rasterize(
177                  {stopIndex: undefined},
178                  function(pictureAsCanvas) {
179                    var picture_ = pictureAsCanvas.picture;
180                    this.pictureAsCanvas_[picture_.guid] = pictureAsCanvas;
181                    this.scheduleUpdateContents_();
182                  }.bind(this));
183              continue;
184            }
185            if (pictureAsCanvas.isPending()) {
186              hasPendingRasterizeImage = true;
187              continue;
188            }
189            if (pictureAsCanvas.error) {
190              if (!firstPictureError)
191                firstPictureError = pictureAsCanvas.error;
192              break;
193            }
194          }
195        }
196        if (hasPendingRasterizeImage)
197          return false;
198
199        if (hasUnresolvedPictureRef) {
200          messages.push({
201            header: 'Missing picture',
202            details: 'Your trace didnt have pictures for every layer. ' +
203                'Old chrome versions had this problem'});
204        }
205        if (hasMissingLayerRect) {
206          messages.push({
207            header: 'Missing layer rect',
208            details: 'Your trace may be corrupt or from a very old ' +
209                'Chrome revision.'});
210        }
211        if (firstPictureError) {
212          messages.push({
213            header: 'Cannot rasterize',
214            details: firstPictureError});
215        }
216      }
217      this.messages_ = messages;
218      return true;
219    },
220
221    get selectedLayer() {
222      if (this.selection) {
223        var selectedLayerId = this.selection.associatedLayerId;
224        return this.layerTreeImpl_.findLayerWithId(selectedLayerId);
225      }
226    },
227
228    get layers() {
229      var layers = this.layerTreeImpl.renderSurfaceLayerList;
230      if (!this.showOtherLayers) {
231        var selectedLayer = this.selectedLayer;
232        if (selectedLayer)
233          layers = [selectedLayer];
234      }
235      return layers;
236    },
237
238    appendImageQuads_: function(layer, layerQuad) {
239      // Generate image quads for the layer
240      for (var ir = layer.pictures.length - 1; ir >= 0; ir--) {
241        var picture = layer.pictures[ir];
242        if (!picture.layerRect)
243          continue;
244
245        var unitRect = picture.layerRect.asUVRectInside(layer.bounds);
246        var iq = layerQuad.projectUnitRect(unitRect);
247
248        var pictureAsCanvas = this.pictureAsCanvas_[picture.guid];
249        if (this.showContents && pictureAsCanvas.canvas)
250          iq.canvas = pictureAsCanvas.canvas;
251        else
252          iq.canvas = undefined;
253
254        iq.stackingGroupId = layerQuad.stackingGroupId;
255        this.quads_.push(iq);
256      }
257    },
258
259    appendInvalidationQuads_: function(layer, layerQuad) {
260      // Generate the invalidation rect quads.
261      for (var ir = 0; ir < layer.invalidation.rects.length; ir++) {
262        var rect = layer.invalidation.rects[ir];
263        var unitRect = rect.asUVRectInside(layer.bounds);
264        var iq = layerQuad.projectUnitRect(unitRect);
265        iq.backgroundColor = 'rgba(255, 0, 0, 0.1)';
266        iq.borderColor = 'rgba(255, 0, 0, 1)';
267        iq.stackingGroupId = layerQuad.stackingGroupId;
268        this.quads_.push(iq);
269      }
270    },
271
272    appendSelectionQuads_: function(layer, layerQuad) {
273      var selection = this.selection;
274      var quad = this.layerTreeImpl_.whichTree == constants.ACTIVE_TREE ?
275          selection.quadIfActive : selection.quadIfPending;
276      if (!quad)
277        return [];
278
279      var colorId = tracing.getStringColorId(selection.title);
280      colorId += tracing.getColorPaletteHighlightIdBoost();
281
282      var color = base.Color.fromString(tracing.getColorPalette()[colorId]);
283
284      var quadForDrawing = quad.clone();
285      quadForDrawing.backgroundColor = color.withAlpha(0.5).toString();
286      quadForDrawing.borderColor = color.withAlpha(1.0).darken().toString();
287      quadForDrawing.stackingGroupId = layerQuad.stackingGroupId;
288      return [quadForDrawing];
289    },
290
291    generateQuads_: function() {
292      this.updateContentsPending_ = false;
293
294      // Generate the quads for the view.
295      var layers = this.layers;
296      this.quads_ = [];
297      for (var i = 0; i < layers.length; i++) {
298        var layer = layers[i];
299
300        var layerQuad = layer.layerQuad.clone();
301        layerQuad.borderColor = 'rgba(0,0,0,0.75)';
302        layerQuad.stackingGroupId = i;
303        if (this.showOtherLayers && this.selectedLayer == layer)
304          layerQuad.upperBorderColor = 'rgb(156,189,45)';
305
306        this.appendImageQuads_(layer, layerQuad);
307
308        if (this.showInvalidations) {
309          this.appendInvalidationQuads_(layer, layerQuad);
310        }
311
312        // Push the layer quad last.
313        this.quads_.push(layerQuad);
314
315        if (this.selectedLayer === layer) {
316          this.appendSelectionQuads_(layer, layerQuad);
317        }
318      }
319      var lthi = this.layerTreeImpl_.layerTreeHostImpl;
320      var lthiInstance = lthi.objectInstance;
321      var worldViewportRect = base.Rect.FromXYWH(0, 0,
322          lthi.deviceViewportSize.width, lthi.deviceViewportSize.height);
323      this.quadStackViewer_.quadStack.initialize(
324          lthiInstance.allLayersBBox.asRect(), worldViewportRect);
325
326      this.quadStackViewer_.quadStack.quads = this.quads_;
327
328      this.updateInfoBar_(this.messages_);
329    },
330
331    updateInfoBar_: function(infoBarMessages) {
332      if (infoBarMessages.length) {
333        this.infoBar_.removeAllButtons();
334        this.infoBar_.message = 'Some problems were encountered...';
335        this.infoBar_.addButton('More info...', function() {
336          var overlay = new ui.Overlay();
337          overlay.textContent = '';
338          infoBarMessages.forEach(function(message) {
339            var title = document.createElement('h3');
340            title.textContent = message.header;
341
342            var details = document.createElement('div');
343            details.textContent = message.details;
344
345            overlay.appendChild(title);
346            overlay.appendChild(details);
347          });
348          overlay.visible = true;
349          overlay.obeyCloseEvents = true;
350        });
351        this.infoBar_.visible = true;
352      } else {
353        this.infoBar_.removeAllButtons();
354        this.infoBar_.message = '';
355        this.infoBar_.visible = false;
356      }
357    }
358  };
359
360  return {
361    LayerTreeQuadStackViewer: LayerTreeQuadStackViewer
362  };
363});
364