1cb93a386Sopenharmony_ci/**
2cb93a386Sopenharmony_ci * Command line application to run Lottie-Web perf on a Lottie file in the
3cb93a386Sopenharmony_ci * browser and then exporting that result.
4cb93a386Sopenharmony_ci *
5cb93a386Sopenharmony_ci */
6cb93a386Sopenharmony_ciconst puppeteer = require('puppeteer');
7cb93a386Sopenharmony_ciconst express = require('express');
8cb93a386Sopenharmony_ciconst fs = require('fs');
9cb93a386Sopenharmony_ciconst commandLineArgs = require('command-line-args');
10cb93a386Sopenharmony_ciconst commandLineUsage= require('command-line-usage');
11cb93a386Sopenharmony_ciconst fetch = require('node-fetch');
12cb93a386Sopenharmony_ci
13cb93a386Sopenharmony_ciconst opts = [
14cb93a386Sopenharmony_ci  {
15cb93a386Sopenharmony_ci    name: 'input',
16cb93a386Sopenharmony_ci    typeLabel: '{underline file}',
17cb93a386Sopenharmony_ci    description: 'The Lottie JSON file to process.'
18cb93a386Sopenharmony_ci  },
19cb93a386Sopenharmony_ci  {
20cb93a386Sopenharmony_ci    name: 'output',
21cb93a386Sopenharmony_ci    typeLabel: '{underline file}',
22cb93a386Sopenharmony_ci    description: 'The perf file to write. Defaults to perf.json',
23cb93a386Sopenharmony_ci  },
24cb93a386Sopenharmony_ci  {
25cb93a386Sopenharmony_ci    name: 'use_gpu',
26cb93a386Sopenharmony_ci    description: 'Whether we should run in non-headless mode with GPU.',
27cb93a386Sopenharmony_ci    type: Boolean,
28cb93a386Sopenharmony_ci  },
29cb93a386Sopenharmony_ci  {
30cb93a386Sopenharmony_ci    name: 'port',
31cb93a386Sopenharmony_ci    description: 'The port number to use, defaults to 8081.',
32cb93a386Sopenharmony_ci    type: Number,
33cb93a386Sopenharmony_ci  },
34cb93a386Sopenharmony_ci  {
35cb93a386Sopenharmony_ci    name: 'lottie_player',
36cb93a386Sopenharmony_ci    description: 'The path to lottie.min.js, defaults to a local npm install location.',
37cb93a386Sopenharmony_ci    type: String,
38cb93a386Sopenharmony_ci  },
39cb93a386Sopenharmony_ci  {
40cb93a386Sopenharmony_ci    name: 'backend',
41cb93a386Sopenharmony_ci    description: 'Which lottie-web backend to use. Options: canvas or svg.',
42cb93a386Sopenharmony_ci    type: String,
43cb93a386Sopenharmony_ci  },
44cb93a386Sopenharmony_ci  {
45cb93a386Sopenharmony_ci    name: 'help',
46cb93a386Sopenharmony_ci    alias: 'h',
47cb93a386Sopenharmony_ci    type: Boolean,
48cb93a386Sopenharmony_ci    description: 'Print this usage guide.'
49cb93a386Sopenharmony_ci  },
50cb93a386Sopenharmony_ci];
51cb93a386Sopenharmony_ci
52cb93a386Sopenharmony_ciconst usage = [
53cb93a386Sopenharmony_ci  {
54cb93a386Sopenharmony_ci    header: 'Lottie-Web Perf',
55cb93a386Sopenharmony_ci    content: 'Command line application to run Lottie-Web perf.',
56cb93a386Sopenharmony_ci  },
57cb93a386Sopenharmony_ci  {
58cb93a386Sopenharmony_ci    header: 'Options',
59cb93a386Sopenharmony_ci    optionList: opts,
60cb93a386Sopenharmony_ci  },
61cb93a386Sopenharmony_ci];
62cb93a386Sopenharmony_ci
63cb93a386Sopenharmony_ci// Parse and validate flags.
64cb93a386Sopenharmony_ciconst options = commandLineArgs(opts);
65cb93a386Sopenharmony_ci
66cb93a386Sopenharmony_ciif (options.backend != 'canvas' && options.backend != 'svg') {
67cb93a386Sopenharmony_ci  console.error('You must supply a lottie-web backend (canvas, svg).');
68cb93a386Sopenharmony_ci  console.log(commandLineUsage(usage));
69cb93a386Sopenharmony_ci  process.exit(1);
70cb93a386Sopenharmony_ci}
71cb93a386Sopenharmony_ci
72cb93a386Sopenharmony_ciif (!options.output) {
73cb93a386Sopenharmony_ci  options.output = 'perf.json';
74cb93a386Sopenharmony_ci}
75cb93a386Sopenharmony_ciif (!options.port) {
76cb93a386Sopenharmony_ci  options.port = 8081;
77cb93a386Sopenharmony_ci}
78cb93a386Sopenharmony_ciif (!options.lottie_player) {
79cb93a386Sopenharmony_ci  options.lottie_player = 'node_modules/lottie-web/build/player/lottie.min.js';
80cb93a386Sopenharmony_ci}
81cb93a386Sopenharmony_ci
82cb93a386Sopenharmony_ciif (options.help) {
83cb93a386Sopenharmony_ci  console.log(commandLineUsage(usage));
84cb93a386Sopenharmony_ci  process.exit(0);
85cb93a386Sopenharmony_ci}
86cb93a386Sopenharmony_ci
87cb93a386Sopenharmony_ciif (!options.input) {
88cb93a386Sopenharmony_ci  console.error('You must supply a Lottie JSON filename.');
89cb93a386Sopenharmony_ci  console.log(commandLineUsage(usage));
90cb93a386Sopenharmony_ci  process.exit(1);
91cb93a386Sopenharmony_ci}
92cb93a386Sopenharmony_ci
93cb93a386Sopenharmony_ci// Start up a web server to serve the three files we need.
94cb93a386Sopenharmony_cilet lottieJS = fs.readFileSync(options.lottie_player, 'utf8');
95cb93a386Sopenharmony_cilet lottieJSON = fs.readFileSync(options.input, 'utf8');
96cb93a386Sopenharmony_cilet driverHTML;
97cb93a386Sopenharmony_ciif (options.backend == 'svg') {
98cb93a386Sopenharmony_ci  console.log('Using lottie-web-perf.html');
99cb93a386Sopenharmony_ci  driverHTML = fs.readFileSync('lottie-web-perf.html', 'utf8');
100cb93a386Sopenharmony_ci} else {
101cb93a386Sopenharmony_ci  console.log('Using lottie-web-canvas-perf.html');
102cb93a386Sopenharmony_ci  driverHTML = fs.readFileSync('lottie-web-canvas-perf.html', 'utf8');
103cb93a386Sopenharmony_ci}
104cb93a386Sopenharmony_ci
105cb93a386Sopenharmony_ci// Find number of frames from the lottie JSON.
106cb93a386Sopenharmony_cilet lottieJSONContent = JSON.parse(lottieJSON);
107cb93a386Sopenharmony_ciconst totalFrames = lottieJSONContent.op - lottieJSONContent.ip;
108cb93a386Sopenharmony_ciconsole.log('Total frames: ' + totalFrames);
109cb93a386Sopenharmony_ci
110cb93a386Sopenharmony_ciconst app = express();
111cb93a386Sopenharmony_ciapp.get('/', (req, res) => res.send(driverHTML));
112cb93a386Sopenharmony_ciapp.get('/res/lottie.js', (req, res) => res.send(lottieJS));
113cb93a386Sopenharmony_ciapp.get('/res/lottie.json', (req, res) => res.send(lottieJSON));
114cb93a386Sopenharmony_ciapp.listen(options.port, () => console.log('- Local web server started.'))
115cb93a386Sopenharmony_ci
116cb93a386Sopenharmony_ci// Utility function.
117cb93a386Sopenharmony_ciasync function wait(ms) {
118cb93a386Sopenharmony_ci    await new Promise(resolve => setTimeout(() => resolve(), ms));
119cb93a386Sopenharmony_ci    return ms;
120cb93a386Sopenharmony_ci}
121cb93a386Sopenharmony_ci
122cb93a386Sopenharmony_ciconst targetURL = "http://localhost:" + options.port + "/#" + totalFrames;
123cb93a386Sopenharmony_ciconst viewPort = {width: 1000, height: 1000};
124cb93a386Sopenharmony_ci
125cb93a386Sopenharmony_ci// Drive chrome to load the web page from the server we have running.
126cb93a386Sopenharmony_ciasync function driveBrowser() {
127cb93a386Sopenharmony_ci  console.log('- Launching chrome for ' + options.input);
128cb93a386Sopenharmony_ci  let browser;
129cb93a386Sopenharmony_ci  let page;
130cb93a386Sopenharmony_ci  const headless = !options.use_gpu;
131cb93a386Sopenharmony_ci  let browser_args = [
132cb93a386Sopenharmony_ci      '--no-sandbox',
133cb93a386Sopenharmony_ci      '--disable-setuid-sandbox',
134cb93a386Sopenharmony_ci      '--window-size=' + viewPort.width + ',' + viewPort.height,
135cb93a386Sopenharmony_ci  ];
136cb93a386Sopenharmony_ci  if (options.use_gpu) {
137cb93a386Sopenharmony_ci    browser_args.push('--ignore-gpu-blacklist');
138cb93a386Sopenharmony_ci    browser_args.push('--ignore-gpu-blocklist');
139cb93a386Sopenharmony_ci    browser_args.push('--enable-gpu-rasterization');
140cb93a386Sopenharmony_ci  }
141cb93a386Sopenharmony_ci  console.log("Running with headless: " + headless + " args: " + browser_args);
142cb93a386Sopenharmony_ci  try {
143cb93a386Sopenharmony_ci    browser = await puppeteer.launch({headless: headless, args: browser_args});
144cb93a386Sopenharmony_ci    page = await browser.newPage();
145cb93a386Sopenharmony_ci    await page.setViewport(viewPort);
146cb93a386Sopenharmony_ci  } catch (e) {
147cb93a386Sopenharmony_ci    console.log('Could not open the browser.', e);
148cb93a386Sopenharmony_ci    process.exit(1);
149cb93a386Sopenharmony_ci  }
150cb93a386Sopenharmony_ci
151cb93a386Sopenharmony_ci  console.log("Loading " + targetURL);
152cb93a386Sopenharmony_ci  try {
153cb93a386Sopenharmony_ci    // Start trace.
154cb93a386Sopenharmony_ci    await page.tracing.start({
155cb93a386Sopenharmony_ci      path: options.output,
156cb93a386Sopenharmony_ci      screenshots: false,
157cb93a386Sopenharmony_ci      categories: ["blink", "cc", "gpu"]
158cb93a386Sopenharmony_ci    });
159cb93a386Sopenharmony_ci
160cb93a386Sopenharmony_ci    await page.goto(targetURL, {
161cb93a386Sopenharmony_ci      timeout: 60000,
162cb93a386Sopenharmony_ci      waitUntil: 'networkidle0'
163cb93a386Sopenharmony_ci    });
164cb93a386Sopenharmony_ci
165cb93a386Sopenharmony_ci    console.log('- Waiting 60s for run to be done.');
166cb93a386Sopenharmony_ci    await page.waitForFunction('window._lottieWebDone === true', {
167cb93a386Sopenharmony_ci      timeout: 60000,
168cb93a386Sopenharmony_ci    });
169cb93a386Sopenharmony_ci
170cb93a386Sopenharmony_ci    // Stop trace.
171cb93a386Sopenharmony_ci    await page.tracing.stop();
172cb93a386Sopenharmony_ci  } catch(e) {
173cb93a386Sopenharmony_ci    console.log('Timed out while loading or drawing. Either the JSON file was ' +
174cb93a386Sopenharmony_ci                'too big or hit a bug in the player.', e);
175cb93a386Sopenharmony_ci    await browser.close();
176cb93a386Sopenharmony_ci    process.exit(1);
177cb93a386Sopenharmony_ci  }
178cb93a386Sopenharmony_ci
179cb93a386Sopenharmony_ci  await browser.close();
180cb93a386Sopenharmony_ci  // Need to call exit() because the web server is still running.
181cb93a386Sopenharmony_ci  process.exit(0);
182cb93a386Sopenharmony_ci}
183cb93a386Sopenharmony_ci
184cb93a386Sopenharmony_cidriveBrowser();
185