// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Platform from '../../../core/platform/platform.js';
import * as Types from '../types/types.js';
// We track the renderer processes we see in each frame on the way through the trace.
const rendererProcessesByFrameId = new Map();
// We will often want to key data by Frame IDs, and commonly we'll care most
// about the main frame's ID, so we store and expose that.
let mainFrameId = '';
let mainFrameURL = '';
const framesByProcessId = new Map();
// We will often want to key data by the browser process, GPU process and top
// level renderer IDs, so keep a track on those.
let browserProcessId = Types.TraceEvents.ProcessID(-1);
let browserThreadId = Types.TraceEvents.ThreadID(-1);
let gpuProcessId = Types.TraceEvents.ProcessID(-1);
let gpuThreadId = Types.TraceEvents.ThreadID(-1);
let viewportRect = null;
const topLevelRendererIds = new Set();
const traceBounds = {
    min: Types.Timing.MicroSeconds(Number.POSITIVE_INFINITY),
    max: Types.Timing.MicroSeconds(Number.NEGATIVE_INFINITY),
    range: Types.Timing.MicroSeconds(Number.POSITIVE_INFINITY),
};
/**
 * These represent the user navigating. Values such as First Contentful Paint,
 * etc, are relative to the navigation.
 *
 *  We store navigation events both by the frame and navigation ID. This means
 * when we need to look them up, we can use whichever ID we have.
 *
 * Note that these Maps will have the same values in them; these are just keyed
 * differently to make look-ups easier.
 *
 * We also additionally maintain an array of only navigations that occured on
 * the main frame. In many places in the UI we only care about highlighting
 * main frame navigations, so calculating this list here is better than
 * filtering either of the below maps over and over again at the UI layer.
 */
