layerTreeView.js (27938B)
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/. */ 4 5 function toFixed(num, fixed) { 6 fixed = fixed || 0; 7 fixed = Math.pow(10, fixed); 8 return Math.floor(num * fixed) / fixed; 9 } 10 function createElement(name, props) { 11 var el = document.createElement(name); 12 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 } 22 23 return el; 24 } 25 26 function parseDisplayList(lines) { 27 var root = { 28 line: "DisplayListRoot 0", 29 name: "DisplayListRoot", 30 address: "0x0", 31 frame: "Root", 32 children: [], 33 }; 34 35 var objectAtIndentation = { 36 "-1": root, 37 }; 38 39 for (var i = 0; i < lines.length; i++) { 40 var line = lines[i]; 41 42 var layerObject = { 43 line, 44 children: [], 45 }; 46 if (!root) { 47 root = layerObject; 48 } 49 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 } 57 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 } 64 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; 76 77 // the content node name doesn't have a prefix, this makes the parsing easier 78 rest = "content" + rest; 79 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); 94 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; 114 } 115 116 function trim(s) { 117 return (s || "").replace(/^\s+|\s+$/g, ""); 118 } 119 120 function getDataURI(str) { 121 if (str.indexOf("data:image/png;base64,") == 0) { 122 return str; 123 } 124 125 var matches = str.match( 126 "data:image/lz4bgra;base64,([0-9]+),([0-9]+),([0-9]+),(.*)" 127 ); 128 if (!matches) { 129 return null; 130 } 131 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; 138 139 // TODO handle stride 140 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 } 149 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); 156 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 } 165 166 ctxt.putImageData(out, 0, 0); 167 return canvas.toDataURL(); 168 } 169 170 function parseLayers(layersDumpLines) { 171 function parseMatrix2x3(str) { 172 str = trim(str); 173 174 // Something like '[ 1 0; 0 1; 0 158; ]' 175 var matches = str.match("^\\[ (.*?) (.*?); (.*?) (.*?); (.*?) (.*?); \\]$"); 176 if (!matches) { 177 return null; 178 } 179 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 ]; 185 186 return matrix; 187 } 188 function parseColor(str) { 189 str = trim(str); 190 191 // Something like 'rgba(0, 0, 0, 0)' 192 var colorMatches = str.match("^rgba\\((.*), (.*), (.*), (.*)\\)$"); 193 if (!colorMatches) { 194 return null; 195 } 196 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); 207 208 // Something like 2.000 209 if (parseFloat(str) == str) { 210 return parseFloat(str); 211 } 212 213 return null; 214 } 215 function parseRect2D(str) { 216 str = trim(str); 217 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 } 223 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); 234 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 } 239 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 } 249 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 } 261 262 var LAYERS_LINE_REGEX = "(\\s*)(\\w+)\\s\\((\\w+)\\)(.*)"; 263 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]; 269 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 || {}; 278 279 tiles[x] = tiles[x] || {}; 280 tiles[x][y] = dataUri; 281 282 parent.tiles = tiles; 283 284 continue; 285 } 286 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]; 293 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; 301 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 } 311 312 continue; 313 } 314 315 var layerObject = { 316 line, 317 children: [], 318 }; 319 if (!root) { 320 root = layerObject; 321 } 322 323 let matches = line.match(LAYERS_LINE_REGEX); 324 if (!matches) { 325 continue; // Something like a texturehost dump. Safe to ignore 326 } 327 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 } 337 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 } 349 350 parent.children.push(layerObject); 351 } 352 353 layerObject.name = matches[2]; 354 layerObject.address = matches[3]; 355 356 var rest = matches[4]; 357 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 } 375 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); 431 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 } 440 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 } 460 461 // dump("Fields: " + JSON.stringify(fields) + "\n"); 462 } 463 root.compositeTime = layersDumpLines.compositeTime; 464 // dump("OBJECTS: " + JSON.stringify(root) + "\n"); 465 return root; 466 } 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; 478 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); 535 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); 620 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 } 632 633 var hasImg = false; 634 // Add tile img objects for this part 635 var previewOffset = rect2d; 636 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 } 688 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 } 699 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"); 714 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 } 751 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 }); 765 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 }); 776 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 }); 813 814 layerViewport.appendChild(diPreview); 815 displayElem.diPreview = diPreview; 816 } 817 } 818 } 819 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 } 831 } 832 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 }); 841 842 var layerManagerFirstLine = "[a-zA-Z]*LayerManager \\(.*$\n"; 843 var nextLineStartWithSpace = "([ \\t].*$\n)*"; 844 var layersRegex = "(" + layerManagerFirstLine + nextLineStartWithSpace + ")"; 845 846 var startLine = "Painting --- after optimization:\n"; 847 var endLine = "Painting --- layer tree:"; 848 var displayListRegex = "(" + startLine + "(.*\n)*?" + endLine + ")"; 849 850 var regex = new RegExp(layersRegex + "|" + displayListRegex, "gm"); 851 var matches = log.match(regex); 852 console.log(matches); 853 window.matches = matches; 854 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); 876 877 var lastDisplayList = null; 878 var frameID = 1; 879 for (let i = 0; i < matches.length; i++) { 880 var currMatch = matches[i]; 881 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 } 907 908 return container; 909 } 910 911 function parseDump(log, displayList, compositeTitle, compositeTime) { 912 compositeTitle |= ""; 913 compositeTime |= 0; 914 915 var container = createElement("div", { 916 style: { 917 background: "white", 918 height: "100%", 919 position: "relative", 920 }, 921 }); 922 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 } 935 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); 946 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); 956 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); 968 969 var root = parseLayers(log); 970 populateLayers(root, displayList, layerListPane, previewDiv); 971 return container; 972 }