devices.js (6773B)
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 asyncStorage = require("resource://devtools/shared/async-storage.js"); 8 9 const { 10 ADD_DEVICE, 11 ADD_DEVICE_TYPE, 12 EDIT_DEVICE, 13 LOAD_DEVICE_LIST_START, 14 LOAD_DEVICE_LIST_ERROR, 15 LOAD_DEVICE_LIST_END, 16 REMOVE_DEVICE, 17 UPDATE_DEVICE_DISPLAYED, 18 UPDATE_DEVICE_MODAL, 19 } = require("resource://devtools/client/responsive/actions/index.js"); 20 const { 21 post, 22 } = require("resource://devtools/client/responsive/utils/message.js"); 23 24 const { 25 addDevice, 26 editDevice, 27 getDevices, 28 removeDevice, 29 } = require("resource://devtools/client/shared/devices.js"); 30 const { 31 changeUserAgent, 32 toggleTouchSimulation, 33 } = require("resource://devtools/client/responsive/actions/ui.js"); 34 const { 35 changeDevice, 36 changePixelRatio, 37 changeViewportAngle, 38 } = require("resource://devtools/client/responsive/actions/viewports.js"); 39 40 const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList"; 41 42 /** 43 * Returns an object containing the user preference of displayed devices. 44 * 45 * @return {object} containing two Sets: 46 * - added: Names of the devices that were explicitly enabled by the user 47 * - removed: Names of the devices that were explicitly removed by the user 48 */ 49 function loadPreferredDevices() { 50 const preferredDevices = { 51 added: new Set(), 52 removed: new Set(), 53 }; 54 55 if (Services.prefs.prefHasUserValue(DISPLAYED_DEVICES_PREF)) { 56 try { 57 let savedData = Services.prefs.getStringPref(DISPLAYED_DEVICES_PREF); 58 savedData = JSON.parse(savedData); 59 if (savedData.added && savedData.removed) { 60 preferredDevices.added = new Set(savedData.added); 61 preferredDevices.removed = new Set(savedData.removed); 62 } 63 } catch (e) { 64 console.error(e); 65 } 66 } 67 68 return preferredDevices; 69 } 70 71 /** 72 * Update the displayed device list preference with the given device list. 73 * 74 * @param {object} containing two Sets: 75 * - added: Names of the devices that were explicitly enabled by the user 76 * - removed: Names of the devices that were explicitly removed by the user 77 */ 78 function updatePreferredDevices(devices) { 79 let devicesToSave = { 80 added: Array.from(devices.added), 81 removed: Array.from(devices.removed), 82 }; 83 devicesToSave = JSON.stringify(devicesToSave); 84 Services.prefs.setStringPref(DISPLAYED_DEVICES_PREF, devicesToSave); 85 } 86 87 module.exports = { 88 // This function is only exported for testing purposes 89 _loadPreferredDevices: loadPreferredDevices, 90 91 updatePreferredDevices, 92 93 addCustomDevice(device) { 94 return async function ({ dispatch }) { 95 // Add custom device to device storage 96 await addDevice(device, "custom"); 97 dispatch({ 98 type: ADD_DEVICE, 99 device, 100 deviceType: "custom", 101 }); 102 }; 103 }, 104 105 addDevice(device, deviceType) { 106 return { 107 type: ADD_DEVICE, 108 device, 109 deviceType, 110 }; 111 }, 112 113 addDeviceType(deviceType) { 114 return { 115 type: ADD_DEVICE_TYPE, 116 deviceType, 117 }; 118 }, 119 120 editCustomDevice(viewport, oldDevice, newDevice) { 121 return async function ({ dispatch }) { 122 // Edit custom device in storage 123 await editDevice(oldDevice, newDevice, "custom"); 124 // Notify the window that the device should be updated in the device selector. 125 post(window, { 126 type: "change-device", 127 device: newDevice, 128 viewport, 129 }); 130 131 // Update UI if the device is selected. 132 if (viewport) { 133 dispatch(changeUserAgent(newDevice.userAgent)); 134 dispatch(toggleTouchSimulation(newDevice.touch)); 135 } 136 137 dispatch({ 138 type: EDIT_DEVICE, 139 deviceType: "custom", 140 viewport, 141 oldDevice, 142 newDevice, 143 }); 144 }; 145 }, 146 147 removeCustomDevice(device) { 148 return async function ({ dispatch }) { 149 // Remove custom device from device storage 150 await removeDevice(device, "custom"); 151 dispatch({ 152 type: REMOVE_DEVICE, 153 device, 154 deviceType: "custom", 155 }); 156 }; 157 }, 158 159 updateDeviceDisplayed(device, deviceType, displayed) { 160 return { 161 type: UPDATE_DEVICE_DISPLAYED, 162 device, 163 deviceType, 164 displayed, 165 }; 166 }, 167 168 loadDevices() { 169 return async function ({ dispatch }) { 170 dispatch({ type: LOAD_DEVICE_LIST_START }); 171 const preferredDevices = loadPreferredDevices(); 172 let deviceByTypes; 173 174 try { 175 deviceByTypes = await getDevices(); 176 } catch (e) { 177 console.error("Could not load device list: " + e); 178 dispatch({ type: LOAD_DEVICE_LIST_ERROR }); 179 return; 180 } 181 182 for (const [type, devices] of deviceByTypes.entries()) { 183 dispatch(module.exports.addDeviceType(type)); 184 for (const device of devices) { 185 if (device.os == "fxos") { 186 continue; 187 } 188 189 const newDevice = Object.assign({}, device, { 190 displayed: 191 preferredDevices.added.has(device.name) || 192 (device.featured && !preferredDevices.removed.has(device.name)), 193 }); 194 195 dispatch(module.exports.addDevice(newDevice, type)); 196 } 197 } 198 199 // Add an empty "custom" type if it doesn't exist in device storage 200 if (!deviceByTypes.has("custom")) { 201 dispatch(module.exports.addDeviceType("custom")); 202 } 203 204 dispatch({ type: LOAD_DEVICE_LIST_END }); 205 }; 206 }, 207 208 restoreDeviceState() { 209 return async function ({ dispatch, getState }) { 210 const deviceState = await asyncStorage.getItem( 211 "devtools.responsive.deviceState" 212 ); 213 if (!deviceState) { 214 return; 215 } 216 217 const { id, device: deviceName, deviceType } = deviceState; 218 const devices = getState().devices; 219 220 if (!devices.types.includes(deviceType)) { 221 // Can't find matching device type. 222 return; 223 } 224 225 const device = devices[deviceType].find(d => d.name === deviceName); 226 if (!device) { 227 // Can't find device with the same device name. 228 return; 229 } 230 231 const viewport = getState().viewports[0]; 232 233 post(window, { 234 type: "change-device", 235 device, 236 viewport, 237 }); 238 239 dispatch(changeDevice(id, device.name, deviceType)); 240 dispatch(changeViewportAngle(id, viewport.angle)); 241 dispatch(changePixelRatio(id, device.pixelRatio)); 242 dispatch(changeUserAgent(device.userAgent)); 243 dispatch(toggleTouchSimulation(device.touch)); 244 }; 245 }, 246 247 updateDeviceModal(isOpen, modalOpenedFromViewport = null) { 248 return { 249 type: UPDATE_DEVICE_MODAL, 250 isOpen, 251 modalOpenedFromViewport, 252 }; 253 }, 254 };