const navigationsByFrameId = new Map();
const navigationsByNavigationId = new Map();
const mainFrameNavigations = [];
// Represents all the threads in the trace, organized by process. This is mostly for internal
// bookkeeping so that during the finalize pass we can obtain the main and browser thread IDs.
const threadsInProcess = new Map();
let traceStartedTime = Types.Timing.MicroSeconds(-1);
const eventPhasesOfInterestForTraceBounds = new Set([
    "B" /* Types.TraceEvents.Phase.BEGIN */,
    "E" /* Types.TraceEvents.Phase.END */,
    "X" /* Types.TraceEvents.Phase.COMPLETE */,
    "I" /* Types.TraceEvents.Phase.INSTANT */,
]);
let handlerState = 1 /* HandlerState.UNINITIALIZED */;
export function reset() {
    navigationsByFrameId.clear();
    navigationsByNavigationId.clear();
    mainFrameNavigations.length = 0;
    browserProcessId = Types.TraceEvents.ProcessID(-1);
    browserThreadId = Types.TraceEvents.ThreadID(-1);
    gpuProcessId = Types.TraceEvents.ProcessID(-1);
    gpuThreadId = Types.TraceEvents.ThreadID(-1);
    viewportRect = null;
    topLevelRendererIds.clear();
    threadsInProcess.clear();
    rendererProcessesByFrameId.clear();
    framesByProcessId.clear();
    traceBounds.min = Types.Timing.MicroSeconds(Number.POSITIVE_INFINITY);
    traceBounds.max = Types.Timing.MicroSeconds(Number.NEGATIVE_INFINITY);
    traceBounds.range = Types.Timing.MicroSeconds(Number.POSITIVE_INFINITY);
    traceStartedTime = Types.Timing.MicroSeconds(-1);
    handlerState = 1 /* HandlerState.UNINITIALIZED */;
}
export function initialize() {
    if (handlerState !== 1 /* HandlerState.UNINITIALIZED */) {
        throw new Error('Meta Handler was not reset');
    }
    handlerState = 2 /* HandlerState.INITIALIZED */;
}
function updateRendererProcessByFrame(event, frame) {
    const framesInProcessById = Platform.MapUtilities.getWithDefault(framesByProcessId, frame.processId, () => new Map());
    framesInProcessById.set(frame.frame, frame);
    const rendererProcessInFrame = Platform.MapUtilities.getWithDefault(rendererProcessesByFrameId, frame.frame, () => new Map());
    const rendererProcessInfo = Platform.MapUtilities.getWithDefault(rendererProcessInFrame, frame.processId, () => {
        return [];
    });
    const lastProcessData = rendererProcessInfo.at(-1);
    // Only store a new entry if the URL changed, otherwise it's just
    // redundant information.
    if (lastProcessData && lastProcessData.frame.url === frame.url) {
        return;
    }
    // For now we store the time of the event as the min. In the finalize we step
    // through each of these windows and update their max and range values.
    rendererProcessInfo.push({
        frame,
        window: {
            min: event.ts,
            max: Types.Timing.MicroSeconds(0),
            range: Types.Timing.MicroSeconds(0),
        },
    });
}
export function handleEvent(event) {
    if (handlerState !== 2 /* HandlerState.INITIALIZED */) {
        throw new Error('Meta Handler is not initialized');
    }
    // If there is a timestamp (which meta events do not have), and the event does
    // not end with ::UMA then it, and the event is in the set of valid phases,
    // then it should be included for the purposes of calculating the trace bounds.
    // The UMA events in particular seem to be reported on page unloading, which
    // often extends the bounds of the trace unhelpfully.
    if (event.ts !== 0 && !event.name.endsWith('::UMA') && eventPhasesOfInterestForTraceBounds.has(event.ph)) {
        traceBounds.min = Types.Timing.MicroSeconds(Math.min(event.ts, traceBounds.min));
        const eventDuration = event.dur || Types.Timing.MicroSeconds(0);
        traceBounds.max = Types.Timing.MicroSeconds(Math.max(event.ts + eventDuration, traceBounds.max));
    }
    if (Types.TraceEvents.isProcessName(event) &&
        (event.args.name === 'Browser' || event.args.name === 'HeadlessBrowser')) {
        browserProcessId = event.pid;
        return;
    }
    if (Types.TraceEvents.isProcessName(event) && (event.args.name === 'Gpu' || event.args.name === 'GPU Process')) {
        gpuProcessId = event.pid;
        return;
    }
    if (Types.TraceEvents.isThreadName(event) && event.args.name === 'CrGpuMain') {
        gpuThreadId = event.tid;
        return;
    }
    if (Types.TraceEvents.isThreadName(event) && event.args.name === 'CrBrowserMain') {
        browserThreadId = event.tid;
    }
    if (Types.TraceEvents.isTraceEventMainFrameViewport(event) && viewportRect === null) {
        const rectAsArray = event.args.data.viewport_rect;
        const viewportX = rectAsArray[0];
        const viewportY = rectAsArray[1];
        const viewportWidth = rectAsArray[2];
        const viewportHeight = rectAsArray[5];
        viewportRect = new DOMRect(viewportX, viewportY, viewportWidth, viewportHeight);
    }
    // The TracingStartedInBrowser event includes the data on which frames are
    // in scope at the start of the trace. We use this to identify the frame with
    // no parent, i.e. the top level frame.
    if (Types.TraceEvents.isTraceEventTracingStartedInBrowser(event)) {
        traceStartedTime = event.ts;
        if (!event.args.data) {
            throw new Error('No frames found in trace data');
        }
        for (const frame of (event.args.data.frames ?? [])) {
            updateRendererProcessByFrame(event, frame);
            if (frame.parent) {
                continue;
            }
            mainFrameId = frame.frame;
            mainFrameURL = frame.url;
            topLevelRendererIds.add(frame.processId);
        }
        return;
    }
    // FrameCommittedInBrowser events tell us information about each frame
    // and we use these to track how long each individual renderer is active
    // for. We track all renderers here (top level and those in frames), but
    // for convenience we also populate a set of top level renderer IDs.
    if (Types.TraceEvents.isTraceEventFrameCommittedInBrowser(event)) {
        const frame = event.args.data;
        if (!frame) {
            return;
        }
        updateRendererProcessByFrame(event, frame);
        if (frame.parent) {
            return;
        }
        topLevelRendererIds.add(frame.processId);
        return;
    }
    if (Types.TraceEvents.isTraceEventCommitLoad(event)) {
        const frameData = event.args.data;
        if (!frameData) {
            return;
        }
        const { frame, name, url } = frameData;
        updateRendererProcessByFrame(event, { processId: event.pid, frame, name, url });
        return;
    }
    // Track all threads based on the process & thread IDs.
    if (Types.TraceEvents.isThreadName(event)) {
        const threads = Platform.MapUtilities.getWithDefault(threadsInProcess, event.pid, () => new Map());
        threads.set(event.tid, event);
        return;
    }
    // Track all navigation events. Note that there can be navigation start events
    // but where the documentLoaderURL is empty. As far as the trace rendering is
    // concerned, these events are noise so we filter them out here.
    if (Types.TraceEvents.isTraceEventNavigationStartWithURL(event) && event.args.data) {
        const navigationId = event.args.data.navigationId;
        if (navigationsByNavigationId.has(navigationId)) {
            throw new Error('Found multiple navigation start events with the same navigation ID.');
        }
        navigationsByNavigationId.set(navigationId, event);
        const frameId = event.args.frame;
        const existingFrameNavigations = navigationsByFrameId.get(frameId) || [];
        existingFrameNavigations.push(event);
        navigationsByFrameId.set(frameId, existingFrameNavigations);
        if (frameId === mainFrameId) {
            mainFrameNavigations.push(event);
        }
        return;
    }
}
export async function finalize() {
    if (handlerState !== 2 /* HandlerState.INITIALIZED */) {
        throw new Error('Handler is not initialized');
    }
    traceBounds.min = traceStartedTime;
    traceBounds.range = Types.Timing.MicroSeconds(traceBounds.max - traceBounds.min);
    // If we go from foo.com to example.com we will get a new renderer, and
    // therefore the "top level renderer" will have a different PID as it has
    // changed. Here we step through each renderer process and updated its window
    // bounds, such that we end up with the time ranges in the trace for when
    // each particular renderer started and stopped being the main renderer
    // process.
    for (const [, processWindows] of rendererProcessesByFrameId) {
        const processWindowValues = [...processWindows.values()].flat();
        for (let i = 0; i < processWindowValues.length; i++) {
            const currentWindow = processWindowValues[i];
            const nextWindow = processWindowValues[i + 1];
            // For the last window we set its max to be positive infinity.
            // TODO: Move the trace bounds handler into meta so we can clamp first and last windows.
            if (!nextWindow) {
                currentWindow.window.max = Types.Timing.MicroSeconds(traceBounds.max);
                currentWindow.window.range = Types.Timing.MicroSeconds(traceBounds.max - currentWindow.window.min);
            }
            else {
                currentWindow.window.max = Types.Timing.MicroSeconds(nextWindow.window.min - 1);
                currentWindow.window.range = Types.Timing.MicroSeconds(currentWindow.window.max - currentWindow.window.min);
            }
        }
    }
    // Frame ids which we didn't register using either the TracingStartedInBrowser or
    // the FrameCommittedInBrowser events are considered noise, so we filter them out, as well
    // as the navigations that belong to such frames.
    for (const [frameId, navigations] of navigationsByFrameId) {
        // The frames in the rendererProcessesByFrameId map come only from the
        // TracingStartedInBrowser and FrameCommittedInBrowser events, so we can use it as point
        // of comparison to determine if a frameId should be discarded.
        if (rendererProcessesByFrameId.has(frameId)) {
            continue;
        }
        navigationsByFrameId.delete(frameId);
        for (const navigation of navigations) {
            if (!navigation.args.data) {
                continue;
            }
            navigationsByNavigationId.delete(navigation.args.data.navigationId);
        }
    }
    handlerState = 3 /* HandlerState.FINALIZED */;
}
export function data() {
    if (handlerState !== 3 /* HandlerState.FINALIZED */) {
        throw new Error('Meta Handler is not finalized');
    }
    return {
        traceBounds: { ...traceBounds },
        browserProcessId,
        browserThreadId,
        gpuProcessId,
        gpuThreadId: gpuThreadId === Types.TraceEvents.ThreadID(-1) ? undefined : gpuThreadId,
        viewportRect: viewportRect || undefined,
        mainFrameId,
        mainFrameURL,
        navigationsByFrameId: new Map(navigationsByFrameId),
        navigationsByNavigationId: new Map(navigationsByNavigationId),
        threadsInProcess: new Map(threadsInProcess),
        rendererProcessesByFrame: new Map(rendererProcessesByFrameId),
        topLevelRendererIds: new Set(topLevelRendererIds),
        frameByProcessId: new Map(framesByProcessId),
        mainFrameNavigations: [...mainFrameNavigations],
    };
}
//# sourceMappingURL=MetaHandler.js.map