// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { assert } from 'chai';
import { describeWithEnvironment } from '../../../testing/EnvironmentHelpers.js';
import { getAllNodes, getEventsIn, getRootAt, makeBeginEvent, makeCompleteEvent, makeEndEvent, makeInstantEvent, prettyPrint, } from '../../../testing/TraceHelpers.js';
import { TraceLoader } from '../../../testing/TraceLoader.js';
import * as Trace from '../trace.js';
const MAIN_FRAME_PID = 2154214;
const SUB_FRAME_PID = 2236065;
const SUB_FRAME_PID_2 = 2236084;
const SUB_FRAME_PID_3 = 2236123;
async function handleEventsFromTraceFile(context, file) {
    const parsedTrace = await TraceLoader.traceEngine(context, file);
    return parsedTrace.data;
}
describeWithEnvironment('RendererHandler', function () {
    it('finds all the renderers in a real world profile', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        assert.strictEqual(renderers.processes.size, 4);
        const pids = [...renderers.processes].map(([pid]) => pid);
        assert.deepEqual(pids, [
            MAIN_FRAME_PID, // Main frame process: localhost:5000
            SUB_FRAME_PID, // Sub frame process (trace start): example.com
            SUB_FRAME_PID_2, // Sub frame process (after first navigation): example.com
            SUB_FRAME_PID_3, // Sub frame process (after second navigation): example.com
        ], 'Process IDs do not match expectations');
        const origins = [...renderers.processes].map(([, process]) => {
            return process.url ? new URL(process.url).origin : null;
        });
        assert.deepEqual(origins, [
            'http://localhost:5000', // Main frame process: localhost:5000
            'https://www.example.com', // Sub frame process (trace start): example.com
            'https://www.example.com', // Sub frame process (after first navigation): example.com
            'https://www.example.com', // Sub frame process (after second navigation): example.com
        ], 'Process origins do not meet expectations');
        // Assert on whether it has correctly detected a given process to be on the
        // main frame or in a subframe.
        const isOnMainFrame = [...renderers.processes].map(([, process]) => process.isOnMainFrame);
        assert.deepEqual(isOnMainFrame, [
            true, // Main frame process: localhost:5000
            false, // Sub frame process (trace start): example.com
            false, // Sub frame process (after first navigation): example.com
            false, // Sub frame process (after second navigation): example.com
        ], 'Processes are incorrectly assigned as being on the main frame');
    });
    it('finds all the main frame threads in a real world profile', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const frame = renderers.processes.get(Trace.Types.Events.ProcessID(MAIN_FRAME_PID));
        const names = [...frame.threads].map(([, thread]) => thread.name).sort();
        assert.deepEqual(names, [
            'Chrome_ChildIOThread',
            'Compositor',
            'CompositorTileWorker1',
            'CompositorTileWorker2',
            'CompositorTileWorker3',
            'CompositorTileWorker4',
            'CrRendererMain',
            'ThreadPoolForegroundWorker',
            'ThreadPoolForegroundWorker',
            'ThreadPoolForegroundWorker',
            'ThreadPoolForegroundWorker',
            'ThreadPoolForegroundWorker',
            'ThreadPoolForegroundWorker',
            'ThreadPoolForegroundWorker',
            'ThreadPoolForegroundWorker',
            'ThreadPoolServiceThread',
        ], 'Main frame thread names do not meet expectations before navigation');
    });
    it('finds all the sub frame threads in a real world profile', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const frame = renderers.processes.get(Trace.Types.Events.ProcessID(SUB_FRAME_PID));
        const names = [...frame.threads].map(([, thread]) => thread.name).sort();
        assert.deepEqual(names, [
            'Chrome_ChildIOThread',
            'Compositor',
            'CrRendererMain',
            'ThreadPoolServiceThread',
        ], 'Main frame thread names do not meet expectations after navigation');
    });
    it('finds all the roots on the main frame\'s main thread in a real world profile', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const frame = renderers.processes.get(Trace.Types.Events.ProcessID(MAIN_FRAME_PID));
        const thread = [...frame.threads.values()].find(thread => thread.name === 'CrRendererMain');
        if (!thread) {
            assert(false, 'Main thread was not found');
        }
        const tree = thread.tree;
        if (!tree) {
            assert(false, 'Main thread has no tree of events');
        }
        assert.deepEqual([...tree.roots].map(root => root.id), [
            0, 1, 2, 3, 4, 5, 16, 18, 29, 38, 49, 58, 77, 183, 184, 185, 186, 188, 189,
            190, 199, 200, 201, 202, 211, 212, 213, 214, 229, 230, 232, 237, 239, 240, 242, 251, 252, 261,
            264, 265, 266, 267, 268, 279, 282, 284, 285, 286, 287, 288, 289, 290, 293, 294, 295, 296, 297,
            298, 299, 300, 301, 302, 303, 304, 305, 306, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337,
            338, 339, 340, 341, 342, 343, 344, 345, 354, 355, 356, 359, 389, 408, 409, 410, 411, 412, 413,
            414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432,
            433, 441, 442, 443, 444, 445, 446, 447, 448, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464,
            465, 466, 467, 468, 469, 479, 480, 481, 482, 483, 484, 485, 492, 493, 494, 495, 496, 498, 506,
            507, 508, 509, 510, 511, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 538, 540, 541,
            552, 555, 556, 565, 566, 575, 576, 585, 586, 595, 596, 605, 606, 615, 616, 625, 626, 635, 636,
            645, 646, 657, 660, 661, 662, 663, 674, 677, 678, 679, 680, 689, 690, 691, 692, 701, 702, 711,
            712, 721, 722, 733, 734, 737, 738, 739, 740, 749, 750, 751, 760, 761, 762, 771, 772, 773, 782,
            783, 784, 793, 794, 795, 796, 797, 808, 809, 810, 811, 835, 843, 844, 845, 846, 848, 861, 869,
            870, 871, 872, 873, 874, 875, 876, 877, 878, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890,
            904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922,
            931, 932, 933, 936, 966, 967, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995,
            996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1020, 1021,
            1022, 1023, 1024, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055,
            1056, 1057, 1064, 1065, 1066, 1068, 1069, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088,
            1089, 1090, 1091, 1092, 1102, 1104, 1105, 1106, 1107, 1116, 1117, 1118, 1127, 1128, 1129, 1138, 1139, 1140, 1141,
            1150, 1151, 1152, 1153, 1154, 1165, 1166, 1167, 1176, 1177, 1178, 1189, 1192, 1193, 1194, 1203, 1204, 1205, 1206,
            1215, 1216, 1225, 1226, 1235, 1236, 1237, 1246, 1247, 1256, 1257, 1266, 1267, 1276, 1277, 1286, 1287, 1298, 1301,
            1302, 1303, 1304, 1313, 1314, 1315, 1324, 1325, 1326, 1335, 1336, 1337, 1348, 1351, 1352, 1353, 1362, 1364, 1365,
            1366, 1367, 1368, 1369, 1370, 1371, 1378,
        ]);
    });
    it('finds all the roots on the sub frame\'s main thread in a real world profile', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const frame = renderers.processes.get(Trace.Types.Events.ProcessID(SUB_FRAME_PID));
        const thread = [...frame.threads.values()].find(thread => thread.name === 'CrRendererMain');
        if (!thread) {
            assert(false, 'Main thread was not found');
        }
        const tree = thread.tree;
        if (!tree) {
            assert(false, 'Main thread has no tree of events');
        }
        assert.deepEqual([...tree.roots].map(root => root.id), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20]);
    });
    it('builds a hierarchy for the main frame\'s main thread in a real world profile', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const frame = renderers.processes.get(Trace.Types.Events.ProcessID(MAIN_FRAME_PID));
        const thread = [...frame.threads.values()].find(thread => thread.name === 'CrRendererMain');
        if (!thread) {
            assert(false, 'Main thread was not found');
        }
        const tree = thread.tree;
        if (!tree) {
            assert(false, 'Main thread has no tree of events');
        }
        const isRoot = (node) => node.depth === 0;
        const isInstant = (event) => Trace.Types.Events.isInstant(event);
        const isLong = (event) => Trace.Types.Events.isComplete(event) && event.dur > 1000;
        const isIncluded = (node, event) => (!isRoot(node) || isInstant(event) || isLong(event)) &&
            Boolean(Trace.Styles.getEventStyle(event.name));
        assert.strictEqual(prettyPrint(tree, isIncluded), `
............
-RunTask [2.21ms]
.
  -MajorGC [2.148ms]
...........................................................
-RunTask [15.436ms]
  -FrameStartedLoading [0ms]
  -EventDispatch (pagehide) [0.018ms]
  -EventDispatch (visibilitychange) [0.01ms]
  -EventDispatch (webkitvisibilitychange) [0.006ms]
.
  -EventDispatch (unload) [0.006ms]
.
  -ResourceSendRequest [0ms]
  -ResourceReceiveResponse [0ms]
...
  -ProfileCall (anonymous) [0.205ms]
    -ProfileCall (anonymous) [0.205ms]
.......................
-RunTask [3.402ms]
  -ParseHTML [2.593ms]
....
    -ParseHTML [0.064ms]
...
    -EventDispatch (readystatechange) [0.008ms]
.
    -EventDispatch (DOMContentLoaded) [0.004ms]
.
    -MarkDOMContent [0ms]
.
    -EventDispatch (readystatechange) [0.01ms]
    -EventDispatch (beforeunload) [0.013ms]
    -FrameStartedLoading [0ms]
.
  -ParseHTML [0.01ms]
..
  -EventDispatch (readystatechange) [0.008ms]
.
  -EventDispatch (DOMContentLoaded) [0.035ms]
.
  -UpdateLayoutTree [0.373ms]
    -InvalidateLayout [0ms]
  -MarkDOMContent [0ms]
-RunTask [2.675ms]
  -BeginMainThreadFrame [0ms]
  -Layout [0.854ms]
    -InvalidateLayout [0ms]
    -Layout [0.302ms]
      -UpdateLayoutTree [0.149ms]
.
  -UpdateLayerTree [0.338ms]
  -Paint [0.203ms]
..
  -firstPaint [0ms]
  -firstContentfulPaint [0ms]
.....
  -largestContentfulPaint::Candidate [0ms]
.................................
-RunTask [1.605ms]
  -EventDispatch (pagehide) [0.014ms]
  -EventDispatch (visibilitychange) [0.038ms]
  -EventDispatch (webkitvisibilitychange) [0.009ms]
  -EventDispatch (unload) [0.004ms]
.
  -ScheduleStyleRecalculation [0ms]
..............
-RunTask [1.231ms]
  -BeginMainThreadFrame [0ms]
  -UpdateLayoutTree [0.093ms]
.
  -UpdateLayerTree [0.186ms]
  -Paint [0.063ms]
  -Paint [0.084ms]
  -UpdateLayer [0.022ms]
  -UpdateLayer [0.006ms]
  -CompositeLayers [0.311ms]
............
-RunTask [1.663ms]
.
  -EventDispatch (readystatechange) [0.009ms]
.
  -EventDispatch (load) [0.014ms]
.
  -MarkLoad [0ms]
  -EventDispatch (pageshow) [0.007ms]
.......................................................................................
-RunTask [1.42ms]
.
  -UpdateLayerTree [0.023ms]
  -HitTest [0.057ms]
  -EventDispatch (mousemove) [0.018ms]
.
  -UpdateLayerTree [0.028ms]
  -HitTest [0.022ms]
.
  -UpdateLayerTree [0.01ms]
  -HitTest [0.002ms]
  -ScheduleStyleRecalculation [0ms]
  -EventDispatch (mousedown) [0.018ms]
  -UpdateLayoutTree [0.146ms]
.
  -UpdateLayerTree [0.031ms]
  -HitTest [0.016ms]
  -ScheduleStyleRecalculation [0ms]
  -UpdateLayoutTree [0.031ms]
  -EventDispatch (focus) [0.014ms]
  -EventDispatch (focusin) [0.005ms]
  -EventDispatch (DOMFocusIn) [0.005ms]
.
  -UpdateLayerTree [0.029ms]
.....
-RunTask [1.034ms]
.
  -UpdateLayerTree [0.021ms]
  -HitTest [0.038ms]
  -ScheduleStyleRecalculation [0ms]
  -EventDispatch (mouseup) [0.016ms]
  -EventDispatch (click) [0.44ms]
    -EventDispatch (beforeunload) [0.009ms]
    -FrameStartedLoading [0ms]
.
  -UpdateLayoutTree [0.137ms]
.
  -UpdateLayerTree [0.03ms]
....................
-RunTask [8.203ms]
  -EventDispatch (pagehide) [0.016ms]
  -EventDispatch (visibilitychange) [0.006ms]
  -EventDispatch (webkitvisibilitychange) [0.004ms]
  -EventDispatch (unload) [0.008ms]
..
  -ResourceSendRequest [0ms]
  -ResourceSendRequest [0ms]
  -ResourceReceiveResponse [0ms]
..........................
-RunTask [2.996ms]
  -ParseHTML [2.368ms]
....
    -ParseHTML [0.074ms]
...
    -EventDispatch (readystatechange) [0.01ms]
.
    -EventDispatch (DOMContentLoaded) [0.005ms]
.
    -MarkDOMContent [0ms]
.
    -EventDispatch (readystatechange) [0.008ms]
    -EventDispatch (beforeunload) [0.009ms]
    -FrameStartedLoading [0ms]
.
  -ParseHTML [0.009ms]
..
  -EventDispatch (readystatechange) [0.007ms]
.
  -EventDispatch (DOMContentLoaded) [0.005ms]
.
  -UpdateLayoutTree [0.301ms]
    -InvalidateLayout [0ms]
  -MarkDOMContent [0ms]
.
-RunTask [1.897ms]
  -BeginMainThreadFrame [0ms]
  -Layout [0.44ms]
    -InvalidateLayout [0ms]
.
  -UpdateLayerTree [0.247ms]
  -Paint [0.289ms]
..
  -firstPaint [0ms]
  -firstContentfulPaint [0ms]
..
  -largestContentfulPaint::Candidate [0ms]
....................................
-RunTask [1.304ms]
  -EventDispatch (pagehide) [0.016ms]
  -EventDispatch (visibilitychange) [0.009ms]
  -EventDispatch (webkitvisibilitychange) [0.004ms]
  -EventDispatch (unload) [0.015ms]
.
  -ScheduleStyleRecalculation [0ms]
......................................................................................................................`);
    });
    it('builds a hierarchy for the sub frame\'s main thread in a real world profile', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const frame = renderers.processes.get(Trace.Types.Events.ProcessID(SUB_FRAME_PID));
        const thread = [...frame.threads.values()].find(thread => thread.name === 'CrRendererMain');
        if (!thread) {
            assert(false, 'Main thread was not found');
        }
        const tree = thread.tree;
        if (!tree) {
            assert(false, 'Main thread has no tree of events');
        }
        const isIncluded = (_node, event) => Boolean(Trace.Styles.getEventStyle(event.name));
        assert.strictEqual(prettyPrint(tree, isIncluded), `
-RunTask [0.13ms]
-RunTask [0.005ms]
-RunTask [0.009ms]
-RunTask [0.065ms]
-RunTask [0.084ms]
-RunTask [0.041ms]
-RunTask [0.057ms]
-RunTask [0.021ms]
-RunTask [0.009ms]
-RunTask [0.065ms]
-RunTask [0.078ms]
-RunTask [0.043ms]
-RunTask [0.077ms]
  -ScheduleStyleRecalculation [0ms]
-RunTask [0.415ms]
-RunTask [0ms]
-EventDispatch (pagehide) [0.012ms]
-EventDispatch (visibilitychange) [0.007ms]
-EventDispatch (webkitvisibilitychange) [0.016ms]
-EventDispatch (unload) [0.007ms]
.`);
    });
    it('has some correct known roots for the main frame\'s main thread in a real world profile', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const frame = renderers.processes.get(Trace.Types.Events.ProcessID(MAIN_FRAME_PID));
        const thread = [...frame.threads.values()].find(thread => thread.name === 'CrRendererMain');
        if (!thread) {
            assert(false, 'Main thread was not found');
        }
        const tree = thread.tree;
        if (!tree) {
            assert(false, 'Main thread has no tree of events');
        }
        const event0 = getRootAt(thread, 1).entry;
        assert.deepEqual(event0, {
            args: {},
            cat: 'disabled-by-default-devtools.timeline',
            dur: 132,
            name: 'RunTask',
            ph: 'X',
            pid: 2154214,
            tdur: 131,
            tid: 1,
            ts: 643492822363,
            tts: 291450,
        });
        assert.strictEqual(renderers.entryToNode.get(event0)?.selfTime, 132);
        const event1 = getRootAt(thread, 2).entry;
        assert.deepEqual(event1, {
            args: {},
            cat: 'disabled-by-default-devtools.timeline',
            dur: 4,
            name: 'RunTask',
            ph: 'X',
            pid: 2154214,
            tdur: 4,
            tid: 1,
            ts: 643492822500,
            tts: 291586,
        });
        assert.strictEqual(renderers.entryToNode.get(event1)?.selfTime, 4);
        const eventLast = getRootAt(thread, tree.roots.size - 1).entry;
        assert.deepEqual(eventLast, {
            args: {},
            cat: 'disabled-by-default-devtools.timeline',
            dur: 67,
            name: 'RunTask',
            ph: 'X',
            pid: 2154214,
            tdur: 67,
            tid: 1,
            ts: 643499551460,
            tts: 949032,
        });
        assert.strictEqual(renderers.entryToNode.get(eventLast)?.selfTime, 35);
    });
    it('has some correct known roots for the sub frame\'s main thread in a real world profile', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const frame = renderers.processes.get(Trace.Types.Events.ProcessID(SUB_FRAME_PID));
        const thread = [...frame.threads.values()].find(thread => thread.name === 'CrRendererMain');
        if (!thread) {
            assert(false, 'Main thread was not found');
        }
        const tree = thread.tree;
        if (!tree) {
            assert(false, 'Main thread has no tree of events');
        }
        const event0 = getRootAt(thread, 0).entry;
        assert.deepEqual(event0, {
            args: {},
            cat: 'disabled-by-default-devtools.timeline',
            dur: 130,
            name: 'RunTask',
            ph: 'X',
            pid: 2236065,
            tdur: 129,
            tid: 1,
            ts: 643492822099,
            tts: 62157,
        });
        assert.strictEqual(renderers.entryToNode.get(event0)?.selfTime, 130);
        const event1 = getRootAt(thread, 1).entry;
        assert.deepEqual(event1, {
            args: {},
            cat: 'disabled-by-default-devtools.timeline',
            dur: 5,
            name: 'RunTask',
            ph: 'X',
            pid: 2236065,
            tdur: 5,
            tid: 1,
            ts: 643492822234,
            tts: 62291,
        });
        assert.strictEqual(renderers.entryToNode.get(event1)?.selfTime, 5);
        const event2 = getRootAt(thread, 2).entry;
        assert.deepEqual(event2, {
            args: {},
            cat: 'disabled-by-default-devtools.timeline',
            dur: 9,
            name: 'RunTask',
            ph: 'X',
            pid: 2236065,
            tdur: 9,
            tid: 1,
            ts: 643492822242,
            tts: 62299,
        });
        assert.strictEqual(renderers.entryToNode.get(event2)?.selfTime, 9);
    });
    it('can correctly sort a simple list of complete events', async () => {
        const data = [
            makeCompleteEvent('d0', 2, 1),
            makeCompleteEvent('b0', 1, 1),
            makeCompleteEvent('a0', 0, 1),
            makeCompleteEvent('a1', 0, 0.5),
            makeCompleteEvent('a2', 0.5, 0.5),
            makeCompleteEvent('c0', 1.5, 0.5),
            makeCompleteEvent('a4', 0.99, 0.01),
            makeCompleteEvent('b1', 1, 0.01),
            makeCompleteEvent('a3', 0.5, 0.25),
        ];
        Trace.Helpers.Trace.sortTraceEventsInPlace(data);
        assert.deepEqual(data.map(e => ({ name: e.name, ts: e.ts, dur: e.dur })), [
            { name: 'a0', ts: 0, dur: 1 },
            { name: 'a1', ts: 0, dur: 0.5 },
            { name: 'a2', ts: 0.5, dur: 0.5 },
            { name: 'a3', ts: 0.5, dur: 0.25 },
            { name: 'a4', ts: 0.99, dur: 0.01 },
            { name: 'b0', ts: 1, dur: 1 },
            { name: 'b1', ts: 1, dur: 0.01 },
            { name: 'c0', ts: 1.5, dur: 0.5 },
            { name: 'd0', ts: 2, dur: 1 },
        ]);
    });
    it('can correctly sort a simple list of complete events interspersed with instant events', async () => {
        const data = [
            makeCompleteEvent('d0', 2, 1),
            makeInstantEvent('i0', 0),
            makeCompleteEvent('b0', 1, 1),
            makeInstantEvent('i1', 0.01),
            makeCompleteEvent('a0', 0, 1),
            makeInstantEvent('i2', 0.5),
            makeCompleteEvent('a1', 0, 0.5),
            makeInstantEvent('i3', 0.99),
            makeCompleteEvent('a2', 0.5, 0.5),
            makeInstantEvent('i4', 1),
            makeCompleteEvent('c0', 1.5, 0.5),
            makeInstantEvent('i5', 1.75),
            makeCompleteEvent('a4', 0.99, 0.01),
            makeInstantEvent('i6', 1.99),
            makeCompleteEvent('b1', 1, 0.01),
            makeInstantEvent('i7', 2),
            makeCompleteEvent('a3', 0.5, 0.25),
            makeInstantEvent('i8', 2.01),
        ];
        Trace.Helpers.Trace.sortTraceEventsInPlace(data);
        assert.deepEqual(data.map(e => ({ name: e.name, ts: e.ts, dur: e.dur })), [
            { name: 'a0', ts: 0, dur: 1 },
            { name: 'a1', ts: 0, dur: 0.5 },
            { name: 'i0', ts: 0, dur: undefined },
            { name: 'i1', ts: 0.01, dur: undefined },
            { name: 'a2', ts: 0.5, dur: 0.5 },
            { name: 'a3', ts: 0.5, dur: 0.25 },
            { name: 'i2', ts: 0.5, dur: undefined },
            { name: 'a4', ts: 0.99, dur: 0.01 },
            { name: 'i3', ts: 0.99, dur: undefined },
            { name: 'b0', ts: 1, dur: 1 },
            { name: 'b1', ts: 1, dur: 0.01 },
            { name: 'i4', ts: 1, dur: undefined },
            { name: 'c0', ts: 1.5, dur: 0.5 },
            { name: 'i5', ts: 1.75, dur: undefined },
            { name: 'i6', ts: 1.99, dur: undefined },
            { name: 'd0', ts: 2, dur: 1 },
            { name: 'i7', ts: 2, dur: undefined },
            { name: 'i8', ts: 2.01, dur: undefined },
        ]);
    });
    it('can process multiple processes', async () => {
        /**
         * |------------- Task A -------------||-- Task E --|
         *  |-- Task B --||-- Task D --|
         *   |- Task C -|
         */
        const data1 = [
            makeCompleteEvent('A', 0, 10), // 0..10
            makeCompleteEvent('B', 1, 3), // 1..4
            makeCompleteEvent('D', 5, 3), // 5..8
            makeCompleteEvent('C', 2, 1), // 2..3
            makeCompleteEvent('E', 11, 3), // 11..14
        ];
        /**
         * |-- Task F --||------------- Task G -------------|
         *               |-- Task H --||-- Task J --|
         *                 |- Task I -|
         */
        const data2 = [
            makeCompleteEvent('F', 0, 3), // 0..3
            makeCompleteEvent('G', 3, 10), // 3..13 (starts when F finishes)
            makeCompleteEvent('H', 3, 3), // 3..6 (starts same time as G)
            makeCompleteEvent('J', 6, 3), // 6..9 (starts when H finishes)
            makeCompleteEvent('I', 5, 1), // 5..6 (finishes when H finishes)
        ];
        const processes = new Map([
            [
                Trace.Types.Events.ProcessID(0),
                {
                    url: ('http://a.com'),
                    isOnMainFrame: true,
                    threads: new Map([[
                            Trace.Types.Events.ThreadID(1),
                            { name: 'Foo', entries: data1 },
                        ]]),
                },
            ],
            [
                Trace.Types.Events.ProcessID(2),
                {
                    url: ('http://b.com'),
                    isOnMainFrame: false,
                    threads: new Map([[
                            Trace.Types.Events.ThreadID(3),
                            { name: 'Bar', entries: data2 },
                        ]]),
                },
            ],
        ]);
        await Trace.Handlers.ModelHandlers.Samples.finalize();
        Trace.Handlers.ModelHandlers.Renderer.buildHierarchy(processes, { filter: { has: () => true } });
        const firstThread = [...[...processes.values()][0].threads.values()][0];
        const secondThread = [...[...processes.values()][1].threads.values()][0];
        if (!firstThread.tree || !secondThread.tree) {
            assert(false, 'Trees not found');
        }
        assert.strictEqual(firstThread.tree.maxDepth, 3, 'Got the correct tree max depth for the first thread');
        assert.strictEqual(secondThread.tree.maxDepth, 3, 'Got the correct tree max depth for the second thread');
        const firstRoots = getEventsIn(firstThread.tree.roots.values());
        assert.deepEqual(firstRoots.map(e => e ? { name: e.name, ts: e.ts, dur: e.dur } : null), [
            { name: 'A', ts: 0, dur: 10 },
            { name: 'E', ts: 11, dur: 3 },
        ]);
        const secondRoots = getEventsIn(secondThread.tree.roots.values());
        assert.deepEqual(secondRoots.map(e => e ? { name: e.name, ts: e.ts, dur: e.dur } : null), [
            { name: 'F', ts: 0, dur: 3 },
            { name: 'G', ts: 3, dur: 10 },
        ]);
    });
    it('can assign origins to processes', async () => {
        const { Meta: metadata } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const processes = new Map();
        Trace.Handlers.ModelHandlers.Renderer.assignOrigin(processes, metadata.rendererProcessesByFrame);
        assert.deepEqual([...processes].map(([pid, p]) => [pid, p.url ? new URL(p.url).origin : null]), [
            [Trace.Types.Events.ProcessID(MAIN_FRAME_PID), 'http://localhost:5000'],
            [Trace.Types.Events.ProcessID(SUB_FRAME_PID), 'https://www.example.com'],
            [Trace.Types.Events.ProcessID(SUB_FRAME_PID_2), 'https://www.example.com'],
            [Trace.Types.Events.ProcessID(SUB_FRAME_PID_3), 'https://www.example.com'],
        ]);
    });
    it('can assign main frame flags to processes', async () => {
        const { Meta: metadata } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const processes = new Map();
        Trace.Handlers.ModelHandlers.Renderer.assignIsMainFrame(processes, metadata.mainFrameId, metadata.rendererProcessesByFrame);
        assert.deepEqual([...processes].map(([pid, p]) => [pid, p.isOnMainFrame]), [
            [Trace.Types.Events.ProcessID(MAIN_FRAME_PID), true],
            [Trace.Types.Events.ProcessID(SUB_FRAME_PID), false],
            [Trace.Types.Events.ProcessID(SUB_FRAME_PID_2), false],
            [Trace.Types.Events.ProcessID(SUB_FRAME_PID_3), false],
        ]);
    });
    it('can assign thread names to threads in processes', async () => {
        const { Meta: metadata } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        const { mainFrameId, rendererProcessesByFrame, threadsInProcess } = metadata;
        const processes = new Map();
        Trace.Handlers.ModelHandlers.Renderer.assignMeta(processes, mainFrameId, rendererProcessesByFrame, threadsInProcess);
        assert.deepEqual([...processes].map(([pid, p]) => [pid, [...p.threads].map(([tid, t]) => [tid, t.name])]), [
            [
                Trace.Types.Events.ProcessID(MAIN_FRAME_PID),
                [
                    [Trace.Types.Events.ThreadID(1), 'CrRendererMain'],
                    [Trace.Types.Events.ThreadID(7), 'Compositor'],
                    [Trace.Types.Events.ThreadID(2), 'ThreadPoolServiceThread'],
                    [Trace.Types.Events.ThreadID(4), 'Chrome_ChildIOThread'],
                    [Trace.Types.Events.ThreadID(24), 'ThreadPoolForegroundWorker'],
                    [Trace.Types.Events.ThreadID(27), 'ThreadPoolForegroundWorker'],
                    [Trace.Types.Events.ThreadID(17), 'ThreadPoolForegroundWorker'],
                    [Trace.Types.Events.ThreadID(29), 'ThreadPoolForegroundWorker'],
                    [Trace.Types.Events.ThreadID(25), 'ThreadPoolForegroundWorker'],
                    [Trace.Types.Events.ThreadID(28), 'ThreadPoolForegroundWorker'],
                    [Trace.Types.Events.ThreadID(30), 'ThreadPoolForegroundWorker'],
                    [Trace.Types.Events.ThreadID(26), 'ThreadPoolForegroundWorker'],
                    [Trace.Types.Events.ThreadID(11), 'CompositorTileWorker3'],
                    [Trace.Types.Events.ThreadID(12), 'CompositorTileWorker4'],
                    [Trace.Types.Events.ThreadID(10), 'CompositorTileWorker2'],
                    [Trace.Types.Events.ThreadID(9), 'CompositorTileWorker1'],
                ],
            ],
            [
                Trace.Types.Events.ProcessID(SUB_FRAME_PID),
                [
                    [Trace.Types.Events.ThreadID(2), 'ThreadPoolServiceThread'],
                    [Trace.Types.Events.ThreadID(1), 'CrRendererMain'],
                    [Trace.Types.Events.ThreadID(7), 'Compositor'],
                    [Trace.Types.Events.ThreadID(4), 'Chrome_ChildIOThread'],
                ],
            ],
            [
                Trace.Types.Events.ProcessID(SUB_FRAME_PID_2),
                [
                    [Trace.Types.Events.ThreadID(1), 'CrRendererMain'],
                    [Trace.Types.Events.ThreadID(4), 'Chrome_ChildIOThread'],
                    [Trace.Types.Events.ThreadID(8), 'Compositor'],
                    [Trace.Types.Events.ThreadID(2), 'ThreadPoolServiceThread'],
                    [Trace.Types.Events.ThreadID(10), 'CompositorTileWorker1'],
                ],
            ],
            [
                Trace.Types.Events.ProcessID(SUB_FRAME_PID_3),
                [
                    [Trace.Types.Events.ThreadID(1), 'CrRendererMain'],
                    [Trace.Types.Events.ThreadID(2), 'ThreadPoolServiceThread'],
                    [Trace.Types.Events.ThreadID(4), 'Chrome_ChildIOThread'],
                    [Trace.Types.Events.ThreadID(7), 'Compositor'],
                    [Trace.Types.Events.ThreadID(10), 'CompositorTileWorker2'],
                    [Trace.Types.Events.ThreadID(3), 'ThreadPoolForegroundWorker'],
                ],
            ],
        ]);
    });
    it('populates the map of trace events to tree nodes', async () => {
        const { Renderer: renderers } = await handleEventsFromTraceFile(this, 'multiple-navigations-with-iframes.json.gz');
        assert.strictEqual(renderers.entryToNode.size, 3591);
    });
    describe('Synthetic complete events', () => {
        async function handleEvents(traceEvents) {
            Trace.Handlers.ModelHandlers.Renderer.reset();
            Trace.Handlers.ModelHandlers.Meta.reset();
            Trace.Handlers.ModelHandlers.Samples.reset();
            for (const event of traceEvents) {
                Trace.Handlers.ModelHandlers.Meta.handleEvent(event);
                Trace.Handlers.ModelHandlers.Renderer.handleEvent(event);
            }
            await Trace.Handlers.ModelHandlers.Meta.finalize();
            await Trace.Handlers.ModelHandlers.Samples.finalize();
            await Trace.Handlers.ModelHandlers.Renderer.finalize();
            return Trace.Handlers.ModelHandlers.Renderer.data();
        }
        const pid = Trace.Types.Events.ProcessID(28274);
        const tid = Trace.Types.Events.ThreadID(775);
        it('builds a hierarchy using begin and end trace events', async () => {
            // |------------- RunTask -------------||-- RunTask --|
            //  |-- RunMicrotasks --||-- Layout --|
            //   |- FunctionCall -|
            const traceEvents = [
                makeBeginEvent('RunTask', 0, '*', pid, tid), // 0..10
                makeBeginEvent('RunMicrotasks', 1, '*', pid, tid), // 1..4
                makeBeginEvent('FunctionCall', 2, '*', pid, tid), // 2..3
                makeEndEvent('FunctionCall', 3, '*', pid, tid), // 2..3
                makeEndEvent('RunMicrotasks', 4, '*', pid, tid), // 1..4
                makeBeginEvent('Layout', 5, '*', pid, tid), // 5..8
                makeEndEvent('Layout', 8, '*', pid, tid), // 5..8
                makeEndEvent('RunTask', 10, '*', pid, tid), // 0..10
                makeBeginEvent('RunTask', 11, '*', pid, tid), // 11..14
                makeEndEvent('RunTask', 14, '*', pid, tid), // 11..14
            ];
            const rendererData = await handleEvents(traceEvents);
            assert.strictEqual(rendererData.processes.size, 1);
            const [process] = rendererData.processes.values();
            assert.strictEqual(process.threads.size, 1);
            const [thread] = process.threads.values();
            assert.strictEqual(thread.tree?.roots.size, 2);
            if (!thread.tree?.roots) {
                // This shouldn't happen, since the tree.roots.size is 2, but add this if check to pass ts check.
                return;
            }
            const allNodes = getAllNodes(thread.tree?.roots);
            assert.lengthOf(allNodes, 5);
            if (!thread.tree) {
                return;
            }
            assert.strictEqual(prettyPrint(thread.tree), `
-RunTask [0.01ms]
  -RunMicrotasks [0.003ms]
    -FunctionCall [0.001ms]
  -Layout [0.003ms]
-RunTask [0.003ms]`);
        });
        it('builds a hierarchy using complete, begin and end trace events', async () => {
            // |------------- RunTask -------------|
            //  |-- RunMicrotasks --||-- Layout --|
            //   |- FunctionCall -|
            const traceEvents = [
                makeBeginEvent('RunTask', 0, '*', pid, tid), // 0..10
                makeBeginEvent('RunMicrotasks', 1, '*', pid, tid), // 1..4
                makeCompleteEvent('FunctionCall', 2, 1, '*', pid, tid), // 2..3
                makeEndEvent('RunMicrotasks', 4, '*', pid, tid), // 1..4
                makeBeginEvent('Layout', 5, '*', pid, tid), // 5..8
                makeEndEvent('Layout', 8, '*', pid, tid), // 5..8
                makeEndEvent('RunTask', 10, '*', pid, tid), // 0..10
            ];
            const rendererData = await handleEvents(traceEvents);
            assert.strictEqual(rendererData.processes.size, 1);
            const [process] = rendererData.processes.values();
            assert.strictEqual(process.threads.size, 1);
            const [thread] = process.threads.values();
            assert.strictEqual(thread.tree?.roots.size, 1);
            if (!thread.tree?.roots) {
                // This shouldn't happen, since the tree.roots.size is 1, but add this if check to pass ts check.
                return;
            }
            const allNodes = getAllNodes(thread.tree?.roots);
            assert.lengthOf(allNodes, 4);
            if (!thread.tree) {
                return;
            }
            assert.strictEqual(prettyPrint(thread.tree), `
-RunTask [0.01ms]
  -RunMicrotasks [0.003ms]
    -FunctionCall [0.001ms]
  -Layout [0.003ms]`);
        });
        it('keeps a FunctionCall that has the end event missing', async () => {
            const traceEvents = [
                makeBeginEvent('RunMicrotasks', 1, '*', pid, tid), // 1..4
                makeBeginEvent('FunctionCall', 2, '*', pid, tid), // 2..3
            ];
            const data = await handleEvents(traceEvents);
            assert.strictEqual(data.processes.size, 1);
            const [process] = data.processes.values();
            assert.strictEqual(process.threads.size, 1);
            const [thread] = process.threads.values();
            if (!thread.tree) {
                throw new Error('thread should have a tree');
            }
            // Ensure that the FunctionCall event has been kept despite not having an END event.
            assert.deepEqual(thread.entries.map(e => e.name), ['RunMicrotasks', 'FunctionCall']);
        });
    });
    describe('building hierarchies trace events and profile calls', () => {
        it('build a hierarchy using data from real world trace file', async () => {
            const { Renderer } = await handleEventsFromTraceFile(this, 'recursive-counting-js.json.gz');
            const threadId = Trace.Types.Events.ThreadID(259);
            const firstProcessId = Trace.Types.Events.ProcessID(23239);
            const thread = Renderer.processes.get(firstProcessId)?.threads.get(threadId);
            if (!thread?.tree) {
                throw new Error('Tree not found');
            }
            const onlyLongTasksPredicate = (_node, event) => Boolean(event.dur && event.dur > 1000) &&
                Boolean(Trace.Styles.getEventStyle(event.name));
            assert.strictEqual(prettyPrint(thread.tree, onlyLongTasksPredicate), `
.............
-RunTask [17.269ms]
.............................
-RunTask [1065.663ms]
  -ParseHTML [1065.609ms]
.........
-RunTask [1.12ms]
  -ParseHTML [1.082ms]
.........................................................
-RunTask [1058.811ms]
  -TimerFire [1058.77ms]
    -FunctionCall [1058.693ms]
.
      -ProfileCall (anonymous) [1058.589ms]
        -ProfileCall (foo) [1058.589ms]
          -ProfileCall (foo) [1058.589ms]
            -ProfileCall (foo) [1058.589ms]
              -ProfileCall (foo) [1058.589ms]
..
                -ProfileCall (count) [1058.453ms]
........
-RunTask [1057.455ms]
  -TimerFire [1057.391ms]
    -FunctionCall [1057.27ms]
.
      -ProfileCall (anonymous) [1056.579ms]
        -ProfileCall (foo) [1056.579ms]
          -ProfileCall (foo) [1056.579ms]
            -ProfileCall (foo) [1056.579ms]
              -ProfileCall (foo) [1056.579ms]
                -ProfileCall (count) [1056.538ms]
........`);
        });
    });
    it('identifies and returns rasterizer threads', async () => {
        const { Renderer } = await handleEventsFromTraceFile(this, 'web-dev.json.gz');
        assert.deepEqual(Array.from(Renderer.compositorTileWorkers.entries()), [
            [
                Trace.Types.Events.ProcessID(68481),
                [
                    Trace.Types.Events.ThreadID(81675),
                ],
            ],
            [
                Trace.Types.Events.ProcessID(73704),
                [
                    Trace.Types.Events.ThreadID(23299),
                    Trace.Types.Events.ThreadID(22275),
                    Trace.Types.Events.ThreadID(41475),
                    Trace.Types.Events.ThreadID(40451),
                    Trace.Types.Events.ThreadID(22531),
                ],
            ],
        ]);
    });
    it('keeps the processes associated with AuctionWorklets and assigns them URLs', async () => {
        const { Renderer, AuctionWorklets } = await handleEventsFromTraceFile(this, 'fenced-frame-fledge.json.gz');
        assert.strictEqual(AuctionWorklets.worklets.size, 3);
        for (const [pid] of AuctionWorklets.worklets) {
            const process = Renderer.processes.get(pid);
            assert.exists(process);
            // Ensure that the URL was set properly based on the AuctionWorklets metadata event.
            assert.isTrue(process?.url?.includes('fledge-demo.glitch.me'));
        }
    });
    describe('ThirdParty', () => {
        it('correctly creates entities (simple)', async function () {
            const { Renderer } = await handleEventsFromTraceFile(this, 'load-simple.json.gz');
            const entities = Array.from(Renderer.entityMappings.eventsByEntity.keys()).map(entity => entity.name);
            const expectedEntities = ['localhost', 'Google Fonts'];
            assert.deepEqual(entities, expectedEntities);
        });
        it('correctly creates entities', async function () {
            const { Renderer } = await handleEventsFromTraceFile(this, 'lantern/paul/trace.json.gz');
            const entityNames = [...Renderer.entityMappings.eventsByEntity.keys()].map(entity => entity.name);
            assert.deepEqual([...new Set(entityNames)], [
                'paulirish.com',
                'Google Tag Manager',
                'Google Fonts',
                'Google Analytics',
                'Disqus',
                // Firebase has no self time, but entity was created in NetworkHandler because transferSize
                'Firebase',
            ]);
        });
    });
});
//# sourceMappingURL=RendererHandler.test.js.map