Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / cc / layer_tree_quad_stack_view.html
1 <!DOCTYPE html>
2 <!--
3 Copyright (c) 2013 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
6 -->
7
8 <link rel="import" href="/cc/picture.html">
9 <link rel="import" href="/cc/render_pass.html">
10 <link rel="import" href="/cc/tile.html">
11 <link rel="import" href="/cc/debug_colors.html">
12 <link rel="import" href="/cc/util.html">
13 <link rel="import" href="/tvcm/color.html">
14 <link rel="import" href="/tvcm/properties.html">
15 <link rel="import" href="/tvcm/raf.html">
16 <link rel="import" href="/tvcm/quad.html">
17 <link rel="import" href="/tvcm/range.html">
18 <link rel="import" href="/tvcm/ui/quad_stack_view.html">
19 <link rel="import" href="/tvcm/ui/info_bar.html">
20
21 <style>
22 layer-tree-quad-stack-view {
23   position: relative;
24 }
25
26 layer-tree-quad-stack-view > top-controls {
27   -webkit-flex: 0 0 auto;
28   background-image: -webkit-gradient(linear,
29                                      0 0, 100% 0,
30                                      from(#E5E5E5),
31                                      to(#D1D1D1));
32   border-bottom: 1px solid #8e8e8e;
33   border-top: 1px solid white;
34   display: flex;
35   flex-flow: row wrap;
36   flex-direction: row;
37   font-size:  14px;
38   padding-left: 2px;
39   overflow: hidden;
40 }
41
42 layer-tree-quad-stack-view > top-controls input[type='checkbox'] {
43   vertical-align: -2px;
44 }
45
46 layer-tree-quad-stack-view > .what-rasterized {
47   color: -webkit-link;
48   cursor: pointer;
49   text-decoration: underline;
50   position: absolute;
51   bottom: 10px;
52   left: 10px;
53 }
54
55 layer-tree-quad-stack-view > #input-event {
56   content: url('../images/input-event.png');
57   display: none;
58 }
59
60 </style>
61
62 <template id='layer-tree-quad-stack-view-template'>
63   <img id='input-event'/>
64 </template>
65
66 <script>
67 'use strict';
68
69 /**
70  * @fileoverview Graphical view of  LayerTreeImpl, with controls for
71  * type of layer content shown and info bar for content-loading warnings.
72  */
73 tvcm.exportTo('cc', function() {
74
75   var THIS_DOC = document.currentScript.ownerDocument;
76   var TILE_HEATMAP_TYPE = {};
77   TILE_HEATMAP_TYPE.NONE = 'none';
78   TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY = 'scheduledPriority';
79   TILE_HEATMAP_TYPE.DISTANCE_TO_VISIBLE = 'distanceToVisible';
80   TILE_HEATMAP_TYPE.USING_GPU_MEMORY = 'usingGpuMemory';
81
82   function createTileRectsSelectorBaseOptions() {
83     return [{label: 'None', value: 'none'},
84             {label: 'Coverage Rects', value: 'coverage'}];
85   }
86
87   var bytesToRoundedMegabytes = cc.bytesToRoundedMegabytes;
88
89
90   /**
91    * @constructor
92    */
93   var LayerTreeQuadStackView = tvcm.ui.define('layer-tree-quad-stack-view');
94
95   LayerTreeQuadStackView.prototype = {
96     __proto__: HTMLDivElement.prototype,
97
98     decorate: function() {
99       this.isRenderPassQuads_ = false;
100       this.pictureAsImageData_ = {}; // Maps picture.guid to PictureAsImageData.
101       this.messages_ = [];
102       this.controls_ = document.createElement('top-controls');
103       this.infoBar_ = new tvcm.ui.InfoBar();
104       this.quadStackView_ = new tvcm.ui.QuadStackView();
105       this.quadStackView_.addEventListener(
106           'selectionchange', this.onQuadStackViewSelectionChange_.bind(this));
107       this.extraHighlightsByLayerId_ = undefined;
108       this.inputEventImageData_ = undefined;
109
110       var m = tvcm.ui.MOUSE_SELECTOR_MODE;
111       var mms = this.quadStackView_.mouseModeSelector;
112       mms.settingsKey = 'cc.layerTreeQuadStackView.mouseModeSelector';
113       mms.setKeyCodeForMode(m.SELECTION, 'Z'.charCodeAt(0));
114       mms.setKeyCodeForMode(m.PANSCAN, 'X'.charCodeAt(0));
115       mms.setKeyCodeForMode(m.ZOOM, 'C'.charCodeAt(0));
116       mms.setKeyCodeForMode(m.ROTATE, 'V'.charCodeAt(0));
117
118       var node = tvcm.instantiateTemplate(
119           '#layer-tree-quad-stack-view-template', THIS_DOC);
120       this.appendChild(node);
121       this.appendChild(this.controls_);
122       this.appendChild(this.infoBar_);
123       this.appendChild(this.quadStackView_);
124
125       this.tileRectsSelector_ = tvcm.ui.createSelector(
126           this, 'howToShowTiles',
127           'layerView.howToShowTiles', 'none',
128           createTileRectsSelectorBaseOptions());
129       this.controls_.appendChild(this.tileRectsSelector_);
130
131       var tileHeatmapText = tvcm.ui.createSpan({
132         textContent: 'Tile heatmap:'
133       });
134       this.controls_.appendChild(tileHeatmapText);
135
136       var tileHeatmapSelector = tvcm.ui.createSelector(
137           this, 'tileHeatmapType',
138           'layerView.tileHeatmapType', TILE_HEATMAP_TYPE.NONE,
139           [{label: 'None',
140             value: TILE_HEATMAP_TYPE.NONE},
141            {label: 'Scheduled Priority',
142             value: TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY},
143            {label: 'Distance to Visible',
144             value: TILE_HEATMAP_TYPE.DISTANCE_TO_VISIBLE},
145            {label: 'Is using GPU memory',
146             value: TILE_HEATMAP_TYPE.USING_GPU_MEMORY}
147           ]);
148       this.controls_.appendChild(tileHeatmapSelector);
149
150       var showOtherLayersCheckbox = tvcm.ui.createCheckBox(
151           this, 'showOtherLayers',
152           'layerView.showOtherLayers', true,
153           'Other layers/passes');
154       showOtherLayersCheckbox.title =
155           'When checked, show all layers, selected or not.';
156       this.controls_.appendChild(showOtherLayersCheckbox);
157
158       var showInvalidationsCheckbox = tvcm.ui.createCheckBox(
159           this, 'showInvalidations',
160           'layerView.showInvalidations', true,
161           'Invalidations');
162       showInvalidationsCheckbox.title =
163           'When checked, compositing invalidations are highlighted in red';
164       this.controls_.appendChild(showInvalidationsCheckbox);
165
166       var showUnrecordedRegionCheckbox = tvcm.ui.createCheckBox(
167           this, 'showUnrecordedRegion',
168           'layerView.showUnrecordedRegion', true,
169           'Unrecorded area');
170       showUnrecordedRegionCheckbox.title =
171           'When checked, unrecorded areas are highlighted in yellow';
172       this.controls_.appendChild(showUnrecordedRegionCheckbox);
173
174       var showBottlenecksCheckbox = tvcm.ui.createCheckBox(
175           this, 'showBottlenecks',
176           'layerView.showBottlenecks', true,
177           'Bottlenecks');
178       showBottlenecksCheckbox.title =
179           'When checked, scroll bottlenecks are highlighted';
180       this.controls_.appendChild(showBottlenecksCheckbox);
181
182       var showLayoutRectsCheckbox = tvcm.ui.createCheckBox(
183           this, 'showLayoutRects',
184           'layerView.showLayoutRects', false,
185           'Layout rects');
186       showLayoutRectsCheckbox.title =
187           'When checked, shows rects for regions where layout happened';
188       this.controls_.appendChild(showLayoutRectsCheckbox);
189
190       var showContentsCheckbox = tvcm.ui.createCheckBox(
191           this, 'showContents',
192           'layerView.showContents', true,
193           'Contents');
194       showContentsCheckbox.title =
195           'When checked, show the rendered contents inside the layer outlines';
196       this.controls_.appendChild(showContentsCheckbox);
197
198       var showAnimationBoundsCheckbox = tvcm.ui.createCheckBox(
199           this, 'showAnimationBounds',
200           'layerView.showAnimationBounds', false,
201           'Animation Bounds');
202       showAnimationBoundsCheckbox.title = 'When checked, show a border around' +
203           ' a layer showing the extent of its animation.';
204       this.controls_.appendChild(showAnimationBoundsCheckbox);
205
206       var showInputEventsCheckbox = tvcm.ui.createCheckBox(
207           this, 'showInputEvents',
208           'layerView.showInputEvents', true,
209           'Input events');
210       showInputEventsCheckbox.title = 'When checked, input events are ' +
211           'displayed as circles.';
212       this.controls_.appendChild(showInputEventsCheckbox);
213
214       this.whatRasterizedLink_ = document.createElement('a');
215       this.whatRasterizedLink_.classList.add('what-rasterized');
216       this.whatRasterizedLink_.textContent = 'What rasterized?';
217       this.whatRasterizedLink_.addEventListener(
218           'click', this.onWhatRasterizedLinkClicked_.bind(this));
219       this.appendChild(this.whatRasterizedLink_);
220     },
221
222     get layerTreeImpl() {
223       return this.layerTreeImpl_;
224     },
225
226     set isRenderPassQuads(newValue) {
227       this.isRenderPassQuads_ = newValue;
228     },
229
230     set layerTreeImpl(layerTreeImpl) {
231       if (this.layerTreeImpl_ === layerTreeImpl)
232         return;
233
234       // FIXME(pdr): We may want to clear pictureAsImageData_ here to save
235       //             memory at the cost of performance. Note that
236       //             pictureAsImageData_ will be cleared when this is
237       //             destructed, but this view might live for several
238       //             layerTreeImpls.
239       this.layerTreeImpl_ = layerTreeImpl;
240       this.selection = undefined;
241     },
242
243     get extraHighlightsByLayerId() {
244       return this.extraHighlightsByLayerId_;
245     },
246
247     set extraHighlightsByLayerId(extraHighlightsByLayerId) {
248       this.extraHighlightsByLayerId_ = extraHighlightsByLayerId;
249       this.scheduleUpdateContents_();
250     },
251
252     get showOtherLayers() {
253       return this.showOtherLayers_;
254     },
255
256     set showOtherLayers(show) {
257       this.showOtherLayers_ = show;
258       this.updateContents_();
259     },
260
261     get showAnimationBounds() {
262       return this.showAnimationBounds_;
263     },
264
265     set showAnimationBounds(show) {
266       this.showAnimationBounds_ = show;
267       this.updateContents_();
268     },
269
270     get showInputEvents() {
271       return this.showInputEvents_;
272     },
273
274     set showInputEvents(show) {
275       this.showInputEvents_ = show;
276       this.updateContents_();
277     },
278
279     get showContents() {
280       return this.showContents_;
281     },
282
283     set showContents(show) {
284       this.showContents_ = show;
285       this.updateContents_();
286     },
287
288     get showInvalidations() {
289       return this.showInvalidations_;
290     },
291
292     set showInvalidations(show) {
293       this.showInvalidations_ = show;
294       this.updateContents_();
295     },
296
297     get showUnrecordedRegion() {
298       return this.showUnrecordedRegion_;
299     },
300
301     set showUnrecordedRegion(show) {
302       this.showUnrecordedRegion_ = show;
303       this.updateContents_();
304     },
305
306     get showBottlenecks() {
307       return this.showBottlenecks_;
308     },
309
310     set showBottlenecks(show) {
311       this.showBottlenecks_ = show;
312       this.updateContents_();
313     },
314
315     get showLayoutRects() {
316       return this.showLayoutRects_;
317     },
318
319     set showLayoutRects(show) {
320       this.showLayoutRects_ = show;
321       this.updateContents_();
322     },
323
324     get howToShowTiles() {
325       return this.howToShowTiles_;
326     },
327
328     set howToShowTiles(val) {
329       // Make sure val is something we expect.
330       console.assert(
331           (val === 'none') ||
332           (val === 'coverage') ||
333           !isNaN(parseFloat(val)));
334
335       this.howToShowTiles_ = val;
336       this.updateContents_();
337     },
338
339     get tileHeatmapType() {
340       return this.tileHeatmapType_;
341     },
342
343     set tileHeatmapType(val) {
344       this.tileHeatmapType_ = val;
345       this.updateContents_();
346     },
347
348     get selection() {
349       return this.selection_;
350     },
351
352     set selection(selection) {
353       if (this.selection === selection)
354         return;
355       this.selection_ = selection;
356       tvcm.dispatchSimpleEvent(this, 'selection-change');
357       this.updateContents_();
358     },
359
360     regenerateContent: function() {
361       this.updateTilesSelector_();
362       this.updateContents_();
363     },
364
365     loadDataForImageElement_: function(image, callback) {
366       var imageContent = window.getComputedStyle(image).content;
367       image.src = imageContent.replace(/url\((.*)\)/, '$1');
368       image.onload = function() {
369         var canvas = document.createElement('canvas');
370         var ctx = canvas.getContext('2d');
371         canvas.width = image.width;
372         canvas.height = image.height;
373         ctx.drawImage(image, 0, 0);
374         var imageData = ctx.getImageData(
375             0, 0, canvas.width, canvas.height);
376         callback(imageData);
377       }
378     },
379
380     onQuadStackViewSelectionChange_: function(e) {
381       var selectableQuads = e.quads.filter(function(q) {
382         return q.selectionToSetIfClicked !== undefined;
383       });
384       if (selectableQuads.length == 0) {
385         this.selection = undefined;
386         return;
387       }
388
389       // Sort the quads low to high on stackingGroupId.
390       selectableQuads.sort(function(x, y) {
391         var z = x.stackingGroupId - y.stackingGroupId;
392         if (z != 0)
393           return z;
394         return x.selectionToSetIfClicked.specicifity -
395             y.selectionToSetIfClicked.specicifity;
396       });
397
398       // TODO(nduca): Support selecting N things at once.
399       var quadToSelect = selectableQuads[selectableQuads.length - 1];
400       this.selection = quadToSelect.selectionToSetIfClicked;
401     },
402
403     scheduleUpdateContents_: function() {
404       if (this.updateContentsPending_)
405         return;
406       this.updateContentsPending_ = true;
407       tvcm.requestAnimationFrameInThisFrameIfPossible(
408           this.updateContents_, this);
409     },
410
411     updateContents_: function() {
412       if (!this.layerTreeImpl_) {
413         this.quadStackView_.headerText = 'No tree';
414         this.quadStackView_.quads = [];
415         return;
416       }
417
418
419       var status = this.computePictureLoadingStatus_();
420       if (!status.picturesComplete)
421         return;
422
423       var lthi = this.layerTreeImpl_.layerTreeHostImpl;
424       var lthiInstance = lthi.objectInstance;
425       var worldViewportRect = tvcm.Rect.fromXYWH(
426           0, 0,
427           lthi.deviceViewportSize.width, lthi.deviceViewportSize.height);
428       this.quadStackView_.deviceRect = worldViewportRect;
429       if (this.isRenderPassQuads_)
430         this.quadStackView_.quads = this.generateRenderPassQuads();
431       else
432         this.quadStackView_.quads = this.generateLayerQuads();
433
434       this.updateWhatRasterizedLinkState_();
435
436       var message = '';
437       if (lthi.tilesHaveGpuMemoryUsageInfo) {
438         var thisTreeUsageInBytes = this.layerTreeImpl_.gpuMemoryUsageInBytes;
439         var otherTreeUsageInBytes = lthi.gpuMemoryUsageInBytes -
440             thisTreeUsageInBytes;
441         message += bytesToRoundedMegabytes(thisTreeUsageInBytes) +
442                 'MB on this tree';
443         if (otherTreeUsageInBytes) {
444           message += ', ' +
445               bytesToRoundedMegabytes(otherTreeUsageInBytes) +
446               'MB on the other tree';
447         }
448       } else {
449         if (this.layerTreeImpl_) {
450           var thisTreeUsageInBytes = this.layerTreeImpl_.gpuMemoryUsageInBytes;
451           message += bytesToRoundedMegabytes(thisTreeUsageInBytes) +
452               'MB on this tree';
453
454           if (this.layerTreeImpl_.otherTree) {
455             // Older Chromes don't report enough data to know how much memory
456             // is being used across both trees. We know the memory consumed by
457             // each tree, but there is resource sharing *between the trees* so
458             // we can't simply sum up the per-tree costs. We need either the total
459             // plus one tree, to guess the unique on the other tree, etc. Newer
460             // chromes report memory per tile, which allows LTHI to compute the
461             // total tile memory usage, letting us figure things out properly.
462             message += ', ???MB on other tree. ';
463           }
464         }
465       }
466
467       if (lthi.args.tileManagerBasicState) {
468         var tmgs = lthi.args.tileManagerBasicState.globalState;
469         message += ' (softMax=' +
470           bytesToRoundedMegabytes(tmgs.softMemoryLimitInBytes) +
471           'MB, hardMax=' +
472           bytesToRoundedMegabytes(tmgs.hardMemoryLimitInBytes) + 'MB, ' +
473           tmgs.memoryLimitPolicy + ')';
474
475       } else {
476         // Old Chromes do not have a globalState on the LTHI dump.
477         // But they do issue a DidManage event wiht the globalstate. Find that
478         // event so that we show some global state.
479         var thread = lthi.snapshottedOnThread;
480         var didManageTilesSlices = thread.sliceGroup.slices.filter(function(s) {
481           if (s.category !== 'cc')
482             return false;
483           if (s.title !== 'DidManage')
484             return false;
485           if (s.end > lthi.ts)
486             return false;
487           return true;
488         });
489         didManageTilesSlices.sort(function(x, y) {
490           return x.end - y.end;
491         });
492         if (didManageTilesSlices.length > 0) {
493           var newest = didManageTilesSlices[didManageTilesSlices.length - 1];
494           var tmgs = newest.args.state.global_state;
495           message += ' (softMax=' +
496             bytesToRoundedMegabytes(tmgs.soft_memory_limit_in_bytes) +
497             'MB, hardMax=' +
498             bytesToRoundedMegabytes(tmgs.hard_memory_limit_in_bytes) + 'MB, ' +
499             tmgs.memory_limit_policy + ')';
500         }
501       }
502
503       if (this.layerTreeImpl_.otherTree)
504         message += ' (Another tree exists)';
505
506
507       if (message.length)
508         this.quadStackView_.headerText = message;
509       else
510         this.quadStackView_.headerText = undefined;
511
512       this.updateInfoBar_(status.messages);
513     },
514
515     updateTilesSelector_: function() {
516       var data = createTileRectsSelectorBaseOptions();
517
518       if (this.layerTreeImpl_) {
519         // First get all of the scales information from LTHI.
520         var lthi = this.layerTreeImpl_.layerTreeHostImpl;
521         var scaleNames = lthi.getContentsScaleNames();
522         for (var scale in scaleNames) {
523           data.push({
524             label: 'Scale ' + scale + ' (' + scaleNames[scale] + ')',
525             value: scale
526           });
527         }
528       }
529
530       // Then create a new selector and replace the old one.
531       var new_selector = tvcm.ui.createSelector(
532           this, 'howToShowTiles',
533           'layerView.howToShowTiles', 'none',
534           data);
535       this.controls_.replaceChild(new_selector, this.tileRectsSelector_);
536       this.tileRectsSelector_ = new_selector;
537     },
538
539     computePictureLoadingStatus_: function() {
540       // Figure out if we can draw the quads yet. While we're at it, figure out
541       // if we have any warnings we need to show.
542       var layers = this.layers;
543       var status = {
544         messages: [],
545         picturesComplete: true
546       };
547       if (this.showContents) {
548         var hasPendingRasterizeImage = false;
549         var firstPictureError = undefined;
550         var hasMissingLayerRect = false;
551         var hasUnresolvedPictureRef = false;
552         for (var i = 0; i < layers.length; i++) {
553           var layer = layers[i];
554           for (var ir = 0; ir < layer.pictures.length; ++ir) {
555             var picture = layer.pictures[ir];
556
557             if (picture.idRef) {
558               hasUnresolvedPictureRef = true;
559               continue;
560             }
561             if (!picture.layerRect) {
562               hasMissingLayerRect = true;
563               continue;
564             }
565
566             var pictureAsImageData = this.pictureAsImageData_[picture.guid];
567             if (!pictureAsImageData) {
568               hasPendingRasterizeImage = true;
569               this.pictureAsImageData_[picture.guid] =
570                   cc.PictureAsImageData.Pending(this);
571               picture.rasterize(
572                   {stopIndex: undefined},
573                   function(pictureImageData) {
574                     var picture_ = pictureImageData.picture;
575                     this.pictureAsImageData_[picture_.guid] = pictureImageData;
576                     this.scheduleUpdateContents_();
577                   }.bind(this));
578               continue;
579             }
580             if (pictureAsImageData.isPending()) {
581               hasPendingRasterizeImage = true;
582               continue;
583             }
584             if (pictureAsImageData.error) {
585               if (!firstPictureError)
586                 firstPictureError = pictureAsImageData.error;
587               break;
588             }
589           }
590         }
591         if (hasPendingRasterizeImage) {
592           status.picturesComplete = false;
593         } else {
594           if (hasUnresolvedPictureRef) {
595             status.messages.push({
596               header: 'Missing picture',
597               details: 'Your trace didnt have pictures for every layer. ' +
598                   'Old chrome versions had this problem'});
599           }
600           if (hasMissingLayerRect) {
601             status.messages.push({
602               header: 'Missing layer rect',
603               details: 'Your trace may be corrupt or from a very old ' +
604                   'Chrome revision.'});
605           }
606           if (firstPictureError) {
607             status.messages.push({
608               header: 'Cannot rasterize',
609               details: firstPictureError});
610           }
611         }
612       }
613       if (this.showInputEvents && this.layerTreeImpl.tracedInputLatencies &&
614           this.inputEventImageData_ === undefined) {
615         var image = this.querySelector('#input-event');
616         if (!image.src) {
617           this.loadDataForImageElement_(image, function(imageData) {
618             this.inputEventImageData_ = imageData;
619             this.updateContentsPending_ = false;
620             this.scheduleUpdateContents_();
621           }.bind(this));
622         }
623         status.picturesComplete = false;
624       }
625       return status;
626     },
627
628     get selectedRenderPass() {
629       if (this.selection)
630         return this.selection.renderPass_;
631     },
632
633     get selectedLayer() {
634       if (this.selection) {
635         var selectedLayerId = this.selection.associatedLayerId;
636         return this.layerTreeImpl_.findLayerWithId(selectedLayerId);
637       }
638     },
639
640     get renderPasses() {
641       var renderPasses =
642           this.layerTreeImpl.layerTreeHostImpl.args.frame.renderPasses;
643       if (!this.showOtherLayers) {
644         var selectedRenderPass = this.selectedRenderPass;
645         if (selectedRenderPass)
646           renderPasses = [selectedRenderPass];
647       }
648       return renderPasses;
649     },
650
651     get layers() {
652       var layers = this.layerTreeImpl.renderSurfaceLayerList;
653       if (!this.showOtherLayers) {
654         var selectedLayer = this.selectedLayer;
655         if (selectedLayer)
656           layers = [selectedLayer];
657       }
658       return layers;
659     },
660
661     appendImageQuads_: function(quads, layer, layerQuad) {
662       // Generate image quads for the layer
663       for (var ir = 0; ir < layer.pictures.length; ++ir) {
664         var picture = layer.pictures[ir];
665         if (!picture.layerRect)
666           continue;
667
668         var unitRect = picture.layerRect.asUVRectInside(layer.bounds);
669         var iq = layerQuad.projectUnitRect(unitRect);
670
671         var pictureData = this.pictureAsImageData_[picture.guid];
672         if (this.showContents && pictureData && pictureData.imageData) {
673           iq.imageData = pictureData.imageData;
674           iq.borderColor = 'rgba(0,0,0,0)';
675         } else {
676           iq.imageData = undefined;
677         }
678
679         iq.stackingGroupId = layerQuad.stackingGroupId;
680         quads.push(iq);
681       }
682     },
683
684     appendAnimationQuads_: function(quads, layer, layerQuad) {
685       if (!layer.animationBoundsRect)
686         return;
687
688       var rect = layer.animationBoundsRect;
689       var abq = tvcm.Quad.fromRect(rect);
690
691       abq.backgroundColor = 'rgba(164,191,48,0.5)';
692       abq.borderColor = 'rgba(205,255,0,0.75)';
693       abq.borderWidth = 3.0;
694       abq.stackingGroupId = layerQuad.stackingGroupId;
695       abq.selectionToSetIfClicked = new cc.AnimationRectSelection(
696           layer, rect);
697       quads.push(abq);
698     },
699
700     appendInvalidationQuads_: function(quads, layer, layerQuad) {
701       if (layer.layerTreeImpl.hasSourceFrameBeenDrawnBefore)
702         return;
703
704       // Generate the invalidation rect quads.
705       for (var ir = 0; ir < layer.invalidation.rects.length; ir++) {
706         var rect = layer.invalidation.rects[ir];
707         var unitRect = rect.asUVRectInside(layer.bounds);
708         var iq = layerQuad.projectUnitRect(unitRect);
709         iq.backgroundColor = 'rgba(0, 255, 0, 0.1)';
710         iq.borderColor = 'rgba(0, 255, 0, 1)';
711         iq.stackingGroupId = layerQuad.stackingGroupId;
712         iq.selectionToSetIfClicked = new cc.LayerRectSelection(
713             layer, 'Invalidation rect', rect, rect);
714         quads.push(iq);
715       }
716     },
717
718     appendUnrecordedRegionQuads_: function(quads, layer, layerQuad) {
719       // Generate the unrecorded region quads.
720       for (var ir = 0; ir < layer.unrecordedRegion.rects.length; ir++) {
721         var rect = layer.unrecordedRegion.rects[ir];
722         var unitRect = rect.asUVRectInside(layer.bounds);
723         var iq = layerQuad.projectUnitRect(unitRect);
724         iq.backgroundColor = 'rgba(240, 230, 140, 0.3)';
725         iq.borderColor = 'rgba(240, 230, 140, 1)';
726         iq.stackingGroupId = layerQuad.stackingGroupId;
727         iq.selectionToSetIfClicked = new cc.LayerRectSelection(
728             layer, 'Unrecorded area', rect, rect);
729         quads.push(iq);
730       }
731     },
732
733     appendBottleneckQuads_: function(quads, layer, layerQuad, stackingGroupId) {
734       function processRegion(region, label, borderColor) {
735         var backgroundColor = borderColor.clone();
736         backgroundColor.a = 0.4 * (borderColor.a || 1.0);
737
738         if (!region || !region.rects)
739           return;
740
741         for (var ir = 0; ir < region.rects.length; ir++) {
742           var rect = region.rects[ir];
743           var unitRect = rect.asUVRectInside(layer.bounds);
744           var iq = layerQuad.projectUnitRect(unitRect);
745           iq.backgroundColor = backgroundColor.toString();
746           iq.borderColor = borderColor.toString();
747           iq.borderWidth = 4.0;
748           iq.stackingGroupId = stackingGroupId;
749           iq.selectionToSetIfClicked = new cc.LayerRectSelection(
750               layer, label, rect, rect);
751           quads.push(iq);
752         }
753       }
754
755       processRegion(layer.touchEventHandlerRegion, 'Touch listener',
756                     tvcm.Color.fromString('rgb(228, 226, 27)'));
757       processRegion(layer.wheelEventHandlerRegion, 'Wheel listener',
758                     tvcm.Color.fromString('rgb(176, 205, 29)'));
759       processRegion(layer.nonFastScrollableRegion, 'Repaints on scroll',
760                     tvcm.Color.fromString('rgb(213, 134, 32)'));
761     },
762
763     appendTileCoverageRectQuads_: function(
764         quads, layer, layerQuad, heatmapType) {
765       if (!layer.tileCoverageRects)
766         return;
767
768       var tiles = [];
769       for (var ct = 0; ct < layer.tileCoverageRects.length; ++ct) {
770         var tile = layer.tileCoverageRects[ct].tile;
771         if (tile !== undefined)
772           tiles.push(tile);
773       }
774
775       var lthi = this.layerTreeImpl_.layerTreeHostImpl;
776       var minMax =
777           this.getMinMaxForHeatmap_(lthi.activeTiles, heatmapType);
778       var heatmapResult =
779           this.computeHeatmapColors_(tiles, minMax, heatmapType);
780       var heatIndex = 0;
781
782       for (var ct = 0; ct < layer.tileCoverageRects.length; ++ct) {
783         var rect = layer.tileCoverageRects[ct].geometryRect;
784         rect = rect.scale(1.0 / layer.geometryContentsScale);
785
786         var tile = layer.tileCoverageRects[ct].tile;
787
788         var unitRect = rect.asUVRectInside(layer.bounds);
789         var quad = layerQuad.projectUnitRect(unitRect);
790
791         quad.backgroundColor = 'rgba(0, 0, 0, 0)';
792         quad.stackingGroupId = layerQuad.stackingGroupId;
793         var type = cc.tileTypes.missing;
794         if (tile) {
795           type = tile.getTypeForLayer(layer);
796           quad.backgroundColor = heatmapResult[heatIndex].color;
797           ++heatIndex;
798         }
799
800         quad.borderColor = cc.tileBorder[type].color;
801         quad.borderWidth = cc.tileBorder[type].width;
802         var label;
803         if (tile)
804           label = 'coverageRect';
805         else
806           label = 'checkerboard coverageRect';
807         quad.selectionToSetIfClicked = new cc.LayerRectSelection(
808             layer, label, rect, layer.tileCoverageRects[ct]);
809
810         quads.push(quad);
811       }
812     },
813
814     appendLayoutRectQuads_: function(quads, layer, layerQuad) {
815       if (!layer.layoutRects) {
816         return;
817       }
818
819       for (var ct = 0; ct < layer.layoutRects.length; ++ct) {
820         var rect = layer.layoutRects[ct].geometryRect;
821         rect = rect.scale(1.0 / layer.geometryContentsScale);
822
823         var unitRect = rect.asUVRectInside(layer.bounds);
824         var quad = layerQuad.projectUnitRect(unitRect);
825
826         quad.backgroundColor = 'rgba(0, 0, 0, 0)';
827         quad.stackingGroupId = layerQuad.stackingGroupId;
828
829         quad.borderColor = 'rgba(0, 0, 200, 0.7)';
830         quad.borderWidth = 2;
831         var label;
832         label = 'Layout rect';
833         quad.selectionToSetIfClicked = new cc.LayerRectSelection(
834             layer, label, rect);
835
836         quads.push(quad);
837       }
838     },
839
840     getValueForHeatmap_: function(tile, heatmapType) {
841       if (heatmapType == TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY) {
842         return tile.scheduledPriority == 0 ?
843             undefined :
844             tile.scheduledPriority;
845       } else if (heatmapType == TILE_HEATMAP_TYPE.DISTANCE_TO_VISIBLE) {
846         return Math.min(3000, tile.distanceToVisible);
847       } else if (heatmapType == TILE_HEATMAP_TYPE.USING_GPU_MEMORY) {
848         if (tile.isSolidColor)
849           return 0.5;
850         return tile.isUsingGpuMemory ? 0 : 1;
851       }
852     },
853
854     getMinMaxForHeatmap_: function(tiles, heatmapType) {
855       var range = new tvcm.Range();
856       if (heatmapType == TILE_HEATMAP_TYPE.USING_GPU_MEMORY) {
857         range.addValue(0);
858         range.addValue(1);
859         return range;
860       }
861
862       for (var i = 0; i < tiles.length; ++i) {
863         var value = this.getValueForHeatmap_(tiles[i], heatmapType);
864         if (value === undefined)
865           continue;
866         range.addValue(value);
867       }
868       if (range.range === 0)
869         range.addValue(1);
870       return range;
871     },
872
873     computeHeatmapColors_: function(tiles, minMax, heatmapType) {
874       var min = minMax.min;
875       var max = minMax.max;
876
877       var color = function(value) {
878         var hue = 120 * (1 - (value - min) / (max - min));
879         if (hue < 0)
880           hue = 0;
881         return 'hsla(' + hue + ', 100%, 50%, 0.5)';
882       };
883
884       var values = [];
885       for (var i = 0; i < tiles.length; ++i) {
886         var tile = tiles[i];
887         var value = this.getValueForHeatmap_(tile, heatmapType);
888         var res = {
889           value: value,
890           color: value !== undefined ? color(value) : undefined
891         };
892         values.push(res);
893       }
894
895       return values;
896     },
897
898     appendTilesWithScaleQuads_: function(
899         quads, layer, layerQuad, scale, heatmapType) {
900       var lthi = this.layerTreeImpl_.layerTreeHostImpl;
901
902       var tiles = [];
903       for (var i = 0; i < lthi.activeTiles.length; ++i) {
904         var tile = lthi.activeTiles[i];
905
906         if (Math.abs(tile.contentsScale - scale) > 1e-6)
907           continue;
908
909         // TODO(vmpstr): Make the stiching of tiles and layers a part of
910         // tile construction (issue 346)
911         if (layer.layerId != tile.layerId)
912           continue;
913
914         tiles.push(tile);
915       }
916
917       var minMax =
918           this.getMinMaxForHeatmap_(lthi.activeTiles, heatmapType);
919       var heatmapResult =
920           this.computeHeatmapColors_(tiles, minMax, heatmapType);
921
922       for (var i = 0; i < tiles.length; ++i) {
923         var tile = tiles[i];
924         var rect = tile.layerRect;
925         if (!tile.layerRect)
926           continue;
927         var unitRect = rect.asUVRectInside(layer.bounds);
928         var quad = layerQuad.projectUnitRect(unitRect);
929
930         quad.backgroundColor = 'rgba(0, 0, 0, 0)';
931         quad.stackingGroupId = layerQuad.stackingGroupId;
932
933         var type = tile.getTypeForLayer(layer);
934         quad.borderColor = cc.tileBorder[type].color;
935         quad.borderWidth = cc.tileBorder[type].width;
936
937         quad.backgroundColor = heatmapResult[i].color;
938         var data = {
939           tileType: type,
940         };
941         if (heatmapType !== TILE_HEATMAP_TYPE.NONE)
942           data[heatmapType] = heatmapResult[i].value;
943         quad.selectionToSetIfClicked = new cc.TileSelection(tile, data);
944         quads.push(quad);
945       }
946     },
947
948     appendHighlightQuadsForLayer_: function(
949         quads, layer, layerQuad, highlights) {
950       highlights.forEach(function(highlight) {
951         var rect = highlight.rect;
952
953         var unitRect = rect.asUVRectInside(layer.bounds);
954         var quad = layerQuad.projectUnitRect(unitRect);
955
956         var colorId = tvcm.ui.getStringColorId(highlight.colorKey);
957         colorId += tvcm.ui.getColorPaletteHighlightIdBoost();
958
959         var color = tvcm.Color.fromString(tvcm.ui.getColorPalette()[colorId]);
960
961         var quadForDrawing = quad.clone();
962         quadForDrawing.backgroundColor = color.withAlpha(0.5).toString();
963         quadForDrawing.borderColor = color.withAlpha(1.0).darken().toString();
964         quadForDrawing.stackingGroupId = layerQuad.stackingGroupId;
965         quads.push(quadForDrawing);
966
967       }, this);
968     },
969
970     generateRenderPassQuads: function() {
971       if (!this.layerTreeImpl.layerTreeHostImpl.args.frame)
972         return [];
973       var renderPasses = this.renderPasses;
974       if (!renderPasses)
975         return [];
976
977       var quads = [];
978       for (var i = 0; i < renderPasses.length; ++i) {
979         var quadList = renderPasses[i].quadList;
980         for (var j = 0; j < quadList.length; ++j) {
981           var drawQuad = quadList[j];
982           var quad = drawQuad.rectAsTargetSpaceQuad.clone();
983           quad.borderColor = 'rgb(170, 204, 238)';
984           quad.borderWidth = 2;
985           quad.stackingGroupId = i;
986           quads.push(quad);
987         }
988       }
989       return quads;
990     },
991
992     generateLayerQuads: function() {
993       this.updateContentsPending_ = false;
994
995       // Generate the quads for the view.
996       var layers = this.layers;
997       var quads = [];
998       var nextStackingGroupId = 0;
999       var alreadyVisitedLayerIds = {};
1000
1001
1002       var selectionHighlightsByLayerId;
1003       if (this.selection)
1004         selectionHighlightsByLayerId = this.selection.highlightsByLayerId;
1005       else
1006         selectionHighlightsByLayerId = {};
1007
1008       var extraHighlightsByLayerId = this.extraHighlightsByLayerId || {};
1009
1010       for (var i = 1; i <= layers.length; i++) {
1011         // Generate quads back-to-front.
1012         var layer = layers[layers.length - i];
1013         alreadyVisitedLayerIds[layer.layerId] = true;
1014         if (layer.objectInstance.name == 'cc::NinePatchLayerImpl')
1015           continue;
1016
1017         var layerQuad = layer.layerQuad.clone();
1018         if (layer.usingGpuRasterization) {
1019           var pixelRatio = window.devicePixelRatio || 1;
1020           layerQuad.borderWidth = 2.0 * pixelRatio;
1021           layerQuad.borderColor = 'rgba(154,205,50,0.75)';
1022         } else {
1023           layerQuad.borderColor = 'rgba(0,0,0,0.75)';
1024         }
1025         layerQuad.stackingGroupId = nextStackingGroupId++;
1026         layerQuad.selectionToSetIfClicked = new cc.LayerSelection(layer);
1027         layerQuad.layer = layer;
1028         if (this.showOtherLayers && this.selectedLayer == layer)
1029           layerQuad.upperBorderColor = 'rgb(156,189,45)';
1030
1031         if (this.showAnimationBounds)
1032           this.appendAnimationQuads_(quads, layer, layerQuad);
1033
1034         this.appendImageQuads_(quads, layer, layerQuad);
1035         quads.push(layerQuad);
1036
1037
1038         if (this.showInvalidations)
1039           this.appendInvalidationQuads_(quads, layer, layerQuad);
1040         if (this.showUnrecordedRegion)
1041           this.appendUnrecordedRegionQuads_(quads, layer, layerQuad);
1042         if (this.showBottlenecks)
1043           this.appendBottleneckQuads_(quads, layer, layerQuad,
1044                                       layerQuad.stackingGroupId);
1045         if (this.showLayoutRects)
1046           this.appendLayoutRectQuads_(quads, layer, layerQuad);
1047
1048         if (this.howToShowTiles === 'coverage') {
1049           this.appendTileCoverageRectQuads_(
1050               quads, layer, layerQuad, this.tileHeatmapType);
1051         } else if (this.howToShowTiles !== 'none') {
1052           this.appendTilesWithScaleQuads_(
1053               quads, layer, layerQuad,
1054               this.howToShowTiles, this.tileHeatmapType);
1055         }
1056
1057         var highlights;
1058         highlights = extraHighlightsByLayerId[layer.layerId];
1059         if (highlights) {
1060           this.appendHighlightQuadsForLayer_(
1061               quads, layer, layerQuad, highlights)
1062         }
1063
1064         highlights = selectionHighlightsByLayerId[layer.layerId];
1065         if (highlights) {
1066           this.appendHighlightQuadsForLayer_(
1067               quads, layer, layerQuad, highlights)
1068         }
1069       }
1070
1071       this.layerTreeImpl.iterLayers(function(layer, depth, isMask, isReplica) {
1072         if (!this.showOtherLayers && this.selectedLayer != layer)
1073           return;
1074         if (alreadyVisitedLayerIds[layer.layerId])
1075           return;
1076         var layerQuad = layer.layerQuad;
1077         var stackingGroupId = nextStackingGroupId++;
1078         if (this.showBottlenecks)
1079           this.appendBottleneckQuads_(quads, layer, layerQuad, stackingGroupId);
1080       }, this);
1081
1082       var tracedInputLatencies = this.layerTreeImpl.tracedInputLatencies;
1083       if (this.showInputEvents && tracedInputLatencies) {
1084         for (var i = 0; i < tracedInputLatencies.length; i++) {
1085           var coordinatesArray = tracedInputLatencies[i].args.data.coordinates;
1086           for (var j = 0; j < coordinatesArray.length; j++) {
1087             var inputQuad = tvcm.Quad.fromXYWH(
1088                 coordinatesArray[j].x - 25,
1089                 coordinatesArray[j].y - 25,
1090                 50,
1091                 50);
1092             inputQuad.borderColor = 'rgba(0, 0, 0, 0)';
1093             inputQuad.imageData = this.inputEventImageData_;
1094             quads.push(inputQuad);
1095           }
1096         }
1097       }
1098
1099       return quads;
1100     },
1101
1102     updateInfoBar_: function(infoBarMessages) {
1103       if (infoBarMessages.length) {
1104         this.infoBar_.removeAllButtons();
1105         this.infoBar_.message = 'Some problems were encountered...';
1106         this.infoBar_.addButton('More info...', function(e) {
1107           var overlay = new tvcm.ui.Overlay();
1108           overlay.textContent = '';
1109           infoBarMessages.forEach(function(message) {
1110             var title = document.createElement('h3');
1111             title.textContent = message.header;
1112
1113             var details = document.createElement('div');
1114             details.textContent = message.details;
1115
1116             overlay.appendChild(title);
1117             overlay.appendChild(details);
1118           });
1119           overlay.visible = true;
1120
1121           e.stopPropagation();
1122           return false;
1123         });
1124         this.infoBar_.visible = true;
1125       } else {
1126         this.infoBar_.removeAllButtons();
1127         this.infoBar_.message = '';
1128         this.infoBar_.visible = false;
1129       }
1130     },
1131
1132     getWhatRasterized_: function() {
1133       var lthi = this.layerTreeImpl_.layerTreeHostImpl;
1134       var renderProcess = lthi.objectInstance.parent;
1135       var tasks = [];
1136       renderProcess.iterateAllEvents(function(event) {
1137         if (!(event instanceof tracing.trace_model.Slice))
1138           return;
1139
1140         var tile = cc.getTileFromRasterTaskSlice(event);
1141         if (tile === undefined)
1142           return false;
1143
1144         if (tile.containingSnapshot == lthi)
1145           tasks.push(event)
1146       }, this);
1147       return tasks;
1148     },
1149
1150     updateWhatRasterizedLinkState_: function() {
1151       var tasks = this.getWhatRasterized_();
1152       if (tasks.length) {
1153         this.whatRasterizedLink_.textContent = tasks.length + ' raster tasks';
1154         this.whatRasterizedLink_.style.display = '';
1155       } else {
1156         this.whatRasterizedLink_.textContent = '';
1157         this.whatRasterizedLink_.style.display = 'none';
1158       }
1159     },
1160
1161     onWhatRasterizedLinkClicked_: function() {
1162       var tasks = this.getWhatRasterized_();
1163       var event = new tracing.RequestSelectionChangeEvent();
1164       event.selection = new tracing.Selection(tasks);
1165       this.dispatchEvent(event);
1166     }
1167   };
1168
1169   return {
1170     LayerTreeQuadStackView: LayerTreeQuadStackView
1171   };
1172 });
1173 </script>