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