1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 6 // Network status constants. 7 const StatusConnected = 'connected'; 8 const StatusDisconnected = 'disconnected'; 9 const StatusConnecting = 'connecting'; 10 const StatusError = 'error'; 11 12 const NetworkOther = 'other'; 13 14 // Setup css canvas 'spinner-circle' 15 (function() { 16 var lineWidth = 3; 17 var r = 8; 18 var ctx = document.getCSSCanvasContext('2d', 'spinner-circle', 2 * r, 2 * r); 19 20 ctx.lineWidth = lineWidth; 21 ctx.lineCap = 'round'; 22 ctx.lineJoin = 'round'; 23 24 ctx.strokeStyle = '#4e73c7'; 25 ctx.beginPath(); 26 ctx.moveTo(lineWidth / 2, r - lineWidth / 2); 27 ctx.arc(r, r, r - lineWidth / 2, Math.PI, Math.PI * 3 / 2); 28 ctx.stroke(); 29 })(); 30 31 /** 32 * Sends "connect" using the 'action' WebUI message. 33 */ 34 function sendConnect(index, passphrase, identity, auto_connect) { 35 chrome.send('action', 36 ['connect', 37 String(index), 38 passphrase, 39 identity, 40 auto_connect ? '1' : '0']); 41 } 42 43 var networkMenuItemProto = (function() { 44 var networkMenuItem = cr.doc.createElement('div'); 45 networkMenuItem.innerHTML = '<div class="network-menu-item">' + 46 '<div class="network-label-icon">' + 47 '<div class="network-label"></div>' + 48 '<div class="network-icon hidden"></div>' + 49 '</div>' + 50 '<div class="network-status hidden"></div>' + 51 '<div class="hidden"></div>' + 52 '</div>'; 53 return networkMenuItem; 54 })(); 55 56 var NetworkMenuItem = cr.ui.define(function() { 57 return networkMenuItemProto.cloneNode(true); 58 }); 59 60 NetworkMenuItem.prototype = { 61 __proto__: MenuItem.prototype, 62 63 ssidEdit: null, 64 passwordEdit: null, 65 autoConnectCheckbox: null, 66 67 /** 68 * The label element. 69 * @private 70 */ 71 get label_() { 72 return this.firstElementChild.firstElementChild.firstElementChild; 73 }, 74 75 /** 76 * The icon element. 77 * @private 78 */ 79 get icon_() { 80 return this.label_.nextElementSibling; 81 }, 82 83 /** 84 * The status area element. 85 * @private 86 */ 87 get status_() { 88 return this.firstElementChild.firstElementChild.nextElementSibling; 89 }, 90 91 /** 92 * The action area container element. 93 * @private 94 */ 95 get action_() { 96 return this.status_.nextElementSibling; 97 }, 98 99 /** 100 * Set status message. 101 * @param {string} message The message to display in status area. 102 * @private 103 */ 104 setStatus_: function(message) { 105 if (message) { 106 this.status_.textContent = message; 107 this.status_.classList.remove('hidden'); 108 } else { 109 this.status_.classList.add('hidden'); 110 } 111 }, 112 113 /** 114 * Set status icon. 115 * @param {string} icon Source url for the icon image. 116 * @private 117 */ 118 setIcon_: function(icon) { 119 if (icon) { 120 this.icon_.style.backgroundImage = 'url(' + icon + ')'; 121 this.icon_.classList.remove('hidden'); 122 } else { 123 this.icon_.classList.add('hidden'); 124 } 125 }, 126 127 /** 128 * Handle reconnect. 129 * @private 130 */ 131 handleConnect_ : function(e) { 132 var index = this.menu_.getMenuItemIndexOf(this); 133 if (this.ssidEdit && this.passwordEdit) { 134 if (this.ssidEdit.value) { 135 sendConnect(index, 136 this.passwordEdit.value, 137 this.ssidEdit.value, 138 this.autoConnectCheckbox.checked); 139 } 140 } else if (this.passwordEdit) { 141 if (this.passwordEdit.value) { 142 sendConnect(index, 143 this.passwordEdit.value, '', this.autoConnectCheckbox.checked); 144 } 145 } else { 146 if (this.attrs.remembered) { 147 sendConnect(index, this.attrs.passphrase, '', this.attrs.auto_connect); 148 } else { 149 sendConnect(index, '', '', this.autoConnectCheckbox.checked); 150 } 151 } 152 }, 153 154 /** 155 * Handle keydown event in ssid edit. 156 * @private 157 */ 158 handleSsidEditKeydown_: function(e) { 159 if (e.target == this.ssidEdit && 160 e.keyIdentifier == 'Enter') { 161 this.passwordEdit.focus(); 162 } 163 }, 164 165 /** 166 * Handle keydown event in password edit. 167 * @private 168 */ 169 handlePassEditKeydown_: function(e) { 170 if (e.target == this.passwordEdit && 171 e.keyIdentifier == 'Enter') { 172 this.handleConnect_(); 173 } 174 }, 175 176 /** 177 * Returns whether action area is visible. 178 * @private 179 */ 180 isActionVisible_: function() { 181 return !this.action_.classList.contains('hidden'); 182 }, 183 184 /** 185 * Show/hide action area. 186 * @private 187 */ 188 showAction_: function(show) { 189 var visible = this.isActionVisible_(); 190 if (show && !visible) { 191 this.action_.classList.remove('hidden'); 192 } else if (!show && visible) { 193 this.action_.classList.add('hidden'); 194 } 195 }, 196 197 /** 198 * Add network name edit to action area. 199 * @private 200 */ 201 addSsidEdit_: function() { 202 this.ssidEdit = this.ownerDocument.createElement('input'); 203 this.ssidEdit.type = 'text'; 204 this.ssidEdit.placeholder = localStrings.getString('ssid_prompt'); 205 this.ssidEdit.pattern = '^\\S+$'; 206 this.ssidEdit.addEventListener('keydown', 207 this.handleSsidEditKeydown_.bind(this)); 208 209 var box = this.ownerDocument.createElement('div'); 210 box.appendChild(this.ssidEdit); 211 this.action_.appendChild(box); 212 }, 213 214 /** 215 * Add password edit to action area. 216 * @private 217 */ 218 addPasswordEdit_: function() { 219 this.passwordEdit = this.ownerDocument.createElement('input'); 220 this.passwordEdit.type = 'password'; 221 this.passwordEdit.placeholder = localStrings.getString('pass_prompt'); 222 this.passwordEdit.pattern = '^\\S+$'; 223 this.passwordEdit.addEventListener('keydown', 224 this.handlePassEditKeydown_.bind(this)); 225 226 var box = this.ownerDocument.createElement('div'); 227 box.appendChild(this.passwordEdit); 228 this.action_.appendChild(box); 229 }, 230 231 /** 232 * Add auto-connect this network check box to action area. 233 * @private 234 */ 235 addAutoConnectCheckbox_: function() { 236 this.autoConnectCheckbox = this.ownerDocument.createElement('input'); 237 this.autoConnectCheckbox.type = 'checkbox'; 238 this.autoConnectCheckbox.checked = this.attrs.auto_connect; 239 240 var autoConnectSpan = this.ownerDocument.createElement('span'); 241 autoConnectSpan.textContent = 242 localStrings.getString('auto_connect_this_network'); 243 244 var autoConnectLabel = this.ownerDocument.createElement('label'); 245 autoConnectLabel.appendChild(this.autoConnectCheckbox); 246 autoConnectLabel.appendChild(autoConnectSpan); 247 248 this.action_.appendChild(autoConnectLabel); 249 }, 250 251 /** 252 * Internal method to initiailze the MenuItem. 253 * @private 254 */ 255 initMenuItem_: function() { 256 // *TODO: eliminate code duplication with menu.js 257 // MenuItem.prototype.initMenuItem_(); 258 var attrs = this.attrs; 259 this.classList.add(attrs.type); 260 this.menu_.addHandlers(this, this); 261 262 //////// NetworkMenuItem specific code: 263 // TODO: Handle specific types of network, connecting icon. 264 this.label_.textContent = attrs.label; 265 266 if (attrs.network_type == NetworkOther) { 267 this.addSsidEdit_(); 268 this.addPasswordEdit_(); 269 this.addAutoConnectCheckbox_(); 270 } else if (attrs.status && attrs.status != 'unknown') { 271 if (attrs.status == StatusConnected) { 272 this.setStatus_(attrs.ip_address); 273 } else if (attrs.status == StatusConnecting) { 274 this.setStatus_(attrs.message); 275 276 this.icon_.classList.add('spinner'); 277 this.icon_.classList.remove('hidden'); 278 } else if (attrs.status == StatusError) { 279 this.setStatus_(attrs.message); 280 this.setIcon_('chrome://theme/IDR_WARNING'); 281 282 var button = this.ownerDocument.createElement('button'); 283 button.textContent = localStrings.getString('reconnect'); 284 button.addEventListener('click', this.handleConnect_.bind(this)); 285 var box = this.ownerDocument.createElement('div'); 286 box.appendChild(button); 287 this.action_.appendChild(box); 288 289 this.showAction_(true); 290 } 291 292 if (attrs.need_passphrase) { 293 this.addPasswordEdit_(); 294 } 295 296 this.addAutoConnectCheckbox_(); 297 } 298 //////// End NetworkMenuItem specifi code 299 300 if (attrs.font) { 301 this.label_.style.font = attrs.font; 302 303 var baseFont = attrs.font.replace(/bold/, '').replace(/italic/, ''); 304 this.status_.style.font = baseFont; 305 this.action_.style.font = baseFont; 306 } 307 }, 308 309 /** @override */ 310 activate: function() { 311 // Close action area and connect if it is visible. 312 if (this.isActionVisible_()) { 313 this.showAction_(false); 314 this.handleConnect_(); 315 return; 316 } 317 318 // Show action area for encrypted network and 'other' network. 319 if ((this.attrs.network_type == NetworkOther || 320 this.attrs.status == StatusDisconnected) && 321 this.attrs.need_passphrase && 322 !this.isActionVisible_()) { 323 this.showAction_(true); 324 return; 325 } 326 327 MenuItem.prototype.activate.call(this); 328 } 329 }; 330 331 332 var NetworkMenu = cr.ui.define('div'); 333 334 NetworkMenu.prototype = { 335 __proto__: Menu.prototype, 336 337 /** @override */ 338 createMenuItem: function(attrs) { 339 if (attrs.type == 'command') { 340 return new NetworkMenuItem(); 341 } else { 342 return new MenuItem(); 343 } 344 }, 345 346 /** @override */ 347 onClick_: function(event, item) { 348 // If item is a NetworkMenuItem, it must have at least one of the following. 349 if (item.autoConnectCheckbox || item.ssidEdit || item.passwordEdit) { 350 // Ignore clicks other than on the NetworkMenuItem itself. 351 if (event.target == item.autoConnectCheckbox || 352 event.target == item.autoConnectCheckbox.nextElementSibling || 353 event.target == item.ssidEdit || 354 event.target == item.passwordEdit) { 355 return; 356 } 357 } 358 359 Menu.prototype.onClick_.call(this, event, item); 360 }, 361 }; 362