summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/utils/fetch_devices.js
diff options
context:
space:
mode:
Diffstat (limited to 'remote/test/puppeteer/utils/fetch_devices.js')
-rwxr-xr-xremote/test/puppeteer/utils/fetch_devices.js282
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;
+}