UserAgentInput.js (6726B)
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 { 8 PureComponent, 9 createFactory, 10 } = require("resource://devtools/client/shared/vendor/react.mjs"); 11 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 12 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 13 const { 14 connect, 15 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 16 const { KeyCodes } = require("resource://devtools/client/shared/keycodes.js"); 17 18 const MenuButton = createFactory( 19 require("resource://devtools/client/shared/components/menu/MenuButton.js") 20 ); 21 const { 22 parseUserAgent, 23 } = require("resource://devtools/client/responsive/utils/ua.js"); 24 25 const { AppConstants } = ChromeUtils.importESModule( 26 "resource://gre/modules/AppConstants.sys.mjs" 27 ); 28 29 loader.lazyGetter(this, "MenuItem", () => { 30 const menuItemClass = require("resource://devtools/client/shared/components/menu/MenuItem.js"); 31 const menuItem = createFactory(menuItemClass); 32 menuItem.DUMMY_ICON = menuItemClass.DUMMY_ICON; 33 return menuItem; 34 }); 35 36 loader.lazyGetter(this, "MenuList", () => { 37 return createFactory( 38 require("resource://devtools/client/shared/components/menu/MenuList.js") 39 ); 40 }); 41 42 const { 43 getStr, 44 } = require("resource://devtools/client/responsive/utils/l10n.js"); 45 46 class UserAgentInput extends PureComponent { 47 static get propTypes() { 48 return { 49 onChangeUserAgent: PropTypes.func.isRequired, 50 userAgent: PropTypes.string.isRequired, 51 selectedDeviceName: PropTypes.string, 52 selectedDeviceUserAgent: PropTypes.string, 53 }; 54 } 55 56 static getDerivedStateFromProps(props, state) { 57 if (props.userAgent !== state.prevUserAgent) { 58 return { 59 value: props.userAgent, 60 prevUserAgent: props.userAgent, 61 }; 62 } 63 return null; 64 } 65 66 constructor(props) { 67 super(props); 68 69 this.state = { 70 // The user agent input value. 71 value: this.props.userAgent, 72 // Track the last passed userAgent value in the props to 73 // to update the local state "value" when the prop changes 74 prevUserAgent: this.props.userAgent, 75 }; 76 77 this.onChange = this.onChange.bind(this); 78 this.onKeyUp = this.onKeyUp.bind(this); 79 this.onBlur = this.onBlur.bind(this); 80 } 81 82 /** 83 * Input change handler. 84 * 85 * @param {Event} event 86 */ 87 onChange({ target }) { 88 const value = target.value; 89 90 this.setState(prevState => { 91 return { 92 ...prevState, 93 value, 94 }; 95 }); 96 } 97 98 /** 99 * Input key up handler. 100 * 101 * @param {Event} event 102 */ 103 onKeyUp({ target, keyCode }) { 104 if (keyCode == KeyCodes.DOM_VK_RETURN) { 105 // This triggers the onBlur() handler, which calls this.props.onChangeUserAgent() 106 target.blur(); 107 } 108 109 if (keyCode == KeyCodes.DOM_VK_ESCAPE) { 110 this.setState({ value: this.props.userAgent }, () => target.blur()); 111 } 112 } 113 114 onBlur({ target }) { 115 this.props.onChangeUserAgent(target.value); 116 } 117 118 onChangeUserAgent(userAgent) { 119 this.setState({ 120 value: userAgent, 121 }); 122 this.props.onChangeUserAgent(userAgent); 123 } 124 125 renderMenuList() { 126 const browsers = []; 127 128 const { selectedDeviceName, selectedDeviceUserAgent } = this.props; 129 if (selectedDeviceName) { 130 const { browser } = parseUserAgent(selectedDeviceUserAgent); 131 browsers.push({ 132 name: selectedDeviceName, 133 userAgent: selectedDeviceUserAgent, 134 icon: browser ? browser.name.toLowerCase() : "", 135 separator: true, 136 }); 137 } 138 139 const androidVersion = "15"; 140 const firefoxVersion = AppConstants.MOZ_APP_VERSION.replace(/[ab]\d+/, ""); 141 // Bug 1953205 should revisit how the browser/user agent string list is implemented 142 // and avoid hardcoding the chrome version number 143 const chromeVersion = "134.0.0.0"; 144 // Chrome uses a fixed version to reference WebKit and Safari 145 const frozenWebkitVersionForChromeUA = "537.36"; 146 147 browsers.push( 148 { 149 name: "Firefox Desktop", 150 // Empty string will default the firefox original user agent 151 userAgent: "", 152 icon: "firefox", 153 version: firefoxVersion, 154 }, 155 { 156 name: "Firefox for Android", 157 userAgent: `Mozilla/5.0 (Android ${androidVersion}; Mobile; rv:${firefoxVersion}) Gecko/${firefoxVersion} Firefox/${firefoxVersion}`, 158 icon: "firefox", 159 version: firefoxVersion, 160 }, 161 { 162 name: "Chrome Desktop", 163 userAgent: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/${frozenWebkitVersionForChromeUA} (KHTML, like Gecko) Chrome/${chromeVersion} Safari/${frozenWebkitVersionForChromeUA}`, 164 icon: "chrome", 165 version: chromeVersion, 166 } 167 ); 168 169 const menuItems = []; 170 for (const browser of browsers) { 171 const { icon, name, userAgent, version, separator } = browser; 172 menuItems.push( 173 MenuItem({ 174 key: name, 175 className: 176 "user-agent-selector-item" + (separator ? " separator" : ""), 177 label: [ 178 name, 179 // Only show the major version as chrome uses 136.0.0.0 180 version 181 ? dom.span( 182 { className: "user-agent-browser-version" }, 183 version.split(".")[0] 184 ) 185 : null, 186 ], 187 icon: icon 188 ? `chrome://devtools/skin/images/browsers/${icon}.svg` 189 : MenuItem.DUMMY_ICON, 190 tooltip: name, 191 checked: this.state.value == userAgent, 192 onClick: () => this.onChangeUserAgent(userAgent), 193 }) 194 ); 195 } 196 197 return MenuList({}, menuItems); 198 } 199 200 render() { 201 return dom.label( 202 { id: "user-agent-label" }, 203 "UA:", 204 dom.input({ 205 id: "user-agent-input", 206 className: "text-input", 207 onChange: this.onChange, 208 onKeyUp: this.onKeyUp, 209 onBlur: this.onBlur, 210 placeholder: getStr("responsive.customUserAgent"), 211 type: "text", 212 value: this.state.value, 213 }), 214 MenuButton( 215 { 216 id: "user-agent-selector", 217 menuId: "user-agent-selector-menu", 218 toolboxDoc: window.document, 219 className: "devtools-button devtools-dropdown-button", 220 label: "", 221 title: getStr("responsive.userAgentList"), 222 }, 223 () => this.renderMenuList() 224 ) 225 ); 226 } 227 } 228 229 const mapStateToProps = state => { 230 return { 231 userAgent: state.ui.userAgent, 232 }; 233 }; 234 235 module.exports = connect(mapStateToProps)(UserAgentInput);