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
23 * ThirdPartyVPN: chrome.networkingPrivate.ThirdPartyVPNProperties
26 * @see extensions/common/api/networking_private.idl
28 var NetworkProperties;
30 /** @typedef {chrome.management.ExtensionInfo} */ var ExtensionInfo;
32 cr.define('options.network', function() {
33 var ArrayDataModel = cr.ui.ArrayDataModel;
34 var List = cr.ui.List;
35 var ListItem = cr.ui.ListItem;
36 var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
37 var Menu = cr.ui.Menu;
38 var MenuItem = cr.ui.MenuItem;
39 var ControlledSettingIndicator = options.ControlledSettingIndicator;
42 * Network settings constants. These enums usually match their C++
45 function Constants() {}
48 * Valid network type names.
50 Constants.NETWORK_TYPES = ['Ethernet', 'WiFi', 'WiMAX', 'Cellular', 'VPN'];
53 * Helper function to check whether |type| is a valid network type.
54 * @param {string} type A string that may contain a valid network type.
55 * @return {boolean} Whether the string represents a valid network type.
57 function isNetworkType(type) {
58 return (Constants.NETWORK_TYPES.indexOf(type) != -1);
62 * Order in which controls are to appear in the network list sorted by key.
64 Constants.NETWORK_ORDER = ['Ethernet',
72 * ID of the menu that is currently visible.
76 var activeMenu_ = null;
79 * The state of the cellular device or undefined if not available.
80 * @type {?chrome.networkingPrivate.DeviceStateProperties}
83 var cellularDevice_ = null;
86 * The active cellular network or null if none.
87 * @type {?NetworkProperties}
90 var cellularNetwork_ = null;
93 * The active ethernet network or null if none.
94 * @type {?NetworkProperties}
97 var ethernetNetwork_ = null;
100 * The state of the WiFi device or undefined if not available.
101 * @type {string|undefined}
104 var wifiDeviceState_ = undefined;
107 * The state of the WiMAX device or undefined if not available.
108 * @type {string|undefined}
111 var wimaxDeviceState_ = undefined;
114 * The current list of third-party VPN providers.
115 * @type {!Array<!chrome.networkingPrivate.ThirdPartyVPNProperties>}}
118 var vpnProviders_ = [];
121 * Indicates if mobile data roaming is enabled.
125 var enableDataRoaming_ = false;
128 * Returns the display name for 'network'.
129 * @param {NetworkProperties} data The network data dictionary.
131 function getNetworkName(data) {
132 if (data.Type == 'Ethernet')
133 return loadTimeData.getString('ethernetName');
134 var name = data.Name;
135 if (data.Type == 'VPN' && data.VPN && data.VPN.Type == 'ThirdPartyVPN' &&
136 data.VPN.ThirdPartyVPN) {
137 var providerName = data.VPN.ThirdPartyVPN.ProviderName;
139 return loadTimeData.getStringF('vpnNameTemplate', providerName, name);
145 * Create an element in the network list for controlling network
147 * @param {Object} data Description of the network list or command.
149 * @extends {cr.ui.ListItem}
151 function NetworkListItem(data) {
152 var el = cr.doc.createElement('li');
154 for (var key in data)
155 el.data_[key] = data[key];
156 NetworkListItem.decorate(el);
161 * @param {string} action An action to send to coreOptionsUserMetricsAction.
163 function sendChromeMetricsAction(action) {
164 chrome.send('coreOptionsUserMetricsAction', [action]);
168 * @param {string} guid The network GUID.
170 function showDetails(guid) {
171 chrome.networkingPrivate.getManagedProperties(
172 guid, DetailsInternetPage.initializeDetailsPage);
176 * Decorate an element as a NetworkListItem.
177 * @param {!Element} el The element to decorate.
179 NetworkListItem.decorate = function(el) {
180 el.__proto__ = NetworkListItem.prototype;
184 NetworkListItem.prototype = {
185 __proto__: ListItem.prototype,
188 * Description of the network group or control.
189 * @type {Object<Object>}
195 * Element for the control's subtitle.
202 * Div containing the list item icon.
209 * Description of the network control.
217 * Text label for the subtitle.
222 this.subtitle_.textContent = text;
223 this.subtitle_.hidden = !text;
227 * Sets the icon based on a network state object.
228 * @param {!NetworkProperties} data Network state properties.
231 if (!isNetworkType(data.Type))
233 var networkIcon = this.getNetworkIcon();
234 networkIcon.networkState =
235 /** @type {chrome.networkingPrivate.NetworkStateProperties} */ (data);
239 * Sets the icon based on a network type or a special type indecator, e.g.
244 if (isNetworkType(type)) {
245 var networkIcon = this.getNetworkIcon();
246 networkIcon.networkType = type;
248 // Special cases. e.g. 'add-connection'. Background images are
249 // defined in browser_options.css.
250 var oldIcon = /** @type {CrNetworkIconElement} */ (
251 this.iconDiv_.querySelector('cr-network-icon'));
253 this.iconDiv_.removeChild(oldIcon);
254 this.iconDiv_.classList.add('network-' + type.toLowerCase());
259 * Returns any existing network icon for the list item or creates a new one.
260 * @return {!CrNetworkIconElement} The network icon for the list item.
262 getNetworkIcon: function() {
263 var networkIcon = /** @type {CrNetworkIconElement} */ (
264 this.iconDiv_.querySelector('cr-network-icon'));
266 networkIcon = /** @type {!CrNetworkIconElement} */ (
267 document.createElement('cr-network-icon'));
268 networkIcon.isListItem = false;
269 this.iconDiv_.appendChild(networkIcon);
275 * Set the direction of the text.
276 * @param {string} direction The direction of the text, e.g. 'ltr'.
278 setSubtitleDirection: function(direction) {
279 this.subtitle_.dir = direction;
283 * Indicate that the selector arrow should be shown.
285 showSelector: function() {
286 this.subtitle_.classList.add('network-selector');
290 * Adds an indicator to show that the network is policy managed.
292 showManagedNetworkIndicator: function() {
293 this.appendChild(new ManagedNetworkIndicator());
297 decorate: function() {
298 ListItem.prototype.decorate.call(this);
299 this.className = 'network-group';
300 this.iconDiv_ = this.ownerDocument.createElement('div');
301 this.iconDiv_.className = 'network-icon';
302 this.appendChild(this.iconDiv_);
303 var textContent = this.ownerDocument.createElement('div');
304 textContent.className = 'network-group-labels';
305 this.appendChild(textContent);
306 var categoryLabel = this.ownerDocument.createElement('div');
308 if (this.data_.key == 'addConnection')
309 title = 'addConnectionTitle';
311 title = this.data_.key.toLowerCase() + 'Title';
312 categoryLabel.className = 'network-title';
313 categoryLabel.textContent = loadTimeData.getString(title);
314 textContent.appendChild(categoryLabel);
315 this.subtitle_ = this.ownerDocument.createElement('div');
316 this.subtitle_.className = 'network-subtitle';
317 textContent.appendChild(this.subtitle_);
322 * Creates a control that displays a popup menu when clicked.
323 * @param {Object} data Description of the control.
325 * @extends {NetworkListItem}
327 function NetworkMenuItem(data) {
328 var el = new NetworkListItem(data);
329 el.__proto__ = NetworkMenuItem.prototype;
334 NetworkMenuItem.prototype = {
335 __proto__: NetworkListItem.prototype,
338 * Popup menu element.
345 decorate: function() {
346 this.subtitle = null;
347 if (this.data.iconType)
348 this.iconType = this.data.iconType;
349 this.addEventListener('click', (function() {
355 * Retrieves the ID for the menu.
357 getMenuName: function() {
358 return this.data_.key.toLowerCase() + '-network-menu';
362 * Creates a popup menu for the control.
363 * @return {Element} The newly created menu.
365 createMenu: function() {
366 if (this.data.menu) {
367 var menu = this.ownerDocument.createElement('div');
368 menu.id = this.getMenuName();
369 menu.className = 'network-menu';
372 menu.menuItemSelector = '.network-menu-item';
373 for (var i = 0; i < this.data.menu.length; i++) {
374 var entry = this.data.menu[i];
375 createCallback_(menu, null, entry.label, entry.command);
383 * Determines if a menu can be updated on the fly. Menus that cannot be
384 * updated are fully regenerated using createMenu. The advantage of
385 * updating a menu is that it can preserve ordering of networks avoiding
386 * entries from jumping around after an update.
387 * @return {boolean} Whether the menu can be updated on the fly.
389 canUpdateMenu: function() {
394 * Removes the current menu contents, causing it to be regenerated when the
395 * menu is shown the next time. If the menu is showing right now, its
396 * contents are regenerated immediately and the menu remains visible.
398 refreshMenu: function() {
400 if (activeMenu_ == this.getMenuName())
405 * Displays a popup menu.
407 showMenu: function() {
409 // Force a rescan if opening the menu for WiFi networks to ensure the
410 // list is up to date. Networks are periodically rescanned, but depending
411 // on timing, there could be an excessive delay before the first rescan
413 var rescan = !activeMenu_ && this.data_.key == 'WiFi';
416 var existing = $(this.getMenuName());
418 if (this.canUpdateMenu() && this.updateMenu())
422 this.menu_ = this.createMenu();
423 this.menu_.addEventListener('mousedown', function(e) {
424 // Prevent blurring of list, which would close the menu.
427 var parent = $('network-menus');
429 parent.replaceChild(this.menu_, existing);
431 parent.appendChild(this.menu_);
433 var top = this.offsetTop + this.clientHeight;
434 var menuId = this.getMenuName();
435 if (menuId != activeMenu_ || rebuild) {
437 activeMenu_ = menuId;
438 this.menu_.style.setProperty('top', top + 'px');
439 this.menu_.hidden = false;
442 chrome.networkingPrivate.requestNetworkScan();
448 * Creates a control for selecting or configuring a network connection based
449 * on the type of connection (e.g. wifi versus vpn).
450 * @param {{key: string, networkList: Array<!NetworkProperties>}} data
451 * An object containing the network type (key) and an array of networks.
453 * @extends {NetworkMenuItem}
455 function NetworkSelectorItem(data) {
456 var el = new NetworkMenuItem(data);
457 el.__proto__ = NetworkSelectorItem.prototype;
463 * Returns true if |source| is a policy managed source.
464 * @param {string} source The ONC source of a network.
465 * @return {boolean} Whether |source| is a managed source.
467 function isManaged(source) {
468 return (source == 'DevicePolicy' || source == 'UserPolicy');
472 * Returns true if |network| is visible.
473 * @param {!chrome.networkingPrivate.NetworkStateProperties} network The
474 * network state properties.
475 * @return {boolean} Whether |network| is visible.
477 function networkIsVisible(network) {
478 if (network.Type == 'WiFi')
479 return !!(network.WiFi && (network.WiFi.SignalStrength > 0));
480 if (network.Type == 'WiMAX')
481 return !!(network.WiMAX && (network.WiMAX.SignalStrength > 0));
482 // Other network types are always considered 'visible'.
487 * Returns true if |cellular| is a GSM network with no sim present.
488 * @param {?chrome.networkingPrivate.DeviceStateProperties} cellularDevice
489 * @return {boolean} Whether |network| is missing a SIM card.
491 function isCellularSimAbsent(cellularDevice) {
492 return !!cellularDevice && cellularDevice.SimPresent === false;
496 * Returns true if |cellular| has a locked SIM card.
497 * @param {?chrome.networkingPrivate.DeviceStateProperties} cellularDevice
498 * @return {boolean} Whether |network| has a locked SIM card.
500 function isCellularSimLocked(cellularDevice) {
501 return !!cellularDevice && !!cellularDevice.SimLockType;
504 NetworkSelectorItem.prototype = {
505 __proto__: NetworkMenuItem.prototype,
508 decorate: function() {
509 // TODO(kevers): Generalize method of setting default label.
510 this.subtitle = loadTimeData.getString('OncConnectionStateNotConnected');
511 var list = this.data_.networkList;
512 var candidateData = null;
513 for (var i = 0; i < list.length; i++) {
514 var networkDetails = list[i];
515 if (networkDetails.ConnectionState == 'Connecting' ||
516 networkDetails.ConnectionState == 'Connected') {
517 this.subtitle = getNetworkName(networkDetails);
518 this.setSubtitleDirection('ltr');
519 candidateData = networkDetails;
520 // Only break when we see a connecting network as it is possible to
521 // have a connected network and a connecting network at the same
523 if (networkDetails.ConnectionState == 'Connecting')
528 this.iconData = candidateData;
530 this.iconType = this.data.key;
534 if (candidateData && isManaged(candidateData.Source))
535 this.showManagedNetworkIndicator();
537 if (activeMenu_ == this.getMenuName()) {
538 // Menu is already showing and needs to be updated. Explicitly calling
539 // show menu will force the existing menu to be replaced. The call
540 // is deferred in order to ensure that position of this element has
541 // beem properly updated.
543 setTimeout(function() {self.showMenu();}, 0);
548 * Creates a menu for selecting, configuring or disconnecting from a
550 * @return {!Element} The newly created menu.
552 createMenu: function() {
553 var menu = this.ownerDocument.createElement('div');
554 menu.id = this.getMenuName();
555 menu.className = 'network-menu';
558 menu.menuItemSelector = '.network-menu-item';
560 if (this.data_.key == 'WiFi') {
562 label: loadTimeData.getString('joinOtherNetwork'),
563 command: createAddNonVPNConnectionCallback_('WiFi'),
566 } else if (this.data_.key == 'Cellular') {
567 if (cellularDevice_.State == 'Enabled' &&
568 cellularNetwork_ && cellularNetwork_.Cellular &&
569 cellularNetwork_.Cellular.SupportNetworkScan) {
571 label: loadTimeData.getString('otherCellularNetworks'),
572 command: createAddNonVPNConnectionCallback_('Cellular'),
573 addClass: ['other-cellulars'],
578 var label = enableDataRoaming_ ? 'disableDataRoaming' :
580 var disabled = !loadTimeData.getValue('loggedInAsOwner');
581 var entry = {label: loadTimeData.getString(label),
584 entry.command = null;
586 loadTimeData.getString('dataRoamingDisableToggleTooltip');
589 entry.command = function() {
590 options.Preferences.setBooleanPref(
591 'cros.signed.data_roaming_enabled',
592 !enableDataRoaming_, true);
593 // Force revalidation of the menu the next time it is displayed.
597 addendum.push(entry);
598 } else if (this.data_.key == 'VPN') {
599 addendum = addendum.concat(createAddVPNConnectionEntries_());
602 var list = this.data.rememberedNetworks;
603 if (list && list.length > 0) {
604 var callback = function(list) {
605 $('remembered-network-list').clear();
606 var dialog = options.PreferredNetworks.getInstance();
607 PageManager.showPageByName('preferredNetworksPage', false);
609 sendChromeMetricsAction('Options_NetworkShowPreferred');
611 addendum.push({label: loadTimeData.getString('preferredNetworks'),
616 var networkGroup = this.ownerDocument.createElement('div');
617 networkGroup.className = 'network-menu-group';
618 list = this.data.networkList;
619 var empty = !list || list.length == 0;
621 var connectedVpnGuid = '';
622 for (var i = 0; i < list.length; i++) {
624 this.createNetworkOptionsCallback_(networkGroup, data);
625 // For VPN only, append a 'Disconnect' item to the dropdown menu.
626 if (!connectedVpnGuid && data.Type == 'VPN' &&
627 (data.ConnectionState == 'Connected' ||
628 data.ConnectionState == 'Connecting')) {
629 connectedVpnGuid = data.GUID;
632 if (connectedVpnGuid) {
633 var disconnectCallback = function() {
634 sendChromeMetricsAction('Options_NetworkDisconnectVPN');
635 chrome.networkingPrivate.startDisconnect(connectedVpnGuid);
639 addendum.push({label: loadTimeData.getString('disconnectNetwork'),
640 command: disconnectCallback,
644 if (this.data_.key == 'WiFi' || this.data_.key == 'WiMAX' ||
645 this.data_.key == 'Cellular') {
647 if (this.data_.key == 'WiFi') {
649 label: loadTimeData.getString('turnOffWifi'),
650 command: function() {
651 sendChromeMetricsAction('Options_NetworkWifiToggle');
652 chrome.networkingPrivate.disableNetworkType(
653 chrome.networkingPrivate.NetworkType.WI_FI);
656 } else if (this.data_.key == 'WiMAX') {
658 label: loadTimeData.getString('turnOffWimax'),
659 command: function() {
660 chrome.networkingPrivate.disableNetworkType(
661 chrome.networkingPrivate.NetworkType.WI_MAX);
664 } else if (this.data_.key == 'Cellular') {
666 label: loadTimeData.getString('turnOffCellular'),
667 command: function() {
668 chrome.networkingPrivate.disableNetworkType(
669 chrome.networkingPrivate.NetworkType.CELLULAR);
675 menu.appendChild(networkGroup);
676 if (addendum.length > 0) {
677 var separator = false;
679 menu.appendChild(MenuItem.createSeparator());
682 for (var i = 0; i < addendum.length; i++) {
683 var value = addendum[i];
685 var item = createCallback_(menu, value.data, value.label,
688 item.title = value.tooltip;
690 item.classList.add(value.addClass);
692 } else if (!separator) {
693 menu.appendChild(MenuItem.createSeparator());
702 canUpdateMenu: function() {
703 return this.data_.key == 'WiFi' && activeMenu_ == this.getMenuName();
707 * Updates an existing menu. Updated menus preserve ordering of prior
708 * entries. During the update process, the ordering may differ from the
709 * preferred ordering as determined by the network library. If the
710 * ordering becomes potentially out of sync, then the updated menu is
711 * marked for disposal on close. Reopening the menu will force a
712 * regeneration, which will in turn fix the ordering. This method must only
713 * be called if canUpdateMenu() returned |true|.
714 * @return {boolean} True if successfully updated.
716 updateMenu: function() {
717 var oldMenu = $(this.getMenuName());
718 var group = oldMenu.getElementsByClassName('network-menu-group')[0];
721 var newMenu = this.createMenu();
722 var discardOnClose = false;
723 var oldNetworkButtons = this.extractNetworkConnectButtons_(oldMenu);
724 var newNetworkButtons = this.extractNetworkConnectButtons_(newMenu);
725 for (var key in oldNetworkButtons) {
726 if (newNetworkButtons[key]) {
727 group.replaceChild(newNetworkButtons[key].button,
728 oldNetworkButtons[key].button);
729 if (newNetworkButtons[key].index != oldNetworkButtons[key].index)
730 discardOnClose = true;
731 newNetworkButtons[key] = null;
733 // Leave item in list to prevent network items from jumping due to
735 oldNetworkButtons[key].disabled = true;
736 discardOnClose = true;
739 for (var key in newNetworkButtons) {
740 var entry = newNetworkButtons[key];
742 group.appendChild(entry.button);
743 discardOnClose = true;
746 oldMenu.data = {discardOnClose: discardOnClose};
751 * Extracts a mapping of network names to menu element and position.
752 * @param {!Element} menu The menu to process.
753 * @return {Object<?{index: number, button: Element}>}
757 extractNetworkConnectButtons_: function(menu) {
758 var group = menu.getElementsByClassName('network-menu-group')[0];
759 var networkButtons = {};
761 return networkButtons;
762 var buttons = group.getElementsByClassName('network-menu-item');
763 for (var i = 0; i < buttons.length; i++) {
764 var label = buttons[i].data.label;
765 networkButtons[label] = {index: i, button: buttons[i]};
767 return networkButtons;
771 * Adds a menu item for showing network details.
772 * @param {!Element} parent The parent element.
773 * @param {NetworkProperties} data Description of the network.
776 createNetworkOptionsCallback_: function(parent, data) {
777 var menuItem = createCallback_(parent,
779 getNetworkName(data),
780 showDetails.bind(null, data.GUID));
781 if (isManaged(data.Source))
782 menuItem.appendChild(new ManagedNetworkIndicator());
783 if (data.ConnectionState == 'Connected' ||
784 data.ConnectionState == 'Connecting') {
785 var label = menuItem.getElementsByClassName(
786 'network-menu-item-label')[0];
787 label.classList.add('active-network');
793 * Creates a button-like control for configurating internet connectivity.
794 * @param {{key: string, subtitle: string, command: Function}} data
795 * Description of the network control.
797 * @extends {NetworkListItem}
799 function NetworkButtonItem(data) {
800 var el = new NetworkListItem(data);
801 el.__proto__ = NetworkButtonItem.prototype;
806 NetworkButtonItem.prototype = {
807 __proto__: NetworkListItem.prototype,
810 decorate: function() {
811 if (this.data.subtitle)
812 this.subtitle = this.data.subtitle;
814 this.subtitle = null;
815 if (this.data.command)
816 this.addEventListener('click', this.data.command);
817 if (this.data.iconData)
818 this.iconData = this.data.iconData;
819 else if (this.data.iconType)
820 this.iconType = this.data.iconType;
821 if (isManaged(this.data.Source))
822 this.showManagedNetworkIndicator();
827 * Adds a command to a menu for modifying network settings.
828 * @param {!Element} menu Parent menu.
829 * @param {?NetworkProperties} data Description of the network.
830 * @param {!string} label Display name for the menu item.
831 * @param {!Function} command Callback function.
832 * @return {!Element} The created menu item.
835 function createCallback_(menu, data, label, command) {
836 var button = menu.ownerDocument.createElement('div');
837 button.className = 'network-menu-item';
839 var buttonIconDiv = menu.ownerDocument.createElement('div');
840 buttonIconDiv.className = 'network-icon';
841 button.appendChild(buttonIconDiv);
842 if (data && isNetworkType(data.Type)) {
843 var networkIcon = /** @type {!CrNetworkIconElement} */ (
844 document.createElement('cr-network-icon'));
845 buttonIconDiv.appendChild(networkIcon);
846 networkIcon.isListItem = true;
847 networkIcon.networkState =
848 /** @type {chrome.networkingPrivate.NetworkStateProperties} */ (data);
851 var buttonLabel = menu.ownerDocument.createElement('span');
852 buttonLabel.className = 'network-menu-item-label';
853 buttonLabel.textContent = label;
854 button.appendChild(buttonLabel);
856 if (command != null) {
858 callback = function() {
859 (/** @type {Function} */(command))(data);
863 callback = function() {
864 (/** @type {Function} */(command))();
869 if (callback != null)
870 button.addEventListener('activate', callback);
872 buttonLabel.classList.add('network-disabled-control');
874 button.data = {label: label};
875 MenuItem.decorate(button);
876 menu.appendChild(button);
881 * A list of controls for manipulating network connectivity.
883 * @extends {cr.ui.List}
885 var NetworkList = cr.ui.define('list');
887 NetworkList.prototype = {
888 __proto__: List.prototype,
891 decorate: function() {
892 List.prototype.decorate.call(this);
893 this.startBatchUpdates();
894 this.autoExpands = true;
895 this.dataModel = new ArrayDataModel([]);
896 this.selectionModel = new ListSingleSelectionModel();
897 this.addEventListener('blur', this.onBlur_.bind(this));
898 this.selectionModel.addEventListener('change',
899 this.onSelectionChange_.bind(this));
901 // Wi-Fi control is always visible.
902 this.update({key: 'WiFi', networkList: []});
904 this.updateAddConnectionMenuEntries_();
906 var prefs = options.Preferences.getInstance();
907 prefs.addEventListener('cros.signed.data_roaming_enabled',
909 enableDataRoaming_ = event.value.value;
911 this.endBatchUpdates();
913 this.onNetworkListChanged_(); // Trigger an initial network update
915 chrome.networkingPrivate.onNetworkListChanged.addListener(
916 this.onNetworkListChanged_.bind(this));
917 chrome.networkingPrivate.onDeviceStateListChanged.addListener(
918 this.onNetworkListChanged_.bind(this));
920 chrome.management.onInstalled.addListener(
921 this.onExtensionAdded_.bind(this));
922 chrome.management.onEnabled.addListener(
923 this.onExtensionAdded_.bind(this));
924 chrome.management.onUninstalled.addListener(
925 this.onExtensionRemoved_.bind(this));
926 chrome.management.onDisabled.addListener(function(extension) {
927 this.onExtensionRemoved_(extension.id);
930 chrome.management.getAll(this.onGetAllExtensions_.bind(this));
931 chrome.networkingPrivate.requestNetworkScan();
935 * networkingPrivate event called when the network list has changed.
937 onNetworkListChanged_: function() {
938 var networkList = this;
939 chrome.networkingPrivate.getDeviceStates(function(deviceStates) {
941 networkType: chrome.networkingPrivate.NetworkType.ALL
943 chrome.networkingPrivate.getNetworks(filter, function(networkStates) {
944 networkList.updateNetworkStates(deviceStates, networkStates);
950 * chrome.management.getAll callback.
951 * @param {!Array<!ExtensionInfo>} extensions
954 onGetAllExtensions_: function(extensions) {
956 for (var extension of extensions)
957 this.addVpnProvider_(extension);
961 * If |extension| is a third-party VPN provider, add it to vpnProviders_.
962 * @param {!ExtensionInfo} extension
965 addVpnProvider_: function(extension) {
966 if (!extension.enabled ||
967 extension.permissions.indexOf('vpnProvider') == -1) {
970 // Ensure that we haven't already added this provider, e.g. if
971 // the onExtensionAdded_ callback gets invoked after onGetAllExtensions_
972 // for an extension in the returned list.
973 for (var provider of vpnProviders_) {
974 if (provider.ExtensionID == extension.id)
978 ExtensionID: extension.id,
979 ProviderName: extension.name
981 vpnProviders_.push(newProvider);
982 this.refreshVpnProviders_();
986 * chrome.management.onInstalled or onEnabled event.
987 * @param {!ExtensionInfo} extension
990 onExtensionAdded_: function(extension) {
991 this.addVpnProvider_(extension);
995 * chrome.management.onUninstalled or onDisabled event.
996 * @param {string} extensionId
999 onExtensionRemoved_: function(extensionId) {
1000 for (var i = 0; i < vpnProviders_.length; ++i) {
1001 var provider = vpnProviders_[i];
1002 if (provider.ExtensionID == extensionId) {
1003 vpnProviders_.splice(i, 1);
1004 this.refreshVpnProviders_();
1011 * Rebuilds the list of VPN providers.
1014 refreshVpnProviders_: function() {
1015 // Refresh the contents of the VPN menu.
1016 var index = this.indexOf('VPN');
1017 if (index != undefined)
1018 this.getListItemByIndex(index).refreshMenu();
1020 // Refresh the contents of the "add connection" menu.
1021 this.updateAddConnectionMenuEntries_();
1022 index = this.indexOf('addConnection');
1023 if (index != undefined)
1024 this.getListItemByIndex(index).refreshMenu();
1028 * Updates the entries in the "add connection" menu, based on the VPN
1029 * providers currently enabled in the user's profile.
1032 updateAddConnectionMenuEntries_: function() {
1034 label: loadTimeData.getString('addConnectionWifi'),
1035 command: createAddNonVPNConnectionCallback_('WiFi')
1037 entries = entries.concat(createAddVPNConnectionEntries_());
1038 this.update({key: 'addConnection',
1039 iconType: 'add-connection',
1045 * When the list loses focus, unselect all items in the list and close the
1049 onBlur_: function() {
1050 this.selectionModel.unselectAll();
1055 handleKeyDown: function(e) {
1057 // keyIdentifier does not report 'Esc' correctly
1058 if (e.keyCode == 27 /* Esc */) {
1063 if ($(activeMenu_).handleKeyDown(e)) {
1065 e.stopPropagation();
1070 if (e.keyIdentifier == 'Enter' ||
1071 e.keyIdentifier == 'U+0020' /* Space */) {
1072 var selectedListItem = this.getListItemByIndex(
1073 this.selectionModel.selectedIndex);
1074 if (selectedListItem) {
1075 selectedListItem.click();
1080 List.prototype.handleKeyDown.call(this, e);
1084 * Close bubble and menu when a different list item is selected.
1085 * @param {Event} event Event detailing the selection change.
1088 onSelectionChange_: function(event) {
1089 PageManager.hideBubble();
1090 // A list item may temporarily become unselected while it is constructing
1091 // its menu. The menu should therefore only be closed if a different item
1092 // is selected, not when the menu's owner item is deselected.
1094 for (var i = 0; i < event.changes.length; ++i) {
1095 if (event.changes[i].selected) {
1096 var item = this.dataModel.item(event.changes[i].index);
1097 if (!item.getMenuName || item.getMenuName() != activeMenu_) {
1107 * Finds the index of a network item within the data model based on
1109 * @param {string} key Unique key for the item in the list.
1110 * @return {(number|undefined)} The index of the network item, or
1111 * |undefined| if it is not found.
1113 indexOf: function(key) {
1114 var size = this.dataModel.length;
1115 for (var i = 0; i < size; i++) {
1116 var entry = this.dataModel.item(i);
1117 if (entry.key == key)
1124 * Updates a network control.
1125 * @param {Object} data Description of the entry.
1127 update: function(data) {
1128 this.startBatchUpdates();
1129 var index = this.indexOf(data.key);
1130 if (index == undefined) {
1131 // Find reference position for adding the element. We cannot hide
1132 // individual list elements, thus we need to conditionally add or
1133 // remove elements and cannot rely on any element having a fixed index.
1134 for (var i = 0; i < Constants.NETWORK_ORDER.length; i++) {
1135 if (data.key == Constants.NETWORK_ORDER[i]) {
1140 var referenceIndex = -1;
1141 for (var i = 0; i < this.dataModel.length; i++) {
1142 var entry = this.dataModel.item(i);
1143 if (entry.sortIndex < data.sortIndex)
1148 if (referenceIndex == -1) {
1149 // Prepend to the start of the list.
1150 this.dataModel.splice(0, 0, data);
1151 } else if (referenceIndex == this.dataModel.length) {
1152 // Append to the end of the list.
1153 this.dataModel.push(data);
1155 // Insert after the reference element.
1156 this.dataModel.splice(referenceIndex + 1, 0, data);
1159 var entry = this.dataModel.item(index);
1160 data.sortIndex = entry.sortIndex;
1161 this.dataModel.splice(index, 1, data);
1163 this.endBatchUpdates();
1168 * @param {Object} entry
1170 createItem: function(entry) {
1171 if (entry.networkList)
1172 return new NetworkSelectorItem(
1173 /** @type {{key: string, networkList: Array<!NetworkProperties>}} */
1176 return new NetworkButtonItem(
1177 /** @type {{key: string, subtitle: string, command: Function}} */(
1180 return new NetworkMenuItem(entry);
1185 * Deletes an element from the list.
1186 * @param {string} key Unique identifier for the element.
1188 deleteItem: function(key) {
1189 var index = this.indexOf(key);
1190 if (index != undefined)
1191 this.dataModel.splice(index, 1);
1195 * Updates the state of network devices and services.
1196 * @param {!Array<!chrome.networkingPrivate.DeviceStateProperties>}
1197 * deviceStates The result from networkingPrivate.getDeviceStates.
1198 * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>}
1199 * networkStates The result from networkingPrivate.getNetworks.
1201 updateNetworkStates: function(deviceStates, networkStates) {
1202 // Update device states.
1203 cellularDevice_ = null;
1204 wifiDeviceState_ = undefined;
1205 wimaxDeviceState_ = undefined;
1206 for (var i = 0; i < deviceStates.length; ++i) {
1207 var device = deviceStates[i];
1208 var type = device.Type;
1209 var state = device.State;
1210 if (type == 'Cellular')
1211 cellularDevice_ = cellularDevice_ || device;
1212 else if (type == 'WiFi')
1213 wifiDeviceState_ = wifiDeviceState_ || state;
1214 else if (type == 'WiMAX')
1215 wimaxDeviceState_ = wimaxDeviceState_ || state;
1218 // Update active network states.
1219 cellularNetwork_ = null;
1220 ethernetNetwork_ = null;
1221 for (var i = 0; i < networkStates.length; i++) {
1222 // Note: This cast is valid since
1223 // networkingPrivate.NetworkStateProperties is a subset of
1224 // NetworkProperties and all missing properties are optional.
1225 var entry = /** @type {NetworkProperties} */ (networkStates[i]);
1226 switch (entry.Type) {
1228 cellularNetwork_ = cellularNetwork_ || entry;
1231 // Ignore any EAP Parameters networks (which lack ConnectionState).
1232 if (entry.ConnectionState)
1233 ethernetNetwork_ = ethernetNetwork_ || entry;
1236 if (cellularNetwork_ && ethernetNetwork_)
1240 if (cellularNetwork_ && cellularNetwork_.GUID) {
1241 // Get the complete set of cellular properties which includes SIM and
1243 var networkList = this;
1244 chrome.networkingPrivate.getProperties(
1245 cellularNetwork_.GUID, function(cellular) {
1246 cellularNetwork_ = /** @type {NetworkProperties} */ (cellular);
1247 networkList.updateControls(networkStates);
1250 this.updateControls(networkStates);
1255 * Updates network controls.
1256 * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>}
1257 * networkStates The result from networkingPrivate.getNetworks.
1259 updateControls: function(networkStates) {
1260 this.startBatchUpdates();
1262 // Only show Ethernet control if available.
1263 if (ethernetNetwork_) {
1264 var ethernetOptions = showDetails.bind(null, ethernetNetwork_.GUID);
1265 var state = ethernetNetwork_.ConnectionState;
1267 if (state == 'Connected')
1268 subtitle = loadTimeData.getString('OncConnectionStateConnected');
1269 else if (state == 'Connecting')
1270 subtitle = loadTimeData.getString('OncConnectionStateConnecting');
1272 subtitle = loadTimeData.getString('OncConnectionStateNotConnected');
1276 iconData: ethernetNetwork_,
1277 command: ethernetOptions,
1278 Source: ethernetNetwork_.Source }
1281 this.deleteItem('Ethernet');
1284 if (wifiDeviceState_ == 'Enabled')
1285 loadData_('WiFi', networkStates);
1287 addEnableNetworkButton_(chrome.networkingPrivate.NetworkType.WI_FI);
1289 // Only show cellular control if available.
1290 if (cellularDevice_) {
1291 if (cellularDevice_.State == 'Enabled' &&
1292 !isCellularSimAbsent(cellularDevice_) &&
1293 !isCellularSimLocked(cellularDevice_)) {
1294 loadData_('Cellular', networkStates);
1296 addEnableNetworkButton_(
1297 chrome.networkingPrivate.NetworkType.CELLULAR);
1300 this.deleteItem('Cellular');
1303 // Only show wimax control if available. Uses cellular icons.
1304 if (wimaxDeviceState_) {
1305 if (wimaxDeviceState_ == 'Enabled')
1306 loadData_('WiMAX', networkStates);
1308 addEnableNetworkButton_(chrome.networkingPrivate.NetworkType.WI_MAX);
1310 this.deleteItem('WiMAX');
1313 // Only show VPN control if there is at least one VPN configured.
1314 if (loadData_('VPN', networkStates) == 0)
1315 this.deleteItem('VPN');
1317 this.endBatchUpdates();
1322 * Replaces a network menu with a button for enabling the network type.
1323 * @param {chrome.networkingPrivate.NetworkType} type
1326 function addEnableNetworkButton_(type) {
1327 var subtitle = loadTimeData.getString('networkDisabled');
1328 var enableNetwork = function() {
1329 if (type == chrome.networkingPrivate.NetworkType.WI_FI)
1330 sendChromeMetricsAction('Options_NetworkWifiToggle');
1331 if (type == chrome.networkingPrivate.NetworkType.CELLULAR) {
1332 if (isCellularSimLocked(cellularDevice_)) {
1333 chrome.send('simOperation', ['unlock']);
1335 } else if (isCellularSimAbsent(cellularDevice_)) {
1336 chrome.send('simOperation', ['configure']);
1340 chrome.networkingPrivate.enableNetworkType(type);
1342 $('network-list').update({key: type,
1345 command: enableNetwork});
1349 * Element for indicating a policy managed network.
1351 * @extends {options.ControlledSettingIndicator}
1353 function ManagedNetworkIndicator() {
1354 var el = cr.doc.createElement('span');
1355 el.__proto__ = ManagedNetworkIndicator.prototype;
1360 ManagedNetworkIndicator.prototype = {
1361 __proto__: ControlledSettingIndicator.prototype,
1364 decorate: function() {
1365 ControlledSettingIndicator.prototype.decorate.call(this);
1366 this.controlledBy = 'policy';
1367 var policyLabel = loadTimeData.getString('managedNetwork');
1368 this.setAttribute('textPolicy', policyLabel);
1369 this.removeAttribute('tabindex');
1373 handleEvent: function(event) {
1374 // Prevent focus blurring as that would close any currently open menu.
1375 if (event.type == 'mousedown')
1377 ControlledSettingIndicator.prototype.handleEvent.call(this, event);
1381 * Handle mouse events received by the bubble, preventing focus blurring as
1382 * that would close any currently open menu and preventing propagation to
1383 * any elements located behind the bubble.
1384 * @param {Event} event Mouse event.
1386 stopEvent: function(event) {
1387 event.preventDefault();
1388 event.stopPropagation();
1392 toggleBubble: function() {
1393 if (activeMenu_ && !$(activeMenu_).contains(this))
1395 ControlledSettingIndicator.prototype.toggleBubble.call(this);
1396 if (this.showingBubble) {
1397 var bubble = PageManager.getVisibleBubble();
1398 bubble.addEventListener('mousedown', this.stopEvent);
1399 bubble.addEventListener('click', this.stopEvent);
1405 * Updates the list of available networks and their status, filtered by
1407 * @param {string} type The type of network.
1408 * @param {Array<!chrome.networkingPrivate.NetworkStateProperties>} networks
1409 * The list of network objects.
1410 * @return {number} The number of visible networks matching |type|.
1412 function loadData_(type, networks) {
1414 var availableNetworks = [];
1415 var rememberedNetworks = [];
1416 for (var i = 0; i < networks.length; i++) {
1417 var network = networks[i];
1418 if (network.Type != type)
1420 if (networkIsVisible(network)) {
1421 availableNetworks.push(network);
1424 if ((type == 'WiFi' || type == 'VPN') && network.Source &&
1425 network.Source != 'None') {
1426 rememberedNetworks.push(network);
1431 networkList: availableNetworks,
1432 rememberedNetworks: rememberedNetworks
1434 $('network-list').update(data);
1439 * Hides the currently visible menu.
1442 function closeMenu_() {
1444 var menu = $(activeMenu_);
1446 if (menu.data && menu.data.discardOnClose)
1447 menu.parentNode.removeChild(menu);
1453 * Creates a callback function that adds a new connection of the given type.
1454 * This method may be used for all network types except VPN.
1455 * @param {string} type An ONC network type
1456 * @return {function()} The created callback.
1459 function createAddNonVPNConnectionCallback_(type) {
1462 sendChromeMetricsAction('Options_NetworkJoinOtherWifi');
1463 chrome.send('addNonVPNConnection', [type]);
1468 * Creates a callback function that shows the "add network" dialog for a VPN
1469 * provider. If |opt_extensionID| is omitted, the dialog for the built-in
1470 * OpenVPN/L2TP provider is shown. Otherwise, |opt_extensionID| identifies the
1471 * third-party provider for which the dialog should be shown.
1472 * @param {string=} opt_extensionID Extension ID identifying the third-party
1473 * VPN provider for which the dialog should be shown.
1474 * @return {function()} The created callback.
1477 function createVPNConnectionCallback_(opt_extensionID) {
1479 sendChromeMetricsAction(opt_extensionID ?
1480 'Options_NetworkAddVPNThirdParty' :
1481 'Options_NetworkAddVPNBuiltIn');
1482 chrome.send('addVPNConnection',
1483 opt_extensionID ? [opt_extensionID] : undefined);
1488 * Generates an "add network" entry for each VPN provider currently enabled in
1489 * the user's profile.
1490 * @return {!Array<{label: string, command: function(), data: !Object}>} The
1494 function createAddVPNConnectionEntries_() {
1496 for (var i = 0; i < vpnProviders_.length; ++i) {
1497 var provider = vpnProviders_[i];
1499 label: loadTimeData.getStringF('addConnectionVPNTemplate',
1500 provider.ProviderName),
1501 command: createVPNConnectionCallback_(provider.ExtensionID),
1505 // Add an entry for the built-in OpenVPN/L2TP provider.
1507 label: loadTimeData.getString('vpnBuiltInProvider'),
1508 command: createVPNConnectionCallback_(),
1515 * Whether the Network list is disabled. Only used for display purpose.
1517 cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR);
1521 NetworkList: NetworkList