diff options
Diffstat (limited to 'remote/test/puppeteer/utils/fetch_devices.js')
-rwxr-xr-x | remote/test/puppeteer/utils/fetch_devices.js | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/remote/test/puppeteer/utils/fetch_devices.js b/remote/test/puppeteer/utils/fetch_devices.js new file mode 100755 index 0000000000..547b68842e --- /dev/null +++ b/remote/test/puppeteer/utils/fetch_devices.js @@ -0,0 +1,282 @@ +#!/usr/bin/env node +/** + * Copyright 2017 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const util = require('util'); +const fs = require('fs'); +const path = require('path'); +const puppeteer = require('..'); +const DEVICES_URL = + 'https://raw.githubusercontent.com/ChromeDevTools/devtools-frontend/master/front_end/emulated_devices/module.json'; + +const template = `/** + * Copyright 2017 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports = %s; +for (const device of module.exports) + module.exports[device.name] = device; +`; + +const help = `Usage: node ${path.basename(__filename)} [-u <from>] <outputPath> + -u, --url The URL to load devices descriptor from. If not set, + devices will be fetched from the tip-of-tree of DevTools + frontend. + + -h, --help Show this help message + +Fetch Chrome DevTools front-end emulation devices from given URL, convert them to puppeteer +devices and save to the <outputPath>. +`; + +const argv = require('minimist')(process.argv.slice(2), { + alias: { u: 'url', h: 'help' }, +}); + +if (argv.help) { + console.log(help); + return; +} + +const url = argv.url || DEVICES_URL; +const outputPath = argv._[0]; +if (!outputPath) { + console.log('ERROR: output file name is missing. Use --help for help.'); + return; +} + +main(url); + +async function main(url) { + const browser = await puppeteer.launch(); + const chromeVersion = (await browser.version()).split('/').pop(); + await browser.close(); + console.log('GET ' + url); + const text = await httpGET(url); + let json = null; + try { + json = JSON.parse(text); + } catch (error) { + console.error(`FAILED: error parsing response - ${error.message}`); + return; + } + const devicePayloads = json.extensions + .filter((extension) => extension.type === 'emulated-device') + .map((extension) => extension.device); + let devices = []; + for (const payload of devicePayloads) { + let names = []; + if (payload.title === 'iPhone 6/7/8') + names = ['iPhone 6', 'iPhone 7', 'iPhone 8']; + else if (payload.title === 'iPhone 6/7/8 Plus') + names = ['iPhone 6 Plus', 'iPhone 7 Plus', 'iPhone 8 Plus']; + else if (payload.title === 'iPhone 5/SE') names = ['iPhone 5', 'iPhone SE']; + else names = [payload.title]; + for (const name of names) { + const device = createDevice(chromeVersion, name, payload, false); + const landscape = createDevice(chromeVersion, name, payload, true); + devices.push(device); + if ( + landscape.viewport.width !== device.viewport.width || + landscape.viewport.height !== device.viewport.height + ) + devices.push(landscape); + } + } + devices = devices.filter((device) => device.viewport.isMobile); + devices.sort((a, b) => a.name.localeCompare(b.name)); + // Use single-quotes instead of double-quotes to conform with codestyle. + const serialized = JSON.stringify(devices, null, 2) + .replace(/'/g, `\\'`) + .replace(/"/g, `'`); + const result = util.format(template, serialized); + fs.writeFileSync(outputPath, result, 'utf8'); +} + +/** + * @param {string} chromeVersion + * @param {string} deviceName + * @param {*} descriptor + * @param {boolean} landscape + * @returns {!Object} + */ +function createDevice(chromeVersion, deviceName, descriptor, landscape) { + const devicePayload = loadFromJSONV1(descriptor); + const viewportPayload = landscape + ? devicePayload.horizontal + : devicePayload.vertical; + return { + name: deviceName + (landscape ? ' landscape' : ''), + userAgent: devicePayload.userAgent.includes('%s') + ? util.format(devicePayload.userAgent, chromeVersion) + : devicePayload.userAgent, + viewport: { + width: viewportPayload.width, + height: viewportPayload.height, + deviceScaleFactor: devicePayload.deviceScaleFactor, + isMobile: devicePayload.capabilities.includes('mobile'), + hasTouch: devicePayload.capabilities.includes('touch'), + isLandscape: landscape || false, + }, + }; +} + +/** + * @param {*} json + * @returns {?Object} + */ +function loadFromJSONV1(json) { + /** + * @param {*} object + * @param {string} key + * @param {string} type + * @param {*=} defaultValue + * @returns {*} + */ + function parseValue(object, key, type, defaultValue) { + if ( + typeof object !== 'object' || + object === null || + !object.hasOwnProperty(key) + ) { + if (typeof defaultValue !== 'undefined') return defaultValue; + throw new Error( + "Emulated device is missing required property '" + key + "'" + ); + } + const value = object[key]; + if (typeof value !== type || value === null) + throw new Error( + "Emulated device property '" + + key + + "' has wrong type '" + + typeof value + + "'" + ); + return value; + } + + /** + * @param {*} object + * @param {string} key + * @returns {number} + */ + function parseIntValue(object, key) { + const value = /** @type {number} */ (parseValue(object, key, 'number')); + if (value !== Math.abs(value)) + throw new Error("Emulated device value '" + key + "' must be integer"); + return value; + } + + /** + * @param {*} json + * @returns {!{width: number, height: number}} + */ + function parseOrientation(json) { + const result = {}; + const minDeviceSize = 50; + const maxDeviceSize = 9999; + result.width = parseIntValue(json, 'width'); + if ( + result.width < 0 || + result.width > maxDeviceSize || + result.width < minDeviceSize + ) + throw new Error('Emulated device has wrong width: ' + result.width); + + result.height = parseIntValue(json, 'height'); + if ( + result.height < 0 || + result.height > maxDeviceSize || + result.height < minDeviceSize + ) + throw new Error('Emulated device has wrong height: ' + result.height); + + return /** @type {!{width: number, height: number}} */ (result); + } + + const result = {}; + result.type = /** @type {string} */ (parseValue(json, 'type', 'string')); + result.userAgent = /** @type {string} */ (parseValue( + json, + 'user-agent', + 'string' + )); + + const capabilities = parseValue(json, 'capabilities', 'object', []); + if (!Array.isArray(capabilities)) + throw new Error('Emulated device capabilities must be an array'); + result.capabilities = []; + for (let i = 0; i < capabilities.length; ++i) { + if (typeof capabilities[i] !== 'string') + throw new Error('Emulated device capability must be a string'); + result.capabilities.push(capabilities[i]); + } + + result.deviceScaleFactor = /** @type {number} */ (parseValue( + json['screen'], + 'device-pixel-ratio', + 'number' + )); + if (result.deviceScaleFactor < 0 || result.deviceScaleFactor > 100) + throw new Error( + 'Emulated device has wrong deviceScaleFactor: ' + result.deviceScaleFactor + ); + + result.vertical = parseOrientation( + parseValue(json['screen'], 'vertical', 'object') + ); + result.horizontal = parseOrientation( + parseValue(json['screen'], 'horizontal', 'object') + ); + return result; +} + +/** + * @param {url} + * @returns {!Promise} + */ +function httpGET(url) { + let fulfill, reject; + const promise = new Promise((res, rej) => { + fulfill = res; + reject = rej; + }); + const driver = url.startsWith('https://') + ? require('https') + : require('http'); + const request = driver.get(url, (response) => { + let data = ''; + response.setEncoding('utf8'); + response.on('data', (chunk) => (data += chunk)); + response.on('end', () => fulfill(data)); + response.on('error', reject); + }); + request.on('error', reject); + return promise; +} |