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