1// Copyright (c) 2012 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 5cr.define('options.network', function() { 6 7 var ArrayDataModel = cr.ui.ArrayDataModel; 8 var List = cr.ui.List; 9 var ListItem = cr.ui.ListItem; 10 var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; 11 var Menu = cr.ui.Menu; 12 var MenuItem = cr.ui.MenuItem; 13 var ControlledSettingIndicator = options.ControlledSettingIndicator; 14 15 /** 16 * Network settings constants. These enums usually match their C++ 17 * counterparts. 18 */ 19 function Constants() {} 20 21 // Network types: 22 Constants.TYPE_UNKNOWN = 'UNKNOWN'; 23 Constants.TYPE_ETHERNET = 'ethernet'; 24 Constants.TYPE_WIFI = 'wifi'; 25 Constants.TYPE_WIMAX = 'wimax'; 26 Constants.TYPE_BLUETOOTH = 'bluetooth'; 27 Constants.TYPE_CELLULAR = 'cellular'; 28 Constants.TYPE_VPN = 'vpn'; 29 30 // Cellular activation states: 31 Constants.ACTIVATION_STATE_UNKNOWN = 0; 32 Constants.ACTIVATION_STATE_ACTIVATED = 1; 33 Constants.ACTIVATION_STATE_ACTIVATING = 2; 34 Constants.ACTIVATION_STATE_NOT_ACTIVATED = 3; 35 Constants.ACTIVATION_STATE_PARTIALLY_ACTIVATED = 4; 36 37 /** 38 * Order in which controls are to appear in the network list sorted by key. 39 */ 40 Constants.NETWORK_ORDER = ['ethernet', 41 'wifi', 42 'wimax', 43 'cellular', 44 'vpn', 45 'addConnection']; 46 47 /** 48 * Mapping of network category titles to the network type. 49 */ 50 var categoryMap = { 51 'cellular': Constants.TYPE_CELLULAR, 52 'ethernet': Constants.TYPE_ETHERNET, 53 'wimax': Constants.TYPE_WIMAX, 54 'wifi': Constants.TYPE_WIFI, 55 'vpn': Constants.TYPE_VPN 56 }; 57 58 /** 59 * ID of the menu that is currently visible. 60 * @type {?string} 61 * @private 62 */ 63 var activeMenu_ = null; 64 65 /** 66 * Indicates if cellular networks are available. 67 * @type {boolean} 68 * @private 69 */ 70 var cellularAvailable_ = false; 71 72 /** 73 * Indicates if cellular networks are enabled. 74 * @type {boolean} 75 * @private 76 */ 77 var cellularEnabled_ = false; 78 79 /** 80 * Indicates if cellular device supports network scanning. 81 * @type {boolean} 82 * @private 83 */ 84 var cellularSupportsScan_ = false; 85 86 /** 87 * Indicates if WiMAX networks are available. 88 * @type {boolean} 89 * @private 90 */ 91 var wimaxAvailable_ = false; 92 93 /** 94 * Indicates if WiMAX networks are enabled. 95 * @type {boolean} 96 * @private 97 */ 98 var wimaxEnabled_ = false; 99 100 /** 101 * Indicates if mobile data roaming is enabled. 102 * @type {boolean} 103 * @private 104 */ 105 var enableDataRoaming_ = false; 106 107 /** 108 * Icon to use when not connected to a particular type of network. 109 * @type {!Object.<string, string>} Mapping of network type to icon data url. 110 * @private 111 */ 112 var defaultIcons_ = {}; 113 114 /** 115 * Contains the current logged in user type, which is one of 'none', 116 * 'regular', 'owner', 'guest', 'retail-mode', 'public-account', 117 * 'locally-managed', and 'kiosk-app', or empty string if the data has not 118 * been set. 119 * @type {string} 120 * @private 121 */ 122 var loggedInUserType_ = ''; 123 124 /** 125 * Create an element in the network list for controlling network 126 * connectivity. 127 * @param {Object} data Description of the network list or command. 128 * @constructor 129 */ 130 function NetworkListItem(data) { 131 var el = cr.doc.createElement('li'); 132 el.data_ = {}; 133 for (var key in data) 134 el.data_[key] = data[key]; 135 NetworkListItem.decorate(el); 136 return el; 137 } 138 139 /** 140 * Decorate an element as a NetworkListItem. 141 * @param {!Element} el The element to decorate. 142 */ 143 NetworkListItem.decorate = function(el) { 144 el.__proto__ = NetworkListItem.prototype; 145 el.decorate(); 146 }; 147 148 NetworkListItem.prototype = { 149 __proto__: ListItem.prototype, 150 151 /** 152 * Description of the network group or control. 153 * @type {Object.<string,Object>} 154 * @private 155 */ 156 data_: null, 157 158 /** 159 * Element for the control's subtitle. 160 * @type {?Element} 161 * @private 162 */ 163 subtitle_: null, 164 165 /** 166 * Icon for the network control. 167 * @type {?Element} 168 * @private 169 */ 170 icon_: null, 171 172 /** 173 * Indicates if in the process of connecting to a network. 174 * @type {boolean} 175 * @private 176 */ 177 connecting_: false, 178 179 /** 180 * Description of the network control. 181 * @type {Object} 182 */ 183 get data() { 184 return this.data_; 185 }, 186 187 /** 188 * Text label for the subtitle. 189 * @type {string} 190 */ 191 set subtitle(text) { 192 if (text) 193 this.subtitle_.textContent = text; 194 this.subtitle_.hidden = !text; 195 }, 196 197 /** 198 * URL for the network icon. 199 * @type {string} 200 */ 201 set iconURL(iconURL) { 202 this.icon_.style.backgroundImage = url(iconURL); 203 }, 204 205 /** 206 * Type of network icon. Each type corresponds to a CSS rule. 207 * @type {string} 208 */ 209 set iconType(type) { 210 if (defaultIcons_[type]) 211 this.iconURL = defaultIcons_[type]; 212 else 213 this.icon_.classList.add('network-' + type); 214 }, 215 216 /** 217 * Indicates if the network is in the process of being connected. 218 * @type {boolean} 219 */ 220 set connecting(state) { 221 this.connecting_ = state; 222 if (state) 223 this.icon_.classList.add('network-connecting'); 224 else 225 this.icon_.classList.remove('network-connecting'); 226 }, 227 228 /** 229 * Indicates if the network is in the process of being connected. 230 * @type {boolean} 231 */ 232 get connecting() { 233 return this.connecting_; 234 }, 235 236 /** 237 * Set the direction of the text. 238 * @param {string} direction The direction of the text, e.g. 'ltr'. 239 */ 240 setSubtitleDirection: function(direction) { 241 this.subtitle_.dir = direction; 242 }, 243 244 /** 245 * Indicate that the selector arrow should be shown. 246 */ 247 showSelector: function() { 248 this.subtitle_.classList.add('network-selector'); 249 }, 250 251 /** 252 * Adds an indicator to show that the network is policy managed. 253 */ 254 showManagedNetworkIndicator: function() { 255 this.appendChild(new ManagedNetworkIndicator()); 256 }, 257 258 /** @override */ 259 decorate: function() { 260 ListItem.prototype.decorate.call(this); 261 this.className = 'network-group'; 262 this.icon_ = this.ownerDocument.createElement('div'); 263 this.icon_.className = 'network-icon'; 264 this.appendChild(this.icon_); 265 var textContent = this.ownerDocument.createElement('div'); 266 textContent.className = 'network-group-labels'; 267 this.appendChild(textContent); 268 var categoryLabel = this.ownerDocument.createElement('div'); 269 var title = this.data_.key + 'Title'; 270 categoryLabel.className = 'network-title'; 271 categoryLabel.textContent = loadTimeData.getString(title); 272 textContent.appendChild(categoryLabel); 273 this.subtitle_ = this.ownerDocument.createElement('div'); 274 this.subtitle_.className = 'network-subtitle'; 275 textContent.appendChild(this.subtitle_); 276 }, 277 }; 278 279 /** 280 * Creates a control that displays a popup menu when clicked. 281 * @param {Object} data Description of the control. 282 */ 283 function NetworkMenuItem(data) { 284 var el = new NetworkListItem(data); 285 el.__proto__ = NetworkMenuItem.prototype; 286 el.decorate(); 287 return el; 288 } 289 290 NetworkMenuItem.prototype = { 291 __proto__: NetworkListItem.prototype, 292 293 /** 294 * Popup menu element. 295 * @type {?Element} 296 * @private 297 */ 298 menu_: null, 299 300 /** @override */ 301 decorate: function() { 302 this.subtitle = null; 303 if (this.data.iconType) 304 this.iconType = this.data.iconType; 305 this.addEventListener('click', function() { 306 this.showMenu(); 307 }); 308 }, 309 310 /** 311 * Retrieves the ID for the menu. 312 */ 313 getMenuName: function() { 314 return this.data_.key + '-network-menu'; 315 }, 316 317 /** 318 * Creates a popup menu for the control. 319 * @return {Element} The newly created menu. 320 */ 321 createMenu: function() { 322 if (this.data.menu) { 323 var menu = this.ownerDocument.createElement('div'); 324 menu.id = this.getMenuName(); 325 menu.className = 'network-menu'; 326 menu.hidden = true; 327 Menu.decorate(menu); 328 for (var i = 0; i < this.data.menu.length; i++) { 329 var entry = this.data.menu[i]; 330 createCallback_(menu, null, entry.label, entry.command); 331 } 332 return menu; 333 } 334 return null; 335 }, 336 337 canUpdateMenu: function() { 338 return false; 339 }, 340 341 /** 342 * Displays a popup menu. 343 */ 344 showMenu: function() { 345 var rebuild = false; 346 // Force a rescan if opening the menu for WiFi networks to ensure the 347 // list is up to date. Networks are periodically rescanned, but depending 348 // on timing, there could be an excessive delay before the first rescan 349 // unless forced. 350 var rescan = !activeMenu_ && this.data_.key == 'wifi'; 351 if (!this.menu_) { 352 rebuild = true; 353 var existing = $(this.getMenuName()); 354 if (existing) { 355 if (this.updateMenu()) 356 return; 357 closeMenu_(); 358 } 359 this.menu_ = this.createMenu(); 360 this.menu_.addEventListener('mousedown', function(e) { 361 // Prevent blurring of list, which would close the menu. 362 e.preventDefault(); 363 }); 364 var parent = $('network-menus'); 365 if (existing) 366 parent.replaceChild(this.menu_, existing); 367 else 368 parent.appendChild(this.menu_); 369 } 370 var top = this.offsetTop + this.clientHeight; 371 var menuId = this.getMenuName(); 372 if (menuId != activeMenu_ || rebuild) { 373 closeMenu_(); 374 activeMenu_ = menuId; 375 this.menu_.style.setProperty('top', top + 'px'); 376 this.menu_.hidden = false; 377 } 378 if (rescan) 379 chrome.send('refreshNetworks'); 380 }, 381 }; 382 383 /** 384 * Creates a control for selecting or configuring a network connection based 385 * on the type of connection (e.g. wifi versus vpn). 386 * @param {{key: string, 387 * networkList: Array.<Object>} data Description of the network. 388 * @constructor 389 */ 390 function NetworkSelectorItem(data) { 391 var el = new NetworkMenuItem(data); 392 el.__proto__ = NetworkSelectorItem.prototype; 393 el.decorate(); 394 return el; 395 } 396 397 NetworkSelectorItem.prototype = { 398 __proto__: NetworkMenuItem.prototype, 399 400 /** @override */ 401 decorate: function() { 402 // TODO(kevers): Generalize method of setting default label. 403 var policyManaged = false; 404 var defaultMessage = this.data_.key == 'wifi' ? 405 'networkOffline' : 'networkNotConnected'; 406 this.subtitle = loadTimeData.getString(defaultMessage); 407 var list = this.data_.networkList; 408 var candidateURL = null; 409 for (var i = 0; i < list.length; i++) { 410 var networkDetails = list[i]; 411 if (networkDetails.connecting || networkDetails.connected) { 412 this.subtitle = networkDetails.networkName; 413 this.setSubtitleDirection('ltr'); 414 policyManaged = networkDetails.policyManaged; 415 candidateURL = networkDetails.iconURL; 416 // Only break when we see a connecting network as it is possible to 417 // have a connected network and a connecting network at the same 418 // time. 419 if (networkDetails.connecting) { 420 this.connecting = true; 421 candidateURL = null; 422 break; 423 } 424 } 425 } 426 if (candidateURL) 427 this.iconURL = candidateURL; 428 else 429 this.iconType = this.data.key; 430 431 this.showSelector(); 432 433 if (policyManaged) 434 this.showManagedNetworkIndicator(); 435 436 if (activeMenu_ == this.getMenuName()) { 437 // Menu is already showing and needs to be updated. Explicitly calling 438 // show menu will force the existing menu to be replaced. The call 439 // is deferred in order to ensure that position of this element has 440 // beem properly updated. 441 var self = this; 442 setTimeout(function() {self.showMenu();}, 0); 443 } 444 }, 445 446 /** 447 * Creates a menu for selecting, configuring or disconnecting from a 448 * network. 449 * @return {Element} The newly created menu. 450 */ 451 createMenu: function() { 452 var menu = this.ownerDocument.createElement('div'); 453 menu.id = this.getMenuName(); 454 menu.className = 'network-menu'; 455 menu.hidden = true; 456 Menu.decorate(menu); 457 var addendum = []; 458 if (this.data_.key == 'wifi') { 459 addendum.push({label: loadTimeData.getString('joinOtherNetwork'), 460 command: 'add', 461 data: {networkType: Constants.TYPE_WIFI, 462 servicePath: ''}}); 463 } else if (this.data_.key == 'cellular') { 464 if (cellularEnabled_ && cellularSupportsScan_) { 465 entry = { 466 label: loadTimeData.getString('otherCellularNetworks'), 467 command: createAddConnectionCallback_(Constants.TYPE_CELLULAR), 468 addClass: ['other-cellulars'], 469 data: {} 470 }; 471 addendum.push(entry); 472 } 473 474 var label = enableDataRoaming_ ? 'disableDataRoaming' : 475 'enableDataRoaming'; 476 var disabled = loggedInUserType_ != 'owner'; 477 var entry = {label: loadTimeData.getString(label), 478 data: {}}; 479 if (disabled) { 480 entry.command = null; 481 entry.tooltip = 482 loadTimeData.getString('dataRoamingDisableToggleTooltip'); 483 } else { 484 var self = this; 485 entry.command = function() { 486 options.Preferences.setBooleanPref( 487 'cros.signed.data_roaming_enabled', 488 !enableDataRoaming_, true); 489 // Force revalidation of the menu the next time it is displayed. 490 self.menu_ = null; 491 }; 492 } 493 addendum.push(entry); 494 } 495 var list = this.data.rememberedNetworks; 496 if (list && list.length > 0) { 497 var callback = function(list) { 498 $('remembered-network-list').clear(); 499 var dialog = options.PreferredNetworks.getInstance(); 500 OptionsPage.showPageByName('preferredNetworksPage', false); 501 dialog.update(list); 502 chrome.send('coreOptionsUserMetricsAction', 503 ['Options_NetworkShowPreferred']); 504 }; 505 addendum.push({label: loadTimeData.getString('preferredNetworks'), 506 command: callback, 507 data: list}); 508 } 509 510 var networkGroup = this.ownerDocument.createElement('div'); 511 networkGroup.className = 'network-menu-group'; 512 list = this.data.networkList; 513 var empty = !list || list.length == 0; 514 if (list) { 515 for (var i = 0; i < list.length; i++) { 516 var data = list[i]; 517 this.createNetworkOptionsCallback_(networkGroup, data); 518 if (data.connected) { 519 if (data.networkType == Constants.TYPE_VPN) { 520 // Add separator 521 addendum.push({}); 522 var i18nKey = 'disconnectNetwork'; 523 addendum.push({label: loadTimeData.getString(i18nKey), 524 command: 'disconnect', 525 data: data}); 526 } 527 } 528 } 529 } 530 if (this.data_.key == 'wifi' || this.data_.key == 'wimax' || 531 this.data_.key == 'cellular') { 532 addendum.push({}); 533 if (this.data_.key == 'wifi') { 534 addendum.push({label: loadTimeData.getString('turnOffWifi'), 535 command: function() { 536 chrome.send('disableWifi'); 537 }, 538 data: {}}); 539 } else if (this.data_.key == 'wimax') { 540 addendum.push({label: loadTimeData.getString('turnOffWimax'), 541 command: function() { 542 chrome.send('disableWimax'); 543 }, 544 data: {}}); 545 } else if (this.data_.key == 'cellular') { 546 addendum.push({label: loadTimeData.getString('turnOffCellular'), 547 command: function() { 548 chrome.send('disableCellular'); 549 }, 550 data: {}}); 551 } 552 } 553 if (!empty) 554 menu.appendChild(networkGroup); 555 if (addendum.length > 0) { 556 var separator = false; 557 if (!empty) { 558 menu.appendChild(MenuItem.createSeparator()); 559 separator = true; 560 } 561 for (var i = 0; i < addendum.length; i++) { 562 var value = addendum[i]; 563 if (value.data) { 564 var item = createCallback_(menu, value.data, value.label, 565 value.command); 566 if (value.tooltip) 567 item.title = value.tooltip; 568 if (value.addClass) 569 item.classList.add(value.addClass); 570 separator = false; 571 } else if (!separator) { 572 menu.appendChild(MenuItem.createSeparator()); 573 separator = true; 574 } 575 } 576 } 577 return menu; 578 }, 579 580 /** 581 * Determines if a menu can be updated on the fly. Menus that cannot be 582 * updated are fully regenerated using createMenu. The advantage of 583 * updating a menu is that it can preserve ordering of networks avoiding 584 * entries from jumping around after an update. 585 */ 586 canUpdateMenu: function() { 587 return this.data_.key == 'wifi' && activeMenu_ == this.getMenuName(); 588 }, 589 590 /** 591 * Updates an existing menu. Updated menus preserve ordering of prior 592 * entries. During the update process, the ordering may differ from the 593 * preferred ordering as determined by the network library. If the 594 * ordering becomes potentially out of sync, then the updated menu is 595 * marked for disposal on close. Reopening the menu will force a 596 * regeneration, which will in turn fix the ordering. 597 * @return {boolean} True if successfully updated. 598 */ 599 updateMenu: function() { 600 if (!this.canUpdateMenu()) 601 return false; 602 var oldMenu = $(this.getMenuName()); 603 var group = oldMenu.getElementsByClassName('network-menu-group')[0]; 604 if (!group) 605 return false; 606 var newMenu = this.createMenu(); 607 var discardOnClose = false; 608 var oldNetworkButtons = this.extractNetworkConnectButtons_(oldMenu); 609 var newNetworkButtons = this.extractNetworkConnectButtons_(newMenu); 610 for (var key in oldNetworkButtons) { 611 if (newNetworkButtons[key]) { 612 group.replaceChild(newNetworkButtons[key].button, 613 oldNetworkButtons[key].button); 614 if (newNetworkButtons[key].index != oldNetworkButtons[key].index) 615 discardOnClose = true; 616 newNetworkButtons[key] = null; 617 } else { 618 // Leave item in list to prevent network items from jumping due to 619 // deletions. 620 oldNetworkButtons[key].disabled = true; 621 discardOnClose = true; 622 } 623 } 624 for (var key in newNetworkButtons) { 625 var entry = newNetworkButtons[key]; 626 if (entry) { 627 group.appendChild(entry.button); 628 discardOnClose = true; 629 } 630 } 631 oldMenu.data = {discardOnClose: discardOnClose}; 632 return true; 633 }, 634 635 /** 636 * Extracts a mapping of network names to menu element and position. 637 * @param {!Element} menu The menu to process. 638 * @return {Object.<string, Element>} Network mapping. 639 * @private 640 */ 641 extractNetworkConnectButtons_: function(menu) { 642 var group = menu.getElementsByClassName('network-menu-group')[0]; 643 var networkButtons = {}; 644 if (!group) 645 return networkButtons; 646 var buttons = group.getElementsByClassName('network-menu-item'); 647 for (var i = 0; i < buttons.length; i++) { 648 var label = buttons[i].data.label; 649 networkButtons[label] = {index: i, button: buttons[i]}; 650 } 651 return networkButtons; 652 }, 653 654 /** 655 * Adds a menu item for showing network details. 656 * @param {!Element} parent The parent element. 657 * @param {Object} data Description of the network. 658 * @private 659 */ 660 createNetworkOptionsCallback_: function(parent, data) { 661 var menuItem = createCallback_(parent, 662 data, 663 data.networkName, 664 'options', 665 data.iconURL); 666 if (data.policyManaged) 667 menuItem.appendChild(new ManagedNetworkIndicator()); 668 if (data.connected || data.connecting) { 669 var label = menuItem.getElementsByClassName( 670 'network-menu-item-label')[0]; 671 label.classList.add('active-network'); 672 } 673 } 674 }; 675 676 /** 677 * Creates a button-like control for configurating internet connectivity. 678 * @param {{key: string, 679 * subtitle: string, 680 * command: function} data Description of the network control. 681 * @constructor 682 */ 683 function NetworkButtonItem(data) { 684 var el = new NetworkListItem(data); 685 el.__proto__ = NetworkButtonItem.prototype; 686 el.decorate(); 687 return el; 688 } 689 690 NetworkButtonItem.prototype = { 691 __proto__: NetworkListItem.prototype, 692 693 /** @override */ 694 decorate: function() { 695 if (this.data.subtitle) 696 this.subtitle = this.data.subtitle; 697 else 698 this.subtitle = null; 699 if (this.data.command) 700 this.addEventListener('click', this.data.command); 701 if (this.data.iconURL) 702 this.iconURL = this.data.iconURL; 703 else if (this.data.iconType) 704 this.iconType = this.data.iconType; 705 if (this.data.policyManaged) 706 this.showManagedNetworkIndicator(); 707 }, 708 }; 709 710 /** 711 * Adds a command to a menu for modifying network settings. 712 * @param {!Element} menu Parent menu. 713 * @param {!Object} data Description of the network. 714 * @param {!string} label Display name for the menu item. 715 * @param {?(string|function)} command Callback function or name 716 * of the command for |networkCommand|. 717 * @param {?string=} opt_iconURL Optional URL to an icon for the menu item. 718 * @return {!Element} The created menu item. 719 * @private 720 */ 721 function createCallback_(menu, data, label, command, opt_iconURL) { 722 var button = menu.ownerDocument.createElement('div'); 723 button.className = 'network-menu-item'; 724 725 var buttonIcon = menu.ownerDocument.createElement('div'); 726 buttonIcon.className = 'network-menu-item-icon'; 727 button.appendChild(buttonIcon); 728 if (opt_iconURL) 729 buttonIcon.style.backgroundImage = url(opt_iconURL); 730 731 var buttonLabel = menu.ownerDocument.createElement('span'); 732 buttonLabel.className = 'network-menu-item-label'; 733 buttonLabel.textContent = label; 734 button.appendChild(buttonLabel); 735 var callback = null; 736 if (typeof command == 'string') { 737 var type = data.networkType; 738 var path = data.servicePath; 739 callback = function() { 740 chrome.send('networkCommand', 741 [type, path, command]); 742 closeMenu_(); 743 }; 744 } else if (command != null) { 745 if (data) { 746 callback = function() { 747 command(data); 748 closeMenu_(); 749 }; 750 } else { 751 callback = function() { 752 command(); 753 closeMenu_(); 754 }; 755 } 756 } 757 if (callback != null) 758 button.addEventListener('click', callback); 759 else 760 buttonLabel.classList.add('network-disabled-control'); 761 762 button.data = {label: label}; 763 MenuItem.decorate(button); 764 menu.appendChild(button); 765 return button; 766 } 767 768 /** 769 * A list of controls for manipulating network connectivity. 770 * @constructor 771 */ 772 var NetworkList = cr.ui.define('list'); 773 774 NetworkList.prototype = { 775 __proto__: List.prototype, 776 777 /** @override */ 778 decorate: function() { 779 List.prototype.decorate.call(this); 780 this.startBatchUpdates(); 781 this.autoExpands = true; 782 this.dataModel = new ArrayDataModel([]); 783 this.selectionModel = new ListSingleSelectionModel(); 784 this.addEventListener('blur', this.onBlur_.bind(this)); 785 this.selectionModel.addEventListener('change', 786 this.onSelectionChange_.bind(this)); 787 788 // Wi-Fi control is always visible. 789 this.update({key: 'wifi', networkList: []}); 790 791 var entryAddWifi = { 792 label: loadTimeData.getString('addConnectionWifi'), 793 command: createAddConnectionCallback_(Constants.TYPE_WIFI) 794 }; 795 var entryAddVPN = { 796 label: loadTimeData.getString('addConnectionVPN'), 797 command: createAddConnectionCallback_(Constants.TYPE_VPN) 798 }; 799 this.update({key: 'addConnection', 800 iconType: 'add-connection', 801 menu: [entryAddWifi, entryAddVPN] 802 }); 803 804 var prefs = options.Preferences.getInstance(); 805 prefs.addEventListener('cros.signed.data_roaming_enabled', 806 function(event) { 807 enableDataRoaming_ = event.value.value; 808 }); 809 this.endBatchUpdates(); 810 }, 811 812 /** 813 * When the list loses focus, unselect all items in the list and close the 814 * active menu. 815 * @private 816 */ 817 onBlur_: function() { 818 this.selectionModel.unselectAll(); 819 closeMenu_(); 820 }, 821 822 /** 823 * Close bubble and menu when a different list item is selected. 824 * @param {Event} event Event detailing the selection change. 825 * @private 826 */ 827 onSelectionChange_: function(event) { 828 OptionsPage.hideBubble(); 829 // A list item may temporarily become unselected while it is constructing 830 // its menu. The menu should therefore only be closed if a different item 831 // is selected, not when the menu's owner item is deselected. 832 if (activeMenu_) { 833 for (var i = 0; i < event.changes.length; ++i) { 834 if (event.changes[i].selected) { 835 var item = this.dataModel.item(event.changes[i].index); 836 if (!item.getMenuName || item.getMenuName() != activeMenu_) { 837 closeMenu_(); 838 return; 839 } 840 } 841 } 842 } 843 }, 844 845 /** 846 * Finds the index of a network item within the data model based on 847 * category. 848 * @param {string} key Unique key for the item in the list. 849 * @return {number} The index of the network item, or |undefined| if it is 850 * not found. 851 */ 852 indexOf: function(key) { 853 var size = this.dataModel.length; 854 for (var i = 0; i < size; i++) { 855 var entry = this.dataModel.item(i); 856 if (entry.key == key) 857 return i; 858 } 859 }, 860 861 /** 862 * Updates a network control. 863 * @param {Object.<string,string>} data Description of the entry. 864 */ 865 update: function(data) { 866 this.startBatchUpdates(); 867 var index = this.indexOf(data.key); 868 if (index == undefined) { 869 // Find reference position for adding the element. We cannot hide 870 // individual list elements, thus we need to conditionally add or 871 // remove elements and cannot rely on any element having a fixed index. 872 for (var i = 0; i < Constants.NETWORK_ORDER.length; i++) { 873 if (data.key == Constants.NETWORK_ORDER[i]) { 874 data.sortIndex = i; 875 break; 876 } 877 } 878 var referenceIndex = -1; 879 for (var i = 0; i < this.dataModel.length; i++) { 880 var entry = this.dataModel.item(i); 881 if (entry.sortIndex < data.sortIndex) 882 referenceIndex = i; 883 else 884 break; 885 } 886 if (referenceIndex == -1) { 887 // Prepend to the start of the list. 888 this.dataModel.splice(0, 0, data); 889 } else if (referenceIndex == this.dataModel.length) { 890 // Append to the end of the list. 891 this.dataModel.push(data); 892 } else { 893 // Insert after the reference element. 894 this.dataModel.splice(referenceIndex + 1, 0, data); 895 } 896 } else { 897 var entry = this.dataModel.item(index); 898 data.sortIndex = entry.sortIndex; 899 this.dataModel.splice(index, 1, data); 900 } 901 this.endBatchUpdates(); 902 }, 903 904 /** @override */ 905 createItem: function(entry) { 906 if (entry.networkList) 907 return new NetworkSelectorItem(entry); 908 if (entry.command) 909 return new NetworkButtonItem(entry); 910 if (entry.menu) 911 return new NetworkMenuItem(entry); 912 }, 913 914 /** 915 * Deletes an element from the list. 916 * @param {string} key Unique identifier for the element. 917 */ 918 deleteItem: function(key) { 919 var index = this.indexOf(key); 920 if (index != undefined) 921 this.dataModel.splice(index, 1); 922 }, 923 924 /** 925 * Updates the state of a toggle button. 926 * @param {string} key Unique identifier for the element. 927 * @param {boolean} active Whether the control is active. 928 */ 929 updateToggleControl: function(key, active) { 930 var index = this.indexOf(key); 931 if (index != undefined) { 932 var entry = this.dataModel.item(index); 933 entry.iconType = active ? 'control-active' : 934 'control-inactive'; 935 this.update(entry); 936 } 937 } 938 }; 939 940 /** 941 * Sets the default icon to use for each network type if disconnected. 942 * @param {!Object.<string, string>} data Mapping of network type to icon 943 * data url. 944 */ 945 NetworkList.setDefaultNetworkIcons = function(data) { 946 defaultIcons_ = Object.create(data); 947 }; 948 949 /** 950 * Sets the current logged in user type. 951 * @param {string} userType Current logged in user type. 952 */ 953 NetworkList.updateLoggedInUserType = function(userType) { 954 loggedInUserType_ = String(userType); 955 }; 956 957 /** 958 * Chrome callback for updating network controls. 959 * @param {Object} data Description of available network devices and their 960 * corresponding state. 961 */ 962 NetworkList.refreshNetworkData = function(data) { 963 var networkList = $('network-list'); 964 networkList.startBatchUpdates(); 965 cellularAvailable_ = data.cellularAvailable; 966 cellularEnabled_ = data.cellularEnabled; 967 cellularSupportsScan_ = data.cellularSupportsScan; 968 wimaxAvailable_ = data.wimaxAvailable; 969 wimaxEnabled_ = data.wimaxEnabled; 970 971 // Only show Ethernet control if connected. 972 var ethernetConnection = getConnection_(data.wiredList); 973 if (ethernetConnection) { 974 var type = String(Constants.TYPE_ETHERNET); 975 var path = ethernetConnection.servicePath; 976 var ethernetOptions = function() { 977 chrome.send('networkCommand', 978 [type, path, 'options']); 979 }; 980 networkList.update({key: 'ethernet', 981 subtitle: loadTimeData.getString('networkConnected'), 982 iconURL: ethernetConnection.iconURL, 983 command: ethernetOptions, 984 policyManaged: ethernetConnection.policyManaged}); 985 } else { 986 networkList.deleteItem('ethernet'); 987 } 988 989 if (data.wifiEnabled) 990 loadData_('wifi', data.wirelessList, data.rememberedList); 991 else 992 addEnableNetworkButton_('wifi', 'enableWifi', 'wifi'); 993 994 // Only show cellular control if available. 995 if (data.cellularAvailable) { 996 if (data.cellularEnabled) 997 loadData_('cellular', data.wirelessList, data.rememberedList); 998 else 999 addEnableNetworkButton_('cellular', 'enableCellular', 'cellular'); 1000 } else { 1001 networkList.deleteItem('cellular'); 1002 } 1003 1004 // Only show cellular control if available. 1005 if (data.wimaxAvailable) { 1006 if (data.wimaxEnabled) 1007 loadData_('wimax', data.wirelessList, data.rememberedList); 1008 else 1009 addEnableNetworkButton_('wimax', 'enableWimax', 'cellular'); 1010 } else { 1011 networkList.deleteItem('wimax'); 1012 } 1013 1014 // Only show VPN control if there is at least one VPN configured. 1015 if (data.vpnList.length > 0) 1016 loadData_('vpn', data.vpnList, data.rememberedList); 1017 else 1018 networkList.deleteItem('vpn'); 1019 networkList.endBatchUpdates(); 1020 }; 1021 1022 /** 1023 * Replaces a network menu with a button for reenabling the type of network. 1024 * @param {string} name The type of network (wifi, cellular or wimax). 1025 * @param {string} command The command for reenabling the network. 1026 * @param {string} type of icon (wifi or cellular). 1027 * @private 1028 */ 1029 function addEnableNetworkButton_(name, command, icon) { 1030 var subtitle = loadTimeData.getString('networkDisabled'); 1031 var enableNetwork = function() { 1032 chrome.send(command); 1033 }; 1034 var networkList = $('network-list'); 1035 networkList.update({key: name, 1036 subtitle: subtitle, 1037 iconType: icon, 1038 command: enableNetwork}); 1039 } 1040 1041 /** 1042 * Element for indicating a policy managed network. 1043 * @constructor 1044 */ 1045 function ManagedNetworkIndicator() { 1046 var el = cr.doc.createElement('span'); 1047 el.__proto__ = ManagedNetworkIndicator.prototype; 1048 el.decorate(); 1049 return el; 1050 } 1051 1052 ManagedNetworkIndicator.prototype = { 1053 __proto__: ControlledSettingIndicator.prototype, 1054 1055 /** @override */ 1056 decorate: function() { 1057 ControlledSettingIndicator.prototype.decorate.call(this); 1058 this.controlledBy = 'policy'; 1059 var policyLabel = loadTimeData.getString('managedNetwork'); 1060 this.setAttribute('textPolicy', policyLabel); 1061 this.removeAttribute('tabindex'); 1062 }, 1063 1064 /** @override */ 1065 handleEvent: function(event) { 1066 // Prevent focus blurring as that would close any currently open menu. 1067 if (event.type == 'mousedown') 1068 return; 1069 ControlledSettingIndicator.prototype.handleEvent.call(this, event); 1070 }, 1071 1072 /** 1073 * Handle mouse events received by the bubble, preventing focus blurring as 1074 * that would close any currently open menu and preventing propagation to 1075 * any elements located behind the bubble. 1076 * @param {Event} Mouse event. 1077 */ 1078 stopEvent: function(event) { 1079 event.preventDefault(); 1080 event.stopPropagation(); 1081 }, 1082 1083 /** @override */ 1084 toggleBubble_: function() { 1085 if (activeMenu_ && !$(activeMenu_).contains(this)) 1086 closeMenu_(); 1087 ControlledSettingIndicator.prototype.toggleBubble_.call(this); 1088 if (this.showingBubble) { 1089 var bubble = OptionsPage.getVisibleBubble(); 1090 bubble.addEventListener('mousedown', this.stopEvent); 1091 bubble.addEventListener('click', this.stopEvent); 1092 } 1093 }, 1094 }; 1095 1096 /** 1097 * Updates the list of available networks and their status, filtered by 1098 * network type. 1099 * @param {string} category The type of network. 1100 * @param {Array} available The list of available networks and their status. 1101 * @param {Array} remembered The list of remmebered networks. 1102 */ 1103 function loadData_(category, available, remembered) { 1104 var data = {key: category}; 1105 var type = categoryMap[category]; 1106 var availableNetworks = []; 1107 for (var i = 0; i < available.length; i++) { 1108 if (available[i].networkType == type) 1109 availableNetworks.push(available[i]); 1110 } 1111 data.networkList = availableNetworks; 1112 if (remembered) { 1113 var rememberedNetworks = []; 1114 for (var i = 0; i < remembered.length; i++) { 1115 if (remembered[i].networkType == type) 1116 rememberedNetworks.push(remembered[i]); 1117 } 1118 data.rememberedNetworks = rememberedNetworks; 1119 } 1120 $('network-list').update(data); 1121 } 1122 1123 /** 1124 * Hides the currently visible menu. 1125 * @private 1126 */ 1127 function closeMenu_() { 1128 if (activeMenu_) { 1129 var menu = $(activeMenu_); 1130 menu.hidden = true; 1131 if (menu.data && menu.data.discardOnClose) 1132 menu.parentNode.removeChild(menu); 1133 activeMenu_ = null; 1134 } 1135 } 1136 1137 /** 1138 * Fetches the active connection. 1139 * @param {Array.<Object>} networkList List of networks. 1140 * @return {boolean} True if connected or connecting to a network. 1141 * @private 1142 */ 1143 function getConnection_(networkList) { 1144 if (!networkList) 1145 return null; 1146 for (var i = 0; i < networkList.length; i++) { 1147 var entry = networkList[i]; 1148 if (entry.connected || entry.connecting) 1149 return entry; 1150 } 1151 return null; 1152 } 1153 1154 /** 1155 * Create a callback function that adds a new connection of the given type. 1156 * @param {!number} type A network type Constants.TYPE_*. 1157 * @return {function()} The created callback. 1158 * @private 1159 */ 1160 function createAddConnectionCallback_(type) { 1161 return function() { 1162 chrome.send('networkCommand', [String(type), '', 'add']); 1163 }; 1164 } 1165 1166 /** 1167 * Whether the Network list is disabled. Only used for display purpose. 1168 * @type {boolean} 1169 */ 1170 cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR); 1171 1172 // Export 1173 return { 1174 NetworkList: NetworkList 1175 }; 1176}); 1177