Bug 1921551 - React to sync sign in flow correctly r=android-reviewers,matt-tighe
[gecko.git] / gfx / layers / layerviewer / layerTreeView.js
blob9e382ecdd9655db5d1d164f1f4cc1651849539af
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 function toFixed(num, fixed) {
6   fixed = fixed || 0;
7   fixed = Math.pow(10, fixed);
8   return Math.floor(num * fixed) / fixed;
10 function createElement(name, props) {
11   var el = document.createElement(name);
13   for (var key in props) {
14     if (key === "style") {
15       for (var styleName in props.style) {
16         el.style[styleName] = props.style[styleName];
17       }
18     } else {
19       el[key] = props[key];
20     }
21   }
23   return el;
26 function parseDisplayList(lines) {
27   var root = {
28     line: "DisplayListRoot 0",
29     name: "DisplayListRoot",
30     address: "0x0",
31     frame: "Root",
32     children: [],
33   };
35   var objectAtIndentation = {
36     "-1": root,
37   };
39   for (var i = 0; i < lines.length; i++) {
40     var line = lines[i];
42     var layerObject = {
43       line,
44       children: [],
45     };
46     if (!root) {
47       root = layerObject;
48     }
50     var matches = line.match(
51       "(\\s*)(\\w+)\\sp=(\\w+)\\sf=(.*?)\\((.*?)\\)\\s(z=(\\w+)\\s)?(.*?)?( layer=(\\w+))?$"
52     );
53     if (!matches) {
54       dump("Failed to match: " + line + "\n");
55       continue;
56     }
58     var indentation = Math.floor(matches[1].length / 2);
59     objectAtIndentation[indentation] = layerObject;
60     var parent = objectAtIndentation[indentation - 1];
61     if (parent) {
62       parent.children.push(layerObject);
63     }
65     layerObject.name = matches[2];
66     layerObject.address = matches[3]; // Use 0x prefix to be consistent with layer dump
67     layerObject.frame = matches[4];
68     layerObject.contentDescriptor = matches[5];
69     layerObject.z = matches[7];
70     var rest = matches[8];
71     if (matches[10]) {
72       // WrapList don't provide a layer
73       layerObject.layer = matches[10];
74     }
75     layerObject.rest = rest;
77     // the content node name doesn't have a prefix, this makes the parsing easier
78     rest = "content" + rest;
80     var nesting = 0;
81     var startIndex;
82     var lastSpace = -1;
83     for (var j = 0; j < rest.length; j++) {
84       if (rest.charAt(j) == "(") {
85         nesting++;
86         if (nesting == 1) {
87           startIndex = j;
88         }
89       } else if (rest.charAt(j) == ")") {
90         nesting--;
91         if (nesting == 0) {
92           var name = rest.substring(lastSpace + 1, startIndex);
93           var value = rest.substring(startIndex + 1, j);
95           var rectMatches = value.match("^(.*?),(.*?),(.*?),(.*?)$");
96           if (rectMatches) {
97             layerObject[name] = [
98               parseFloat(rectMatches[1]),
99               parseFloat(rectMatches[2]),
100               parseFloat(rectMatches[3]),
101               parseFloat(rectMatches[4]),
102             ];
103           } else {
104             layerObject[name] = value;
105           }
106         }
107       } else if (nesting == 0 && rest.charAt(j) == " ") {
108         lastSpace = j;
109       }
110     }
111     // dump("FIELDS: " + JSON.stringify(fields) + "\n");
112   }
113   return root;
116 function trim(s) {
117   return (s || "").replace(/^\s+|\s+$/g, "");
120 function getDataURI(str) {
121   if (str.indexOf("data:image/png;base64,") == 0) {
122     return str;
123   }
125   var matches = str.match(
126     "data:image/lz4bgra;base64,([0-9]+),([0-9]+),([0-9]+),(.*)"
127   );
128   if (!matches) {
129     return null;
130   }
132   var canvas = document.createElement("canvas");
133   var w = parseInt(matches[1]);
134   var stride = parseInt(matches[2]);
135   var h = parseInt(matches[3]);
136   canvas.width = w;
137   canvas.height = h;
139   // TODO handle stride
141   var binary_string = window.atob(matches[4]);
142   var len = binary_string.length;
143   var bytes = new Uint8Array(len);
144   var decoded = new Uint8Array(stride * h);
145   for (var i = 0; i < len; i++) {
146     var ascii = binary_string.charCodeAt(i);
147     bytes[i] = ascii;
148   }
150   var ctxt = canvas.getContext("2d");
151   var out = ctxt.createImageData(w, h);
152   // This is actually undefined throughout the tree and it isn't clear what it
153   // should be. Since this is only development code, leave it alone for now.
154   // eslint-disable-next-line no-undef
155   LZ4_uncompressChunk(bytes, decoded);
157   for (var x = 0; x < w; x++) {
158     for (var y = 0; y < h; y++) {
159       out.data[4 * x + 4 * y * w + 0] = decoded[4 * x + y * stride + 2];
160       out.data[4 * x + 4 * y * w + 1] = decoded[4 * x + y * stride + 1];
161       out.data[4 * x + 4 * y * w + 2] = decoded[4 * x + y * stride + 0];
162       out.data[4 * x + 4 * y * w + 3] = decoded[4 * x + y * stride + 3];
163     }
164   }
166   ctxt.putImageData(out, 0, 0);
167   return canvas.toDataURL();
170 function parseLayers(layersDumpLines) {
171   function parseMatrix2x3(str) {
172     str = trim(str);
174     // Something like '[ 1 0; 0 1; 0 158; ]'
175     var matches = str.match("^\\[ (.*?) (.*?); (.*?) (.*?); (.*?) (.*?); \\]$");
176     if (!matches) {
177       return null;
178     }
180     var matrix = [
181       [parseFloat(matches[1]), parseFloat(matches[2])],
182       [parseFloat(matches[3]), parseFloat(matches[4])],
183       [parseFloat(matches[5]), parseFloat(matches[6])],
184     ];
186     return matrix;
187   }
188   function parseColor(str) {
189     str = trim(str);
191     // Something like 'rgba(0, 0, 0, 0)'
192     var colorMatches = str.match("^rgba\\((.*), (.*), (.*), (.*)\\)$");
193     if (!colorMatches) {
194       return null;
195     }
197     var color = {
198       r: colorMatches[1],
199       g: colorMatches[2],
200       b: colorMatches[3],
201       a: colorMatches[4],
202     };
203     return color;
204   }
205   function parseFloat_cleo(str) {
206     str = trim(str);
208     // Something like 2.000
209     if (parseFloat(str) == str) {
210       return parseFloat(str);
211     }
213     return null;
214   }
215   function parseRect2D(str) {
216     str = trim(str);
218     // Something like '(x=0, y=0, w=2842, h=158)'
219     var rectMatches = str.match("^\\(x=(.*?), y=(.*?), w=(.*?), h=(.*?)\\)$");
220     if (!rectMatches) {
221       return null;
222     }
224     var rect = [
225       parseFloat(rectMatches[1]),
226       parseFloat(rectMatches[2]),
227       parseFloat(rectMatches[3]),
228       parseFloat(rectMatches[4]),
229     ];
230     return rect;
231   }
232   function parseRegion(str) {
233     str = trim(str);
235     // Something like '< (x=0, y=0, w=2842, h=158); (x=0, y=1718, w=2842, h=500); >'
236     if (str.charAt(0) != "<" || str.charAt(str.length - 1) != ">") {
237       return null;
238     }
240     var region = [];
241     str = trim(str.substring(1, str.length - 1));
242     while (str != "") {
243       var rectMatches = str.match(
244         "^\\(x=(.*?), y=(.*?), w=(.*?), h=(.*?)\\);(.*)$"
245       );
246       if (!rectMatches) {
247         return null;
248       }
250       var rect = [
251         parseFloat(rectMatches[1]),
252         parseFloat(rectMatches[2]),
253         parseFloat(rectMatches[3]),
254         parseFloat(rectMatches[4]),
255       ];
256       str = trim(rectMatches[5]);
257       region.push(rect);
258     }
259     return region;
260   }
262   var LAYERS_LINE_REGEX = "(\\s*)(\\w+)\\s\\((\\w+)\\)(.*)";
264   var root;
265   var objectAtIndentation = [];
266   for (var i = 0; i < layersDumpLines.length; i++) {
267     // Something like 'ThebesLayerComposite (0x12104cc00) [shadow-visible=< (x=0, y=0, w=1920, h=158); >] [visible=< (x=0, y=0, w=1920, h=158); >] [opaqueContent] [valid=< (x=0, y=0, w=1920, h=2218); >]'
268     var line = layersDumpLines[i].name || layersDumpLines[i];
270     var tileMatches = line.match("(\\s*)Tile \\(x=(.*), y=(.*)\\): (.*)");
271     if (tileMatches) {
272       let indentation = Math.floor(matches[1].length / 2);
273       var x = tileMatches[2];
274       var y = tileMatches[3];
275       var dataUri = tileMatches[4];
276       let parent = objectAtIndentation[indentation - 1];
277       var tiles = parent.tiles || {};
279       tiles[x] = tiles[x] || {};
280       tiles[x][y] = dataUri;
282       parent.tiles = tiles;
284       continue;
285     }
287     var surfaceMatches = line.match("(\\s*)Surface: (.*)");
288     if (surfaceMatches) {
289       let indentation = Math.floor(matches[1].length / 2);
290       let parent =
291         objectAtIndentation[indentation - 1] ||
292         objectAtIndentation[indentation - 2];
294       var surfaceURI = surfaceMatches[2];
295       if (parent.surfaceURI != null) {
296         console.log(
297           "error: surfaceURI already set for this layer " + parent.line
298         );
299       }
300       parent.surfaceURI = surfaceURI;
302       // Look for the buffer-rect offset
303       var contentHostLine =
304         layersDumpLines[i - 2].name || layersDumpLines[i - 2];
305       let matches = contentHostLine.match(LAYERS_LINE_REGEX);
306       if (matches) {
307         var contentHostRest = matches[4];
308         parent.contentHostProp = {};
309         parseProperties(contentHostRest, parent.contentHostProp);
310       }
312       continue;
313     }
315     var layerObject = {
316       line,
317       children: [],
318     };
319     if (!root) {
320       root = layerObject;
321     }
323     let matches = line.match(LAYERS_LINE_REGEX);
324     if (!matches) {
325       continue; // Something like a texturehost dump. Safe to ignore
326     }
328     if (
329       matches[2].includes("TiledContentHost") ||
330       matches[2].includes("ContentHost") ||
331       matches[2].includes("ContentClient") ||
332       matches[2].includes("MemoryTextureHost") ||
333       matches[2].includes("ImageHost")
334     ) {
335       continue; // We're already pretty good at visualizing these
336     }
338     var indentation = Math.floor(matches[1].length / 2);
339     objectAtIndentation[indentation] = layerObject;
340     for (var c = indentation + 1; c < objectAtIndentation.length; c++) {
341       objectAtIndentation[c] = null;
342     }
343     if (indentation > 0) {
344       var parent = objectAtIndentation[indentation - 1];
345       while (!parent) {
346         indentation--;
347         parent = objectAtIndentation[indentation - 1];
348       }
350       parent.children.push(layerObject);
351     }
353     layerObject.name = matches[2];
354     layerObject.address = matches[3];
356     var rest = matches[4];
358     function parseProperties(rest, layerObject) {
359       var fields = [];
360       var nesting = 0;
361       var startIndex;
362       for (let j = 0; j < rest.length; j++) {
363         if (rest.charAt(j) == "[") {
364           nesting++;
365           if (nesting == 1) {
366             startIndex = j;
367           }
368         } else if (rest.charAt(j) == "]") {
369           nesting--;
370           if (nesting == 0) {
371             fields.push(rest.substring(startIndex + 1, j));
372           }
373         }
374       }
376       for (let j = 0; j < fields.length; j++) {
377         // Something like 'valid=< (x=0, y=0, w=1920, h=2218); >' or 'opaqueContent'
378         var field = fields[j];
379         // dump("FIELD: " + field + "\n");
380         var parts = field.split("=", 2);
381         var fieldName = parts[0];
382         rest = field.substring(fieldName.length + 1);
383         if (parts.length == 1) {
384           layerObject[fieldName] = "true";
385           layerObject[fieldName].type = "bool";
386           continue;
387         }
388         var float = parseFloat_cleo(rest);
389         if (float) {
390           layerObject[fieldName] = float;
391           layerObject[fieldName].type = "float";
392           continue;
393         }
394         var region = parseRegion(rest);
395         if (region) {
396           layerObject[fieldName] = region;
397           layerObject[fieldName].type = "region";
398           continue;
399         }
400         var rect = parseRect2D(rest);
401         if (rect) {
402           layerObject[fieldName] = rect;
403           layerObject[fieldName].type = "rect2d";
404           continue;
405         }
406         var matrix = parseMatrix2x3(rest);
407         if (matrix) {
408           layerObject[fieldName] = matrix;
409           layerObject[fieldName].type = "matrix2x3";
410           continue;
411         }
412         var color = parseColor(rest);
413         if (color) {
414           layerObject[fieldName] = color;
415           layerObject[fieldName].type = "color";
416           continue;
417         }
418         if (rest[0] == "{" && rest[rest.length - 1] == "}") {
419           var object = {};
420           parseProperties(rest.substring(1, rest.length - 2).trim(), object);
421           layerObject[fieldName] = object;
422           layerObject[fieldName].type = "object";
423           continue;
424         }
425         fieldName = fieldName.split(" ")[0];
426         layerObject[fieldName] = rest[0];
427         layerObject[fieldName].type = "string";
428       }
429     }
430     parseProperties(rest, layerObject);
432     if (!layerObject["shadow-transform"]) {
433       // No shadow transform = identify
434       layerObject["shadow-transform"] = [
435         [1, 0],
436         [0, 1],
437         [0, 0],
438       ];
439     }
441     // Compute screenTransformX/screenTransformY
442     // TODO Fully support transforms
443     if (layerObject["shadow-transform"] && layerObject.transform) {
444       layerObject["screen-transform"] = [
445         layerObject["shadow-transform"][2][0],
446         layerObject["shadow-transform"][2][1],
447       ];
448       var currIndentation = indentation - 1;
449       while (currIndentation >= 0) {
450         var transform =
451           objectAtIndentation[currIndentation]["shadow-transform"] ||
452           objectAtIndentation[currIndentation].transform;
453         if (transform) {
454           layerObject["screen-transform"][0] += transform[2][0];
455           layerObject["screen-transform"][1] += transform[2][1];
456         }
457         currIndentation--;
458       }
459     }
461     // dump("Fields: " + JSON.stringify(fields) + "\n");
462   }
463   root.compositeTime = layersDumpLines.compositeTime;
464   // dump("OBJECTS: " + JSON.stringify(root) + "\n");
465   return root;
467 function populateLayers(
468   root,
469   displayList,
470   pane,
471   previewParent,
472   hasSeenRoot,
473   contentScale,
474   rootPreviewParent
475 ) {
476   contentScale = contentScale || 1;
477   rootPreviewParent = rootPreviewParent || previewParent;
479   function getDisplayItemForLayer(displayList) {
480     var items = [];
481     if (!displayList) {
482       return items;
483     }
484     if (displayList.layer == root.address) {
485       items.push(displayList);
486     }
487     for (var i = 0; i < displayList.children.length; i++) {
488       var subDisplayItems = getDisplayItemForLayer(displayList.children[i]);
489       for (let j = 0; j < subDisplayItems.length; j++) {
490         items.push(subDisplayItems[j]);
491       }
492     }
493     return items;
494   }
495   var elem = createElement("div", {
496     className: "layerObjectDescription",
497     textContent: root.line,
498     style: {
499       whiteSpace: "pre",
500     },
501     onmouseover() {
502       if (this.layerViewport) {
503         this.layerViewport.classList.add("layerHover");
504       }
505     },
506     onmouseout() {
507       if (this.layerViewport) {
508         this.layerViewport.classList.remove("layerHover");
509       }
510     },
511   });
512   var icon = createElement("img", {
513     src: "show.png",
514     style: {
515       width: "12px",
516       height: "12px",
517       marginLeft: "4px",
518       marginRight: "4px",
519       cursor: "pointer",
520     },
521     onclick() {
522       if (this.layerViewport) {
523         if (this.layerViewport.style.visibility == "hidden") {
524           this.layerViewport.style.visibility = "";
525           this.src = "show.png";
526         } else {
527           this.layerViewport.style.visibility = "hidden";
528           this.src = "hide.png";
529         }
530       }
531     },
532   });
533   elem.insertBefore(icon, elem.firstChild);
534   pane.appendChild(elem);
536   if (root["shadow-visible"] || root.visible) {
537     var visibleRegion = root["shadow-visible"] || root.visible;
538     var layerViewport = createElement("div", {
539       id: root.address + "_viewport",
540       style: {
541         position: "absolute",
542         pointerEvents: "none",
543       },
544     });
545     elem.layerViewport = layerViewport;
546     icon.layerViewport = layerViewport;
547     var layerViewportMatrix = [1, 0, 0, 1, 0, 0];
548     if (root["shadow-clip"] || root.clip) {
549       var clip = root["shadow-clip"] || root.clip;
550       var clipElem = createElement("div", {
551         id: root.address + "_clip",
552         style: {
553           left: clip[0] + "px",
554           top: clip[1] + "px",
555           width: clip[2] + "px",
556           height: clip[3] + "px",
557           position: "absolute",
558           overflow: "hidden",
559           pointerEvents: "none",
560         },
561       });
562       layerViewportMatrix[4] += -clip[0];
563       layerViewportMatrix[5] += -clip[1];
564       layerViewport.style.transform =
565         "translate(-" + clip[0] + "px, -" + clip[1] + "px)";
566     }
567     if (root["shadow-transform"] || root.transform) {
568       var matrix = root["shadow-transform"] || root.transform;
569       layerViewportMatrix[0] = matrix[0][0];
570       layerViewportMatrix[1] = matrix[0][1];
571       layerViewportMatrix[2] = matrix[1][0];
572       layerViewportMatrix[3] = matrix[1][1];
573       layerViewportMatrix[4] += matrix[2][0];
574       layerViewportMatrix[5] += matrix[2][1];
575     }
576     layerViewport.style.transform =
577       "matrix(" +
578       layerViewportMatrix[0] +
579       "," +
580       layerViewportMatrix[1] +
581       "," +
582       layerViewportMatrix[2] +
583       "," +
584       layerViewportMatrix[3] +
585       "," +
586       layerViewportMatrix[4] +
587       "," +
588       layerViewportMatrix[5] +
589       ")";
590     if (!hasSeenRoot) {
591       hasSeenRoot = true;
592       layerViewport.style.transform =
593         "scale(" + 1 / contentScale + "," + 1 / contentScale + ")";
594     }
595     if (clipElem) {
596       previewParent.appendChild(clipElem);
597       clipElem.appendChild(layerViewport);
598     } else {
599       previewParent.appendChild(layerViewport);
600     }
601     previewParent = layerViewport;
602     for (let i = 0; i < visibleRegion.length; i++) {
603       let rect2d = visibleRegion[i];
604       var layerPreview = createElement("div", {
605         id: root.address + "_visible_part" + i + "-" + visibleRegion.length,
606         className: "layerPreview",
607         style: {
608           position: "absolute",
609           left: rect2d[0] + "px",
610           top: rect2d[1] + "px",
611           width: rect2d[2] + "px",
612           height: rect2d[3] + "px",
613           overflow: "hidden",
614           border: "solid 1px black",
615           background:
616             'url("noise.png"), linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.2))',
617         },
618       });
619       layerViewport.appendChild(layerPreview);
621       function isInside(rect1, rect2) {
622         if (
623           rect1[0] + rect1[2] < rect2[0] &&
624           rect2[0] + rect2[2] < rect1[0] &&
625           rect1[1] + rect1[3] < rect2[1] &&
626           rect2[1] + rect2[3] < rect1[1]
627         ) {
628           return true;
629         }
630         return true;
631       }
633       var hasImg = false;
634       // Add tile img objects for this part
635       var previewOffset = rect2d;
637       if (root.tiles) {
638         hasImg = true;
639         for (var x in root.tiles) {
640           for (var y in root.tiles[x]) {
641             if (isInside(rect2d, [x, y, 512, 512])) {
642               var tileImgElem = createElement("img", {
643                 src: getDataURI(root.tiles[x][y]),
644                 style: {
645                   position: "absolute",
646                   left: x - previewOffset[0] + "px",
647                   top: y - previewOffset[1] + "px",
648                   pointerEvents: "auto",
649                 },
650               });
651               layerPreview.appendChild(tileImgElem);
652             }
653           }
654         }
655         layerPreview.style.background = "";
656       } else if (root.surfaceURI) {
657         hasImg = true;
658         var offsetX = 0;
659         var offsetY = 0;
660         if (root.contentHostProp && root.contentHostProp["buffer-rect"]) {
661           offsetX = root.contentHostProp["buffer-rect"][0];
662           offsetY = root.contentHostProp["buffer-rect"][1];
663         }
664         var surfaceImgElem = createElement("img", {
665           src: getDataURI(root.surfaceURI),
666           style: {
667             position: "absolute",
668             left: offsetX - previewOffset[0] + "px",
669             top: offsetY - previewOffset[1] + "px",
670             pointerEvents: "auto",
671           },
672         });
673         layerPreview.appendChild(surfaceImgElem);
674         layerPreview.style.background = "";
675       } else if (root.color) {
676         hasImg = true;
677         layerPreview.style.background =
678           "rgba(" +
679           root.color.r +
680           ", " +
681           root.color.g +
682           ", " +
683           root.color.b +
684           ", " +
685           root.color.a +
686           ")";
687       }
689       if (hasImg || true) {
690         layerPreview.mouseoverElem = elem;
691         layerPreview.onmouseenter = function () {
692           this.mouseoverElem.onmouseover();
693         };
694         layerPreview.onmouseout = function () {
695           this.mouseoverElem.onmouseout();
696         };
697       }
698     }
700     var layerDisplayItems = getDisplayItemForLayer(displayList);
701     for (let i = 0; i < layerDisplayItems.length; i++) {
702       var displayItem = layerDisplayItems[i];
703       var displayElem = createElement("div", {
704         className: "layerObjectDescription",
705         textContent: "            " + trim(displayItem.line),
706         style: {
707           whiteSpace: "pre",
708         },
709         displayItem,
710         layerViewport,
711         onmouseover() {
712           if (this.diPreview) {
713             this.diPreview.classList.add("displayHover");
715             var description = "";
716             if (this.displayItem.contentDescriptor) {
717               description += "Content: " + this.displayItem.contentDescriptor;
718             } else {
719               description += "Content: Unknown";
720             }
721             description +=
722               "<br>Item: " +
723               this.displayItem.name +
724               " (" +
725               this.displayItem.address +
726               ")";
727             description +=
728               "<br>Layer: " + root.name + " (" + root.address + ")";
729             if (this.displayItem.frame) {
730               description += "<br>Frame: " + this.displayItem.frame;
731             }
732             if (this.displayItem.layerBounds) {
733               description +=
734                 "<br>Bounds: [" +
735                 toFixed(this.displayItem.layerBounds[0] / 60, 2) +
736                 ", " +
737                 toFixed(this.displayItem.layerBounds[1] / 60, 2) +
738                 ", " +
739                 toFixed(this.displayItem.layerBounds[2] / 60, 2) +
740                 ", " +
741                 toFixed(this.displayItem.layerBounds[3] / 60, 2) +
742                 "] (CSS Pixels)";
743             }
744             if (this.displayItem.z) {
745               description += "<br>Z: " + this.displayItem.z;
746             }
747             // At the end
748             if (this.displayItem.rest) {
749               description += "<br>" + this.displayItem.rest;
750             }
752             var box = this.diPreview.getBoundingClientRect();
753             this.diPreview.tooltip = createElement("div", {
754               className: "csstooltip",
755               innerHTML: description,
756               style: {
757                 top:
758                   Math.min(
759                     box.bottom,
760                     document.documentElement.clientHeight - 150
761                   ) + "px",
762                 left: box.left + "px",
763               },
764             });
766             document.body.appendChild(this.diPreview.tooltip);
767           }
768         },
769         onmouseout() {
770           if (this.diPreview) {
771             this.diPreview.classList.remove("displayHover");
772             document.body.removeChild(this.diPreview.tooltip);
773           }
774         },
775       });
777       icon = createElement("img", {
778         style: {
779           width: "12px",
780           height: "12px",
781           marginLeft: "4px",
782           marginRight: "4px",
783         },
784       });
785       displayElem.insertBefore(icon, displayElem.firstChild);
786       pane.appendChild(displayElem);
787       // bounds doesn't adjust for within the layer. It's not a bad fallback but
788       // will have the wrong offset
789       let rect2d = displayItem.layerBounds || displayItem.bounds;
790       if (rect2d) {
791         // This doesn't place them corectly
792         var appUnitsToPixels = 60 / contentScale;
793         let diPreview = createElement("div", {
794           id: "displayitem_" + displayItem.content + "_" + displayItem.address,
795           className: "layerPreview",
796           style: {
797             position: "absolute",
798             left: rect2d[0] / appUnitsToPixels + "px",
799             top: rect2d[1] / appUnitsToPixels + "px",
800             width: rect2d[2] / appUnitsToPixels + "px",
801             height: rect2d[3] / appUnitsToPixels + "px",
802             border: "solid 1px gray",
803             pointerEvents: "auto",
804           },
805           displayElem,
806           onmouseover() {
807             this.displayElem.onmouseover();
808           },
809           onmouseout() {
810             this.displayElem.onmouseout();
811           },
812         });
814         layerViewport.appendChild(diPreview);
815         displayElem.diPreview = diPreview;
816       }
817     }
818   }
820   for (var i = 0; i < root.children.length; i++) {
821     populateLayers(
822       root.children[i],
823       displayList,
824       pane,
825       previewParent,
826       hasSeenRoot,
827       contentScale,
828       rootPreviewParent
829     );
830   }
833 // This function takes a stdout snippet and finds the frames
834 function parseMultiLineDump(log) {
835   var container = createElement("div", {
836     style: {
837       height: "100%",
838       position: "relative",
839     },
840   });
842   var layerManagerFirstLine = "[a-zA-Z]*LayerManager \\(.*$\n";
843   var nextLineStartWithSpace = "([ \\t].*$\n)*";
844   var layersRegex = "(" + layerManagerFirstLine + nextLineStartWithSpace + ")";
846   var startLine = "Painting --- after optimization:\n";
847   var endLine = "Painting --- layer tree:";
848   var displayListRegex = "(" + startLine + "(.*\n)*?" + endLine + ")";
850   var regex = new RegExp(layersRegex + "|" + displayListRegex, "gm");
851   var matches = log.match(regex);
852   console.log(matches);
853   window.matches = matches;
855   var matchList = createElement("span", {
856     style: {
857       height: "95%",
858       width: "10%",
859       position: "relative",
860       border: "solid black 2px",
861       display: "inline-block",
862       float: "left",
863       overflow: "auto",
864     },
865   });
866   container.appendChild(matchList);
867   var contents = createElement("span", {
868     style: {
869       height: "95%",
870       width: "88%",
871       display: "inline-block",
872     },
873     textContent: "Click on a frame on the left to view the layer tree",
874   });
875   container.appendChild(contents);
877   var lastDisplayList = null;
878   var frameID = 1;
879   for (let i = 0; i < matches.length; i++) {
880     var currMatch = matches[i];
882     if (currMatch.indexOf(startLine) == 0) {
883       // Display list match
884       var matchLines = matches[i].split("\n");
885       lastDisplayList = parseDisplayList(matchLines);
886     } else {
887       // Layer tree match:
888       let displayList = lastDisplayList;
889       lastDisplayList = null;
890       var currFrameDiv = createElement("a", {
891         style: {
892           padding: "3px",
893           display: "block",
894         },
895         href: "#",
896         textContent: "LayerTree " + frameID++,
897         onclick() {
898           contents.innerHTML = "";
899           var matchLines = matches[i].split("\n");
900           var dumpDiv = parseDump(matchLines, displayList);
901           contents.appendChild(dumpDiv);
902         },
903       });
904       matchList.appendChild(currFrameDiv);
905     }
906   }
908   return container;
911 function parseDump(log, displayList, compositeTitle, compositeTime) {
912   compositeTitle |= "";
913   compositeTime |= 0;
915   var container = createElement("div", {
916     style: {
917       background: "white",
918       height: "100%",
919       position: "relative",
920     },
921   });
923   if (compositeTitle == null && compositeTime == null) {
924     var titleDiv = createElement("div", {
925       className: "treeColumnHeader",
926       style: {
927         width: "100%",
928       },
929       textContent:
930         compositeTitle +
931         (compositeTitle ? " (near " + compositeTime.toFixed(0) + " ms)" : ""),
932     });
933     container.appendChild(titleDiv);
934   }
936   var mainDiv = createElement("div", {
937     style: {
938       position: "absolute",
939       top: "16px",
940       left: "0px",
941       right: "0px",
942       bottom: "0px",
943     },
944   });
945   container.appendChild(mainDiv);
947   var layerListPane = createElement("div", {
948     style: {
949       cssFloat: "left",
950       height: "100%",
951       width: "300px",
952       overflowY: "scroll",
953     },
954   });
955   mainDiv.appendChild(layerListPane);
957   var previewDiv = createElement("div", {
958     style: {
959       position: "absolute",
960       left: "300px",
961       right: "0px",
962       top: "0px",
963       bottom: "0px",
964       overflow: "auto",
965     },
966   });
967   mainDiv.appendChild(previewDiv);
969   var root = parseLayers(log);
970   populateLayers(root, displayList, layerListPane, previewDiv);
971   return container;