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.
6 * Partial definition of the result of networkingPrivate.getProperties()).
7 * TODO(stevenjb): Replace with chrome.networkingPrivate.NetworkStateProperties
8 * once that is fully speced.
10 * ConnectionState: string,
13 * SIMPresent: ?boolean,
14 * SIMLockStatus: { LockType: ?string },
15 * SupportNetworkScan: ?boolean
22 * @see extensions/common/api/networking_private.idl
24 var NetworkProperties;
26 cr.define('options.network', function() {
27 var ArrayDataModel = cr.ui.ArrayDataModel;
28 var List = cr.ui.List;
29 var ListItem = cr.ui.ListItem;
30 var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
31 var Menu = cr.ui.Menu;
32 var MenuItem = cr.ui.MenuItem;
33 var ControlledSettingIndicator = options.ControlledSettingIndicator;
36 * Network settings constants. These enums usually match their C++
39 function Constants() {}
42 * Valid network type names.
44 Constants.NETWORK_TYPES = ['Ethernet', 'WiFi', 'WiMAX', 'Cellular', 'VPN'];
47 * Helper function to check whether |type| is a valid network type.
48 * @param {string} type A string that may contain a valid network type.
49 * @return {boolean} Whether the string represents a valid network type.
51 function isNetworkType(type) {
52 return (Constants.NETWORK_TYPES.indexOf(type) != -1);
56 * Order in which controls are to appear in the network list sorted by key.
58 Constants.NETWORK_ORDER = ['Ethernet',
66 * ID of the menu that is currently visible.
70 var activeMenu_ = null;
73 * The state of the cellular device or undefined if not available.
74 * @type {?chrome.networkingPrivate.DeviceStateProperties}
77 var cellularDevice_ = null;
80 * The active cellular network or null if none.
81 * @type {?NetworkProperties}
84 var cellularNetwork_ = null;
87 * The active ethernet network or null if none.
88 * @type {?NetworkProperties}
91 var ethernetNetwork_ = null;
94 * The state of the WiFi device or undefined if not available.
95 * @type {string|undefined}
98 var wifiDeviceState_ = undefined;
101 * The state of the WiMAX device or undefined if not available.
102 * @type {string|undefined}
105 var wimaxDeviceState_ = undefined;
108 * Indicates if mobile data roaming is enabled.
112 var enableDataRoaming_ = false;
115 * Returns the display name for 'network'.
116 * @param {NetworkProperties} data The network data dictionary.
118 function getNetworkName(data) {
119 if (data.Type == 'Ethernet')
120 return loadTimeData.getString('ethernetName');
121 if (data.Type == 'VPN')
122 return options.VPNProviders.formatNetworkName(new cr.onc.OncData(data));
127 * Create an element in the network list for controlling network
129 * @param {Object} data Description of the network list or command.
131 * @extends {cr.ui.ListItem}
133 function NetworkListItem(data) {
134 var el = cr.doc.createElement('li');
136 for (var key in data)
137 el.data_[key] = data[key];
138 NetworkListItem.decorate(el);
143 * @param {string} action An action to send to coreOptionsUserMetricsAction.
145 function sendChromeMetricsAction(action) {
146 chrome.send('coreOptionsUserMetricsAction', [action]);
150 * @param {string} guid The network GUID.
152 function showDetails(guid) {
153 chrome.networkingPrivate.getManagedProperties(
154 guid, DetailsInternetPage.initializeDetailsPage);
158 * Decorate an element as a NetworkListItem.
159 * @param {!Element} el The element to decorate.
161 NetworkListItem.decorate = function(el) {
162 el.__proto__ = NetworkListItem.prototype;
166 NetworkListItem.prototype = {
167 __proto__: ListItem.prototype,
170 * Description of the network group or control.
171 * @type {Object<Object>}
177 * Element for the control's subtitle.
184 * Div containing the list item icon.
191 * Description of the network control.
199 * Text label for the subtitle.
204 this.subtitle_.textContent = text;
205 this.subtitle_.hidden = !text;
209 * Sets the icon based on a network state object.
210 * @param {!NetworkProperties} data Network state properties.
213 if (!isNetworkType(data.Type))
215 var networkIcon = this.getNetworkIcon();
216 networkIcon.networkState =
217 /** @type {chrome.networkingPrivate.NetworkStateProperties} */ (data);
221 * Sets the icon based on a network type or a special type indecator, e.g.
226 if (isNetworkType(type)) {
227 var networkIcon = this.getNetworkIcon();
228 networkIcon.networkType = type;
230 // Special cases. e.g. 'add-connection'. Background images are
231 // defined in browser_options.css.
232 var oldIcon = /** @type {CrNetworkIconElement} */ (
233 this.iconDiv_.querySelector('cr-network-icon'));
235 this.iconDiv_.removeChild(oldIcon);
236 this.iconDiv_.classList.add('network-' + type.toLowerCase());
241 * Returns any existing network icon for the list item or creates a new one.
242 * @return {!CrNetworkIconElement} The network icon for the list item.
244 getNetworkIcon: function() {
245 var networkIcon = /** @type {CrNetworkIconElement} */ (
246 this.iconDiv_.querySelector('cr-network-icon'));
248 networkIcon = /** @type {!CrNetworkIconElement} */ (
249 document.createElement('cr-network-icon'));
250 networkIcon.isListItem = false;
251 this.iconDiv_.appendChild(networkIcon);
257 * Set the direction of the text.
258 * @param {string} direction The direction of the text, e.g. 'ltr'.
260 setSubtitleDirection: function(direction) {
261 this.subtitle_.dir = direction;
265 * Indicate that the selector arrow should be shown.
267 showSelector: function() {
268 this.subtitle_.classList.add('network-selector');
272 * Adds an indicator to show that the network is policy managed.
274 showManagedNetworkIndicator: function() {
275 this.appendChild(new ManagedNetworkIndicator());
279 decorate: function() {
280 ListItem.prototype.decorate.call(this);
281 this.className = 'network-group';
282 this.iconDiv_ = this.ownerDocument.createElement('div');
283 this.iconDiv_.className = 'network-icon';
284 this.appendChild(this.iconDiv_);
285 var textContent = this.ownerDocument.createElement('div');
286 textContent.className = 'network-group-labels';
287 this.appendChild(textContent);
288 var categoryLabel = this.ownerDocument.createElement('div');
290 if (this.data_.key == 'addConnection')
291 title = 'addConnectionTitle';
293 title = this.data_.key.toLowerCase() + 'Title';
294 categoryLabel.className = 'network-title';
295 categoryLabel.textContent = loadTimeData.getString(title);
296 textContent.appendChild(categoryLabel);
297 this.subtitle_ = this.ownerDocument.createElement('div');
298 this.subtitle_.className = 'network-subtitle';
299 textContent.appendChild(this.subtitle_);
304 * Creates a control that displays a popup menu when clicked.
305 * @param {Object} data Description of the control.
307 * @extends {NetworkListItem}
309 function NetworkMenuItem(data) {
310 var el = new NetworkListItem(data);
311 el.__proto__ = NetworkMenuItem.prototype;
316 NetworkMenuItem.prototype = {
317 __proto__: NetworkListItem.prototype,
320 * Popup menu element.
327 decorate: function() {
328 this.subtitle = null;
329 if (this.data.iconType)
330 this.iconType = this.data.iconType;
331 this.addEventListener('click', (function() {
337 * Retrieves the ID for the menu.
339 getMenuName: function() {
340 return this.data_.key.toLowerCase() + '-network-menu';
344 * Creates a popup menu for the control.
345 * @return {Element} The newly created menu.
347 createMenu: function() {
348 if (this.data.menu) {
349 var menu = this.ownerDocument.createElement('div');
350 menu.id = this.getMenuName();
351 menu.className = 'network-menu';
354 menu.menuItemSelector = '.network-menu-item';
355 for (var i = 0; i < this.data.menu.length; i++) {
356 var entry = this.data.menu[i];
357 createCallback_(menu, null, entry.label, entry.command);
365 * Determines if a menu can be updated on the fly. Menus that cannot be
366 * updated are fully regenerated using createMenu. The advantage of
367 * updating a menu is that it can preserve ordering of networks avoiding
368 * entries from jumping around after an update.
369 * @return {boolean} Whether the menu can be updated on the fly.
371 canUpdateMenu: function() {
376 * Removes the current menu contents, causing it to be regenerated when the
377 * menu is shown the next time. If the menu is showing right now, its
378 * contents are regenerated immediately and the menu remains visible.
380 refreshMenu: function() {
382 if (activeMenu_ == this.getMenuName())
387 * Displays a popup menu.
389 showMenu: function() {
391 // Force a rescan if opening the menu for WiFi networks to ensure the
392 // list is up to date. Networks are periodically rescanned, but depending
393 // on timing, there could be an excessive delay before the first rescan
395 var rescan = !activeMenu_ && this.data_.key == 'WiFi';
398 var existing = $(this.getMenuName());
400 if (this.canUpdateMenu() && this.updateMenu())
404 this.menu_ = this.createMenu();
405 this.menu_.addEventListener('mousedown', function(e) {
406 // Prevent blurring of list, which would close the menu.
409 var parent = $('network-menus');
411 parent.replaceChild(this.menu_, existing);
413 parent.appendChild(this.menu_);
415 var top = this.offsetTop + this.clientHeight;
416 var menuId = this.getMenuName();
417 if (menuId != activeMenu_ || rebuild) {
419 activeMenu_ = menuId;
420 this.menu_.style.setProperty('top', top + 'px');
421 this.menu_.hidden = false;
424 chrome.networkingPrivate.requestNetworkScan();
430 * Creates a control for selecting or configuring a network connection based
431 * on the type of connection (e.g. wifi versus vpn).
432 * @param {{key: string, networkList: Array<!NetworkProperties>}} data
433 * An object containing the network type (key) and an array of networks.
435 * @extends {NetworkMenuItem}
437 function NetworkSelectorItem(data) {
438 var el = new NetworkMenuItem(data);
439 el.__proto__ = NetworkSelectorItem.prototype;
445 * Returns true if |source| is a policy managed source.
446 * @param {string} source The ONC source of a network.
447 * @return {boolean} Whether |source| is a managed source.
449 function isManaged(source) {
450 return (source == 'DevicePolicy' || source == 'UserPolicy');
454 * Returns true if |network| is visible.
455 * @param {!chrome.networkingPrivate.NetworkStateProperties} network The
456 * network state properties.
457 * @return {boolean} Whether |network| is visible.
459 function networkIsVisible(network) {
460 if (network.Type == 'WiFi')
461 return !!(network.WiFi && (network.WiFi.SignalStrength > 0));
462 if (network.Type == 'WiMAX')
463 return !!(network.WiMAX && (network.WiMAX.SignalStrength > 0));
464 // Other network types are always considered 'visible'.
469 * Returns true if |cellular| is a GSM network with no sim present.
470 * @param {?chrome.networkingPrivate.DeviceStateProperties} cellularDevice
471 * @return {boolean} Whether |network| is missing a SIM card.
473 function isCellularSimAbsent(cellularDevice) {
474 return !!cellularDevice && cellularDevice.SimPresent === false;
478 * Returns true if |cellular| has a locked SIM card.
479 * @param {?chrome.networkingPrivate.DeviceStateProperties} cellularDevice
480 * @return {boolean} Whether |network| has a locked SIM card.
482 function isCellularSimLocked(cellularDevice) {
483 return !!cellularDevice && !!cellularDevice.SimLockType;
486 NetworkSelectorItem.prototype = {
487 __proto__: NetworkMenuItem.prototype,
490 decorate: function() {
491 // TODO(kevers): Generalize method of setting default label.
492 this.subtitle = loadTimeData.getString('OncConnectionStateNotConnected');
493 var list = this.data_.networkList;
494 var candidateData = null;
495 for (var i = 0; i < list.length; i++) {
496 var networkDetails = list[i];
497 if (networkDetails.ConnectionState == 'Connecting' ||
498 networkDetails.ConnectionState == 'Connected') {
499 this.subtitle = getNetworkName(networkDetails);
500 this.setSubtitleDirection('ltr');
501 candidateData = networkDetails;
502 // Only break when we see a connecting network as it is possible to
503 // have a connected network and a connecting network at the same
505 if (networkDetails.ConnectionState == 'Connecting')
510 this.iconData = candidateData;
512 this.iconType = this.data.key;
516 if (candidateData && isManaged(candidateData.Source))
517 this.showManagedNetworkIndicator();
519 if (activeMenu_ == this.getMenuName()) {
520 // Menu is already showing and needs to be updated. Explicitly calling
521 // show menu will force the existing menu to be replaced. The call
522 // is deferred in order to ensure that position of this element has
523 // beem properly updated.
525 setTimeout(function() {self.showMenu();}, 0);
530 * Creates a menu for selecting, configuring or disconnecting from a
532 * @return {!Element} The newly created menu.
534 createMenu: function() {
535 var menu = this.ownerDocument.createElement('div');
536 menu.id = this.getMenuName();
537 menu.className = 'network-menu';
540 menu.menuItemSelector = '.network-menu-item';
542 if (this.data_.key == 'WiFi') {
544 label: loadTimeData.getString('joinOtherNetwork'),
545 command: createAddNonVPNConnectionCallback_('WiFi'),
548 } else if (this.data_.key == 'Cellular') {
549 if (cellularDevice_.State == 'Enabled' &&
550 cellularNetwork_ && cellularNetwork_.Cellular &&
551 cellularNetwork_.Cellular.SupportNetworkScan) {
553 label: loadTimeData.getString('otherCellularNetworks'),
554 command: createAddNonVPNConnectionCallback_('Cellular'),
555 addClass: ['other-cellulars'],
560 var label = enableDataRoaming_ ? 'disableDataRoaming' :
562 var disabled = !loadTimeData.getValue('loggedInAsOwner');
563 var entry = {label: loadTimeData.getString(label),
566 entry.command = null;
568 loadTimeData.getString('dataRoamingDisableToggleTooltip');
571 entry.command = function() {
572 options.Preferences.setBooleanPref(
573 'cros.signed.data_roaming_enabled',
574 !enableDataRoaming_, true);
575 // Force revalidation of the menu the next time it is displayed.
579 addendum.push(entry);
580 } else if (this.data_.key == 'VPN') {
581 addendum = addendum.concat(createAddVPNConnectionEntries_());
584 var list = this.data.rememberedNetworks;
585 if (list && list.length > 0) {
586 var callback = function(list) {
587 $('remembered-network-list').clear();
588 var dialog = options.PreferredNetworks.getInstance();
589 PageManager.showPageByName('preferredNetworksPage', false);
591 sendChromeMetricsAction('Options_NetworkShowPreferred');
593 addendum.push({label: loadTimeData.getString('preferredNetworks'),
598 var networkGroup = this.ownerDocument.createElement('div');
599 networkGroup.className = 'network-menu-group';
600 list = this.data.networkList;
601 var empty = !list || list.length == 0;
603 var connectedVpnGuid = '';
604 for (var i = 0; i < list.length; i++) {
606 this.createNetworkOptionsCallback_(networkGroup, data);
607 // For VPN only, append a 'Disconnect' item to the dropdown menu.
608 if (!connectedVpnGuid && data.Type == 'VPN' &&
609 (data.ConnectionState == 'Connected' ||
610 data.ConnectionState == 'Connecting')) {
611 connectedVpnGuid = data.GUID;
614 if (connectedVpnGuid) {
615 var disconnectCallback = function() {
616 sendChromeMetricsAction('Options_NetworkDisconnectVPN');
617 chrome.networkingPrivate.startDisconnect(connectedVpnGuid);
621 addendum.push({label: loadTimeData.getString('disconnectNetwork'),
622 command: disconnectCallback,
626 if (this.data_.key == 'WiFi' || this.data_.key == 'WiMAX' ||
627 this.data_.key == 'Cellular') {
629 if (this.data_.key == 'WiFi') {
631 label: loadTimeData.getString('turnOffWifi'),
632 command: function() {
633 sendChromeMetricsAction('Options_NetworkWifiToggle');
634 chrome.networkingPrivate.disableNetworkType(
635 chrome.networkingPrivate.NetworkType.WI_FI);
638 } else if (this.data_.key == 'WiMAX') {
640 label: loadTimeData.getString('turnOffWimax'),
641 command: function() {
642 chrome.networkingPrivate.disableNetworkType(
643 chrome.networkingPrivate.NetworkType.WI_MAX);
646 } else if (this.data_.key == 'Cellular') {
648 label: loadTimeData.getString('turnOffCellular'),
649 command: function() {
650 chrome.networkingPrivate.disableNetworkType(
651 chrome.networkingPrivate.NetworkType.CELLULAR);
657 menu.appendChild(networkGroup);
658 if (addendum.length > 0) {
659 var separator = false;
661 menu.appendChild(MenuItem.createSeparator());
664 for (var i = 0; i < addendum.length; i++) {
665 var value = addendum[i];
667 var item = createCallback_(menu, value.data, value.label,
670 item.title = value.tooltip;
672 item.classList.add(value.addClass);
674 } else if (!separator) {
675 menu.appendChild(MenuItem.createSeparator());
684 canUpdateMenu: function() {
685 return this.data_.key == 'WiFi' && activeMenu_ == this.getMenuName();
689 * Updates an existing menu. Updated menus preserve ordering of prior
690 * entries. During the update process, the ordering may differ from the
691 * preferred ordering as determined by the network library. If the
692 * ordering becomes potentially out of sync, then the updated menu is
693 * marked for disposal on close. Reopening the menu will force a
694 * regeneration, which will in turn fix the ordering. This method must only
695 * be called if canUpdateMenu() returned |true|.
696 * @return {boolean} True if successfully updated.
698 updateMenu: function() {
699 var oldMenu = $(this.getMenuName());
700 var group = oldMenu.getElementsByClassName('network-menu-group')[0];
703 var newMenu = this.createMenu();
704 var discardOnClose = false;
705 var oldNetworkButtons = this.extractNetworkConnectButtons_(oldMenu);
706 var newNetworkButtons = this.extractNetworkConnectButtons_(newMenu);
707 for (var key in oldNetworkButtons) {
708 if (newNetworkButtons[key]) {
709 group.replaceChild(newNetworkButtons[key].button,
710 oldNetworkButtons[key].button);
711 if (newNetworkButtons[key].index != oldNetworkButtons[key].index)
712 discardOnClose = true;
713 newNetworkButtons[key] = null;
715 // Leave item in list to prevent network items from jumping due to
717 oldNetworkButtons[key].disabled = true;
718 discardOnClose = true;
721 for (var key in newNetworkButtons) {
722 var entry = newNetworkButtons[key];
724 group.appendChild(entry.button);
725 discardOnClose = true;
728 oldMenu.data = {discardOnClose: discardOnClose};
733 * Extracts a mapping of network names to menu element and position.
734 * @param {!Element} menu The menu to process.
735 * @return {Object<?{index: number, button: Element}>}
739 extractNetworkConnectButtons_: function(menu) {
740 var group = menu.getElementsByClassName('network-menu-group')[0];
741 var networkButtons = {};
743 return networkButtons;
744 var buttons = group.getElementsByClassName('network-menu-item');
745 for (var i = 0; i < buttons.length; i++) {
746 var label = buttons[i].data.label;
747 networkButtons[label] = {index: i, button: buttons[i]};
749 return networkButtons;
753 * Adds a menu item for showing network details.
754 * @param {!Element} parent The parent element.
755 * @param {NetworkProperties} data Description of the network.
758 createNetworkOptionsCallback_: function(parent, data) {
759 var menuItem = createCallback_(parent,
761 getNetworkName(data),
762 showDetails.bind(null, data.GUID));
763 if (isManaged(data.Source))
764 menuItem.appendChild(new ManagedNetworkIndicator());
765 if (data.ConnectionState == 'Connected' ||
766 data.ConnectionState == 'Connecting') {
767 var label = menuItem.getElementsByClassName(
768 'network-menu-item-label')[0];
769 label.classList.add('active-network');
775 * Creates a button-like control for configurating internet connectivity.
776 * @param {{key: string, subtitle: string, command: Function}} data
777 * Description of the network control.
779 * @extends {NetworkListItem}
781 function NetworkButtonItem(data) {
782 var el = new NetworkListItem(data);
783 el.__proto__ = NetworkButtonItem.prototype;
788 NetworkButtonItem.prototype = {
789 __proto__: NetworkListItem.prototype,
792 decorate: function() {
793 if (this.data.subtitle)
794 this.subtitle = this.data.subtitle;
796 this.subtitle = null;
797 if (this.data.command)
798 this.addEventListener('click', this.data.command);
799 if (this.data.iconData)
800 this.iconData = this.data.iconData;
801 else if (this.data.iconType)
802 this.iconType = this.data.iconType;
803 if (isManaged(this.data.Source))
804 this.showManagedNetworkIndicator();
809 * Adds a command to a menu for modifying network settings.
810 * @param {!Element} menu Parent menu.
811 * @param {?NetworkProperties} data Description of the network.
812 * @param {!string} label Display name for the menu item.
813 * @param {!Function} command Callback function.
814 * @return {!Element} The created menu item.
817 function createCallback_(menu, data, label, command) {
818 var button = menu.ownerDocument.createElement('div');
819 button.className = 'network-menu-item';
821 var buttonIconDiv = menu.ownerDocument.createElement('div');
822 buttonIconDiv.className = 'network-icon';
823 button.appendChild(buttonIconDiv);
824 if (data && isNetworkType(data.Type)) {
825 var networkIcon = /** @type {!CrNetworkIconElement} */ (
826 document.createElement('cr-network-icon'));
827 buttonIconDiv.appendChild(networkIcon);
828 networkIcon.isListItem = true;
829 networkIcon.networkState =
830 /** @type {chrome.networkingPrivate.NetworkStateProperties} */ (data);
833 var buttonLabel = menu.ownerDocument.createElement('span');
834 buttonLabel.className = 'network-menu-item-label';
835 buttonLabel.textContent = label;
836 button.appendChild(buttonLabel);
838 if (command != null) {
840 callback = function() {
841 (/** @type {Function} */(command))(data);
845 callback = function() {
846 (/** @type {Function} */(command))();
851 if (callback != null)
852 button.addEventListener('activate', callback);
854 buttonLabel.classList.add('network-disabled-control');
856 button.data = {label: label};
857 MenuItem.decorate(button);
858 menu.appendChild(button);
863 * A list of controls for manipulating network connectivity.
865 * @extends {cr.ui.List}
867 var NetworkList = cr.ui.define('list');
869 NetworkList.prototype = {
870 __proto__: List.prototype,
873 decorate: function() {
874 List.prototype.decorate.call(this);
875 this.startBatchUpdates();
876 this.autoExpands = true;
877 this.dataModel = new ArrayDataModel([]);
878 this.selectionModel = new ListSingleSelectionModel();
879 this.addEventListener('blur', this.onBlur_.bind(this));
880 this.selectionModel.addEventListener('change',
881 this.onSelectionChange_.bind(this));
883 // Wi-Fi control is always visible.
884 this.update({key: 'WiFi', networkList: []});
886 this.updateAddConnectionMenuEntries_();
888 var prefs = options.Preferences.getInstance();
889 prefs.addEventListener('cros.signed.data_roaming_enabled',
891 enableDataRoaming_ = event.value.value;
893 this.endBatchUpdates();
895 this.onNetworkListChanged_(); // Trigger an initial network update
897 chrome.networkingPrivate.onNetworkListChanged.addListener(
898 this.onNetworkListChanged_.bind(this));
899 chrome.networkingPrivate.onDeviceStateListChanged.addListener(
900 this.onNetworkListChanged_.bind(this));
902 chrome.networkingPrivate.requestNetworkScan();
904 options.VPNProviders.addObserver(this.onVPNProvidersChanged_.bind(this));
908 * networkingPrivate event called when the network list has changed.
910 onNetworkListChanged_: function() {
911 var networkList = this;
912 chrome.networkingPrivate.getDeviceStates(function(deviceStates) {
914 networkType: chrome.networkingPrivate.NetworkType.ALL
916 chrome.networkingPrivate.getNetworks(filter, function(networkStates) {
917 networkList.updateNetworkStates(deviceStates, networkStates);
923 * Called when the list of VPN providers changes. Refreshes the contents of
924 * menus that list VPN providers.
927 onVPNProvidersChanged_: function() {
928 // Refresh the contents of the VPN menu.
929 var index = this.indexOf('VPN');
930 if (index != undefined)
931 this.getListItemByIndex(index).refreshMenu();
933 // Refresh the contents of the "add connection" menu.
934 this.updateAddConnectionMenuEntries_();
935 index = this.indexOf('addConnection');
936 if (index != undefined)
937 this.getListItemByIndex(index).refreshMenu();
941 * Updates the entries in the "add connection" menu, based on the VPN
942 * providers currently enabled in the primary user's profile.
945 updateAddConnectionMenuEntries_: function() {
947 label: loadTimeData.getString('addConnectionWifi'),
948 command: createAddNonVPNConnectionCallback_('WiFi')
950 entries = entries.concat(createAddVPNConnectionEntries_());
951 this.update({key: 'addConnection',
952 iconType: 'add-connection',
958 * When the list loses focus, unselect all items in the list and close the
962 onBlur_: function() {
963 this.selectionModel.unselectAll();
968 handleKeyDown: function(e) {
970 // keyIdentifier does not report 'Esc' correctly
971 if (e.keyCode == 27 /* Esc */) {
976 if ($(activeMenu_).handleKeyDown(e)) {
983 if (e.keyIdentifier == 'Enter' ||
984 e.keyIdentifier == 'U+0020' /* Space */) {
985 var selectedListItem = this.getListItemByIndex(
986 this.selectionModel.selectedIndex);
987 if (selectedListItem) {
988 selectedListItem.click();
993 List.prototype.handleKeyDown.call(this, e);
997 * Close bubble and menu when a different list item is selected.
998 * @param {Event} event Event detailing the selection change.
1001 onSelectionChange_: function(event) {
1002 PageManager.hideBubble();
1003 // A list item may temporarily become unselected while it is constructing
1004 // its menu. The menu should therefore only be closed if a different item
1005 // is selected, not when the menu's owner item is deselected.
1007 for (var i = 0; i < event.changes.length; ++i) {
1008 if (event.changes[i].selected) {
1009 var item = this.dataModel.item(event.changes[i].index);
1010 if (!item.getMenuName || item.getMenuName() != activeMenu_) {
1020 * Finds the index of a network item within the data model based on
1022 * @param {string} key Unique key for the item in the list.
1023 * @return {(number|undefined)} The index of the network item, or
1024 * |undefined| if it is not found.
1026 indexOf: function(key) {
1027 var size = this.dataModel.length;
1028 for (var i = 0; i < size; i++) {
1029 var entry = this.dataModel.item(i);
1030 if (entry.key == key)
1037 * Updates a network control.
1038 * @param {Object} data Description of the entry.
1040 update: function(data) {
1041 this.startBatchUpdates();
1042 var index = this.indexOf(data.key);
1043 if (index == undefined) {
1044 // Find reference position for adding the element. We cannot hide
1045 // individual list elements, thus we need to conditionally add or
1046 // remove elements and cannot rely on any element having a fixed index.
1047 for (var i = 0; i < Constants.NETWORK_ORDER.length; i++) {
1048 if (data.key == Constants.NETWORK_ORDER[i]) {
1053 var referenceIndex = -1;
1054 for (var i = 0; i < this.dataModel.length; i++) {
1055 var entry = this.dataModel.item(i);
1056 if (entry.sortIndex < data.sortIndex)
1061 if (referenceIndex == -1) {
1062 // Prepend to the start of the list.
1063 this.dataModel.splice(0, 0, data);
1064 } else if (referenceIndex == this.dataModel.length) {
1065 // Append to the end of the list.
1066 this.dataModel.push(data);
1068 // Insert after the reference element.
1069 this.dataModel.splice(referenceIndex + 1, 0, data);
1072 var entry = this.dataModel.item(index);
1073 data.sortIndex = entry.sortIndex;
1074 this.dataModel.splice(index, 1, data);
1076 this.endBatchUpdates();
1081 * @param {Object} entry
1083 createItem: function(entry) {
1084 if (entry.networkList)
1085 return new NetworkSelectorItem(
1086 /** @type {{key: string, networkList: Array<!NetworkProperties>}} */
1089 return new NetworkButtonItem(
1090 /** @type {{key: string, subtitle: string, command: Function}} */(
1093 return new NetworkMenuItem(entry);
1098 * Deletes an element from the list.
1099 * @param {string} key Unique identifier for the element.
1101 deleteItem: function(key) {
1102 var index = this.indexOf(key);
1103 if (index != undefined)
1104 this.dataModel.splice(index, 1);
1108 * Updates the state of network devices and services.
1109 * @param {!Array<!chrome.networkingPrivate.DeviceStateProperties>}
1110 * deviceStates The result from networkingPrivate.getDeviceStates.
1111 * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>}
1112 * networkStates The result from networkingPrivate.getNetworks.
1114 updateNetworkStates: function(deviceStates, networkStates) {
1115 // Update device states.
1116 cellularDevice_ = null;
1117 wifiDeviceState_ = undefined;
1118 wimaxDeviceState_ = undefined;
1119 for (var i = 0; i < deviceStates.length; ++i) {
1120 var device = deviceStates[i];
1121 var type = device.Type;
1122 var state = device.State;
1123 if (type == 'Cellular')
1124 cellularDevice_ = cellularDevice_ || device;
1125 else if (type == 'WiFi')
1126 wifiDeviceState_ = wifiDeviceState_ || state;
1127 else if (type == 'WiMAX')
1128 wimaxDeviceState_ = wimaxDeviceState_ || state;
1131 // Update active network states.
1132 cellularNetwork_ = null;
1133 ethernetNetwork_ = null;
1134 for (var i = 0; i < networkStates.length; i++) {
1135 // Note: This cast is valid since
1136 // networkingPrivate.NetworkStateProperties is a subset of
1137 // NetworkProperties and all missing properties are optional.
1138 var entry = /** @type {NetworkProperties} */ (networkStates[i]);
1139 switch (entry.Type) {
1141 cellularNetwork_ = cellularNetwork_ || entry;
1144 // Ignore any EAP Parameters networks (which lack ConnectionState).
1145 if (entry.ConnectionState)
1146 ethernetNetwork_ = ethernetNetwork_ || entry;
1149 if (cellularNetwork_ && ethernetNetwork_)
1153 if (cellularNetwork_ && cellularNetwork_.GUID) {
1154 // Get the complete set of cellular properties which includes SIM and
1156 var networkList = this;
1157 chrome.networkingPrivate.getProperties(
1158 cellularNetwork_.GUID, function(cellular) {
1159 cellularNetwork_ = /** @type {NetworkProperties} */ (cellular);
1160 networkList.updateControls(networkStates);
1163 this.updateControls(networkStates);
1168 * Updates network controls.
1169 * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>}
1170 * networkStates The result from networkingPrivate.getNetworks.
1172 updateControls: function(networkStates) {
1173 this.startBatchUpdates();
1175 // Only show Ethernet control if available.
1176 if (ethernetNetwork_) {
1177 var ethernetOptions = showDetails.bind(null, ethernetNetwork_.GUID);
1178 var state = ethernetNetwork_.ConnectionState;
1180 if (state == 'Connected')
1181 subtitle = loadTimeData.getString('OncConnectionStateConnected');
1182 else if (state == 'Connecting')
1183 subtitle = loadTimeData.getString('OncConnectionStateConnecting');
1185 subtitle = loadTimeData.getString('OncConnectionStateNotConnected');
1189 iconData: ethernetNetwork_,
1190 command: ethernetOptions,
1191 Source: ethernetNetwork_.Source }
1194 this.deleteItem('Ethernet');
1197 if (wifiDeviceState_ == 'Enabled')
1198 loadData_('WiFi', networkStates);
1200 addEnableNetworkButton_(chrome.networkingPrivate.NetworkType.WI_FI);
1202 // Only show cellular control if available.
1203 if (cellularDevice_) {
1204 if (cellularDevice_.State == 'Enabled' &&
1205 !isCellularSimAbsent(cellularDevice_) &&
1206 !isCellularSimLocked(cellularDevice_)) {
1207 loadData_('Cellular', networkStates);
1209 addEnableNetworkButton_(
1210 chrome.networkingPrivate.NetworkType.CELLULAR);
1213 this.deleteItem('Cellular');
1216 // Only show wimax control if available. Uses cellular icons.
1217 if (wimaxDeviceState_) {
1218 if (wimaxDeviceState_ == 'Enabled')
1219 loadData_('WiMAX', networkStates);
1221 addEnableNetworkButton_(chrome.networkingPrivate.NetworkType.WI_MAX);
1223 this.deleteItem('WiMAX');
1226 // Only show VPN control if there is at least one VPN configured.
1227 if (loadData_('VPN', networkStates) == 0)
1228 this.deleteItem('VPN');
1230 this.endBatchUpdates();
1235 * Replaces a network menu with a button for enabling the network type.
1236 * @param {chrome.networkingPrivate.NetworkType} type
1239 function addEnableNetworkButton_(type) {
1240 var subtitle = loadTimeData.getString('networkDisabled');
1241 var enableNetwork = function() {
1242 if (type == chrome.networkingPrivate.NetworkType.WI_FI)
1243 sendChromeMetricsAction('Options_NetworkWifiToggle');
1244 if (type == chrome.networkingPrivate.NetworkType.CELLULAR) {
1245 if (isCellularSimLocked(cellularDevice_)) {
1246 chrome.send('simOperation', ['unlock']);
1248 } else if (isCellularSimAbsent(cellularDevice_)) {
1249 chrome.send('simOperation', ['configure']);
1253 chrome.networkingPrivate.enableNetworkType(type);
1255 $('network-list').update({key: type,
1258 command: enableNetwork});
1262 * Element for indicating a policy managed network.
1264 * @extends {options.ControlledSettingIndicator}
1266 function ManagedNetworkIndicator() {
1267 var el = cr.doc.createElement('span');
1268 el.__proto__ = ManagedNetworkIndicator.prototype;
1273 ManagedNetworkIndicator.prototype = {
1274 __proto__: ControlledSettingIndicator.prototype,
1277 decorate: function() {
1278 ControlledSettingIndicator.prototype.decorate.call(this);
1279 this.controlledBy = 'policy';
1280 var policyLabel = loadTimeData.getString('managedNetwork');
1281 this.setAttribute('textPolicy', policyLabel);
1282 this.removeAttribute('tabindex');
1286 handleEvent: function(event) {
1287 // Prevent focus blurring as that would close any currently open menu.
1288 if (event.type == 'mousedown')
1290 ControlledSettingIndicator.prototype.handleEvent.call(this, event);
1294 * Handle mouse events received by the bubble, preventing focus blurring as
1295 * that would close any currently open menu and preventing propagation to
1296 * any elements located behind the bubble.
1297 * @param {Event} event Mouse event.
1299 stopEvent: function(event) {
1300 event.preventDefault();
1301 event.stopPropagation();
1305 toggleBubble: function() {
1306 if (activeMenu_ && !$(activeMenu_).contains(this))
1308 ControlledSettingIndicator.prototype.toggleBubble.call(this);
1309 if (this.showingBubble) {
1310 var bubble = PageManager.getVisibleBubble();
1311 bubble.addEventListener('mousedown', this.stopEvent);
1312 bubble.addEventListener('click', this.stopEvent);
1318 * Updates the list of available networks and their status, filtered by
1320 * @param {string} type The type of network.
1321 * @param {Array<!chrome.networkingPrivate.NetworkStateProperties>} networks
1322 * The list of network objects.
1323 * @return {number} The number of visible networks matching |type|.
1325 function loadData_(type, networks) {
1327 var availableNetworks = [];
1328 var rememberedNetworks = [];
1329 for (var i = 0; i < networks.length; i++) {
1330 var network = networks[i];
1331 if (network.Type != type)
1333 if (networkIsVisible(network)) {
1334 availableNetworks.push(network);
1337 if ((type == 'WiFi' || type == 'VPN') && network.Source &&
1338 network.Source != 'None') {
1339 rememberedNetworks.push(network);
1344 networkList: availableNetworks,
1345 rememberedNetworks: rememberedNetworks
1347 $('network-list').update(data);
1352 * Hides the currently visible menu.
1355 function closeMenu_() {
1357 var menu = $(activeMenu_);
1359 if (menu.data && menu.data.discardOnClose)
1360 menu.parentNode.removeChild(menu);
1366 * Creates a callback function that adds a new connection of the given type.
1367 * This method may be used for all network types except VPN.
1368 * @param {string} type An ONC network type
1369 * @return {function()} The created callback.
1372 function createAddNonVPNConnectionCallback_(type) {
1375 sendChromeMetricsAction('Options_NetworkJoinOtherWifi');
1376 chrome.send('addNonVPNConnection', [type]);
1381 * Creates a callback function that shows the "add network" dialog for a VPN
1382 * provider. If |opt_extensionID| is omitted, the dialog for the built-in
1383 * OpenVPN/L2TP provider is shown. Otherwise, |opt_extensionID| identifies the
1384 * third-party provider for which the dialog should be shown.
1385 * @param {string=} opt_extensionID Extension ID identifying the third-party
1386 * VPN provider for which the dialog should be shown.
1387 * @return {function()} The created callback.
1390 function createVPNConnectionCallback_(opt_extensionID) {
1392 sendChromeMetricsAction(opt_extensionID ?
1393 'Options_NetworkAddVPNThirdParty' :
1394 'Options_NetworkAddVPNBuiltIn');
1395 chrome.send('addVPNConnection',
1396 opt_extensionID ? [opt_extensionID] : undefined);
1401 * Generates an "add network" entry for each VPN provider currently enabled in
1402 * the primary user's profile.
1403 * @return {!Array<{label: string, command: function(), data: !Object}>} The
1407 function createAddVPNConnectionEntries_() {
1409 var providers = options.VPNProviders.getProviders();
1410 for (var i = 0; i < providers.length; ++i) {
1412 label: loadTimeData.getStringF('addConnectionVPNTemplate',
1414 command: createVPNConnectionCallback_(
1415 providers[i].extensionID || undefined),
1423 * Whether the Network list is disabled. Only used for display purpose.
1425 cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR);
1429 NetworkList: NetworkList