devices.js (4694B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); 8 const L10N = new LocalizationHelper( 9 "devtools/client/locales/device.properties" 10 ); 11 12 const { RemoteSettings } = ChromeUtils.importESModule( 13 "resource://services-settings/remote-settings.sys.mjs" 14 ); 15 16 loader.lazyRequireGetter( 17 this, 18 "asyncStorage", 19 "resource://devtools/shared/async-storage.js" 20 ); 21 22 const LOCAL_DEVICES = "devtools.devices.local"; 23 24 /* This is a catalog of common web-enabled devices and their properties, 25 * intended for (mobile) device emulation. 26 * 27 * The properties of a device are: 28 * - name: brand and model(s). 29 * - width: viewport width. 30 * - height: viewport height. 31 * - pixelRatio: ratio from viewport to physical screen pixels. 32 * - userAgent: UA string of the device's browser. 33 * - touch: whether it has a touch screen. 34 * - os: default OS, such as "ios", "fxos", "android". 35 * 36 * The device types are: 37 * ["phones", "tablets", "laptops", "televisions", "consoles", "watches"]. 38 * 39 * To propose new devices for the shared catalog, see 40 * https://firefox-source-docs.mozilla.org/devtools/responsive/devices.html#adding-and-removing-devices. 41 * 42 * You can easily add more devices to this catalog from your own code (e.g. an 43 * addon) like so: 44 * 45 * var myPhone = { name: "My Phone", ... }; 46 * require("devtools/client/shared/devices").addDevice(myPhone, "phones"); 47 */ 48 49 // Local devices catalog that addons can add to. 50 let localDevices; 51 let localDevicesLoaded = false; 52 53 /** 54 * Load local devices from storage. 55 */ 56 async function loadLocalDevices() { 57 if (localDevicesLoaded) { 58 return; 59 } 60 let devicesJSON = await asyncStorage.getItem(LOCAL_DEVICES); 61 if (!devicesJSON) { 62 devicesJSON = "{}"; 63 } 64 localDevices = JSON.parse(devicesJSON); 65 localDevicesLoaded = true; 66 } 67 68 /** 69 * Add a device to the local catalog. 70 * Returns `true` if the device is added, `false` otherwise. 71 */ 72 async function addDevice(device, type = "phones") { 73 await loadLocalDevices(); 74 let list = localDevices[type]; 75 if (!list) { 76 list = localDevices[type] = []; 77 } 78 79 // Ensure the new device is has a unique name 80 const exists = list.some(entry => entry.name == device.name); 81 if (exists) { 82 return false; 83 } 84 85 list.push(Object.assign({}, device)); 86 await asyncStorage.setItem(LOCAL_DEVICES, JSON.stringify(localDevices)); 87 88 return true; 89 } 90 91 /** 92 * Edit a device from the local catalog. 93 * Returns `true` if the device is edited, `false` otherwise. 94 */ 95 async function editDevice(oldDevice, newDevice, type = "phones") { 96 await loadLocalDevices(); 97 const list = localDevices[type]; 98 if (!list) { 99 return false; 100 } 101 102 const index = list.findIndex(entry => entry.name == oldDevice.name); 103 if (index == -1) { 104 return false; 105 } 106 107 // Replace old device info with new one 108 list.splice(index, 1, newDevice); 109 await asyncStorage.setItem(LOCAL_DEVICES, JSON.stringify(localDevices)); 110 111 return true; 112 } 113 114 /** 115 * Remove a device from the local catalog. 116 * Returns `true` if the device is removed, `false` otherwise. 117 */ 118 async function removeDevice(device, type = "phones") { 119 await loadLocalDevices(); 120 const list = localDevices[type]; 121 if (!list) { 122 return false; 123 } 124 125 const index = list.findIndex(entry => entry.name == device.name); 126 if (index == -1) { 127 return false; 128 } 129 130 list.splice(index, 1); 131 await asyncStorage.setItem(LOCAL_DEVICES, JSON.stringify(localDevices)); 132 133 return true; 134 } 135 136 /** 137 * Remove all local devices. Useful to clear everything when testing. 138 */ 139 async function removeLocalDevices() { 140 await asyncStorage.removeItem(LOCAL_DEVICES); 141 localDevices = {}; 142 } 143 144 /** 145 * Get the complete devices catalog. 146 */ 147 async function getDevices() { 148 const records = await RemoteSettings("devtools-devices").get(); 149 const devicesByType = new Map(); 150 for (const record of records) { 151 const { type } = record; 152 if (!devicesByType.has(type)) { 153 devicesByType.set(type, []); 154 } 155 devicesByType.get(type).push(record); 156 } 157 158 await loadLocalDevices(); 159 for (const type in localDevices) { 160 if (!devicesByType.has(type)) { 161 devicesByType.set(type, []); 162 } 163 devicesByType.get(type).push(...localDevices[type]); 164 } 165 return devicesByType; 166 } 167 168 /** 169 * Get the localized string for a device type. 170 */ 171 function getDeviceString(deviceType) { 172 return L10N.getStr("device." + deviceType); 173 } 174 175 module.exports = { 176 addDevice, 177 editDevice, 178 removeDevice, 179 removeLocalDevices, 180 getDevices, 181 getDeviceString, 182 };