No dual_mode on Win10+ shortcuts.
[chromium-blink-merge.git] / chrome / browser / resources / options / chromeos / network_list.js
blob018f67a98d5c490b26cbd9e7c312f622b92dc64b
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.
5 /**
6 * Partial definition of the result of networkingPrivate.getProperties()).
7 * TODO(stevenjb): Replace with chrome.networkingPrivate.NetworkStateProperties
8 * once that is fully speced.
9 * @typedef {{
10 * ConnectionState: string,
11 * Cellular: {
12 * Family: ?string,
13 * SIMPresent: ?boolean,
14 * SIMLockStatus: { LockType: ?string },
15 * SupportNetworkScan: ?boolean
16 * },
17 * GUID: string,
18 * Name: string,
19 * Source: string,
20 * Type: string
21 * }}
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;
35 /**
36 * Network settings constants. These enums usually match their C++
37 * counterparts.
39 function Constants() {}
41 /**
42 * Valid network type names.
44 Constants.NETWORK_TYPES = ['Ethernet', 'WiFi', 'WiMAX', 'Cellular', 'VPN'];
46 /**
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);
55 /**
56 * Order in which controls are to appear in the network list sorted by key.
58 Constants.NETWORK_ORDER = ['Ethernet',
59 'WiFi',
60 'WiMAX',
61 'Cellular',
62 'VPN',
63 'addConnection'];
65 /**
66 * ID of the menu that is currently visible.
67 * @type {?string}
68 * @private
70 var activeMenu_ = null;
72 /**
73 * The state of the cellular device or undefined if not available.
74 * @type {string|undefined}
75 * @private
77 var cellularDeviceState_ = undefined;
79 /**
80 * The active cellular network or null if none.
81 * @type {?NetworkProperties}
82 * @private
84 var cellularNetwork_ = null;
86 /**
87 * The active ethernet network or null if none.
88 * @type {?NetworkProperties}
89 * @private
91 var ethernetNetwork_ = null;
93 /**
94 * The state of the WiFi device or undefined if not available.
95 * @type {string|undefined}
96 * @private
98 var wifiDeviceState_ = undefined;
101 * The state of the WiMAX device or undefined if not available.
102 * @type {string|undefined}
103 * @private
105 var wimaxDeviceState_ = undefined;
108 * Indicates if mobile data roaming is enabled.
109 * @type {boolean}
110 * @private
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));
123 return data.Name;
127 * Create an element in the network list for controlling network
128 * connectivity.
129 * @param {Object} data Description of the network list or command.
130 * @constructor
131 * @extends {cr.ui.ListItem}
133 function NetworkListItem(data) {
134 var el = cr.doc.createElement('li');
135 el.data_ = {};
136 for (var key in data)
137 el.data_[key] = data[key];
138 NetworkListItem.decorate(el);
139 return 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;
163 el.decorate();
166 NetworkListItem.prototype = {
167 __proto__: ListItem.prototype,
170 * Description of the network group or control.
171 * @type {Object<Object>}
172 * @private
174 data_: null,
177 * Element for the control's subtitle.
178 * @type {?Element}
179 * @private
181 subtitle_: null,
184 * Div containing the list item icon.
185 * @type {?Element}
186 * @private
188 iconDiv_: null,
191 * Description of the network control.
192 * @type {Object}
194 get data() {
195 return this.data_;
199 * Text label for the subtitle.
200 * @type {string}
202 set subtitle(text) {
203 if (text)
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.
212 set iconData(data) {
213 if (!isNetworkType(data.Type))
214 return;
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.
222 * 'add-connection'
223 * @type {string}
225 set iconType(type) {
226 if (isNetworkType(type)) {
227 var networkIcon = this.getNetworkIcon();
228 networkIcon.networkType = type;
229 } else {
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'));
234 if (oldIcon)
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'));
247 if (!networkIcon) {
248 networkIcon = /** @type {!CrNetworkIconElement} */ (
249 document.createElement('cr-network-icon'));
250 networkIcon.isListItem = false;
251 this.iconDiv_.appendChild(networkIcon);
253 return 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());
278 /** @override */
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');
289 var title;
290 if (this.data_.key == 'addConnection')
291 title = 'addConnectionTitle';
292 else
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.
306 * @constructor
307 * @extends {NetworkListItem}
309 function NetworkMenuItem(data) {
310 var el = new NetworkListItem(data);
311 el.__proto__ = NetworkMenuItem.prototype;
312 el.decorate();
313 return el;
316 NetworkMenuItem.prototype = {
317 __proto__: NetworkListItem.prototype,
320 * Popup menu element.
321 * @type {?Element}
322 * @private
324 menu_: null,
326 /** @override */
327 decorate: function() {
328 this.subtitle = null;
329 if (this.data.iconType)
330 this.iconType = this.data.iconType;
331 this.addEventListener('click', (function() {
332 this.showMenu();
333 }).bind(this));
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';
352 menu.hidden = true;
353 Menu.decorate(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);
359 return menu;
361 return null;
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() {
372 return false;
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() {
381 this.menu_ = null;
382 if (activeMenu_ == this.getMenuName())
383 this.showMenu();
387 * Displays a popup menu.
389 showMenu: function() {
390 var rebuild = false;
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
394 // unless forced.
395 var rescan = !activeMenu_ && this.data_.key == 'WiFi';
396 if (!this.menu_) {
397 rebuild = true;
398 var existing = $(this.getMenuName());
399 if (existing) {
400 if (this.canUpdateMenu() && this.updateMenu())
401 return;
402 closeMenu_();
404 this.menu_ = this.createMenu();
405 this.menu_.addEventListener('mousedown', function(e) {
406 // Prevent blurring of list, which would close the menu.
407 e.preventDefault();
409 var parent = $('network-menus');
410 if (existing)
411 parent.replaceChild(this.menu_, existing);
412 else
413 parent.appendChild(this.menu_);
415 var top = this.offsetTop + this.clientHeight;
416 var menuId = this.getMenuName();
417 if (menuId != activeMenu_ || rebuild) {
418 closeMenu_();
419 activeMenu_ = menuId;
420 this.menu_.style.setProperty('top', top + 'px');
421 this.menu_.hidden = false;
423 if (rescan) {
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.
434 * @constructor
435 * @extends {NetworkMenuItem}
437 function NetworkSelectorItem(data) {
438 var el = new NetworkMenuItem(data);
439 el.__proto__ = NetworkSelectorItem.prototype;
440 el.decorate();
441 return el;
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'.
465 return true;
469 * Returns true if |cellular| is a GSM network with no sim present.
470 * @param {?NetworkProperties} cellular The network state properties.
471 * @return {boolean} Whether |network| is missing a SIM card.
473 function isCellularSimAbsent(cellular) {
474 if (!cellular || !cellular.Cellular)
475 return false;
476 return cellular.Cellular.Family == 'GSM' && !cellular.Cellular.SIMPresent;
480 * Returns true if |cellular| has a locked SIM card.
481 * @param {?NetworkProperties} cellular The network state properties.
482 * @return {boolean} Whether |network| has a locked SIM card.
484 function isCellularSimLocked(cellular) {
485 if (!cellular || !cellular.Cellular)
486 return false;
487 var simLockStatus = cellular.Cellular.SIMLockStatus;
488 return !!(simLockStatus && simLockStatus.LockType);
491 NetworkSelectorItem.prototype = {
492 __proto__: NetworkMenuItem.prototype,
494 /** @override */
495 decorate: function() {
496 // TODO(kevers): Generalize method of setting default label.
497 this.subtitle = loadTimeData.getString('OncConnectionStateNotConnected');
498 var list = this.data_.networkList;
499 var candidateData = null;
500 for (var i = 0; i < list.length; i++) {
501 var networkDetails = list[i];
502 if (networkDetails.ConnectionState == 'Connecting' ||
503 networkDetails.ConnectionState == 'Connected') {
504 this.subtitle = getNetworkName(networkDetails);
505 this.setSubtitleDirection('ltr');
506 candidateData = networkDetails;
507 // Only break when we see a connecting network as it is possible to
508 // have a connected network and a connecting network at the same
509 // time.
510 if (networkDetails.ConnectionState == 'Connecting')
511 break;
514 if (candidateData)
515 this.iconData = candidateData;
516 else
517 this.iconType = this.data.key;
519 this.showSelector();
521 if (candidateData && isManaged(candidateData.Source))
522 this.showManagedNetworkIndicator();
524 if (activeMenu_ == this.getMenuName()) {
525 // Menu is already showing and needs to be updated. Explicitly calling
526 // show menu will force the existing menu to be replaced. The call
527 // is deferred in order to ensure that position of this element has
528 // beem properly updated.
529 var self = this;
530 setTimeout(function() {self.showMenu();}, 0);
535 * Creates a menu for selecting, configuring or disconnecting from a
536 * network.
537 * @return {!Element} The newly created menu.
539 createMenu: function() {
540 var menu = this.ownerDocument.createElement('div');
541 menu.id = this.getMenuName();
542 menu.className = 'network-menu';
543 menu.hidden = true;
544 Menu.decorate(menu);
545 menu.menuItemSelector = '.network-menu-item';
546 var addendum = [];
547 if (this.data_.key == 'WiFi') {
548 addendum.push({
549 label: loadTimeData.getString('joinOtherNetwork'),
550 command: createAddNonVPNConnectionCallback_('WiFi'),
551 data: {}
553 } else if (this.data_.key == 'Cellular') {
554 if (cellularDeviceState_ == 'Enabled' &&
555 cellularNetwork_ && cellularNetwork_.Cellular &&
556 cellularNetwork_.Cellular.SupportNetworkScan) {
557 addendum.push({
558 label: loadTimeData.getString('otherCellularNetworks'),
559 command: createAddNonVPNConnectionCallback_('Cellular'),
560 addClass: ['other-cellulars'],
561 data: {}
565 var label = enableDataRoaming_ ? 'disableDataRoaming' :
566 'enableDataRoaming';
567 var disabled = !loadTimeData.getValue('loggedInAsOwner');
568 var entry = {label: loadTimeData.getString(label),
569 data: {}};
570 if (disabled) {
571 entry.command = null;
572 entry.tooltip =
573 loadTimeData.getString('dataRoamingDisableToggleTooltip');
574 } else {
575 var self = this;
576 entry.command = function() {
577 options.Preferences.setBooleanPref(
578 'cros.signed.data_roaming_enabled',
579 !enableDataRoaming_, true);
580 // Force revalidation of the menu the next time it is displayed.
581 self.menu_ = null;
584 addendum.push(entry);
585 } else if (this.data_.key == 'VPN') {
586 addendum = addendum.concat(createAddVPNConnectionEntries_());
589 var list = this.data.rememberedNetworks;
590 if (list && list.length > 0) {
591 var callback = function(list) {
592 $('remembered-network-list').clear();
593 var dialog = options.PreferredNetworks.getInstance();
594 PageManager.showPageByName('preferredNetworksPage', false);
595 dialog.update(list);
596 sendChromeMetricsAction('Options_NetworkShowPreferred');
598 addendum.push({label: loadTimeData.getString('preferredNetworks'),
599 command: callback,
600 data: list});
603 var networkGroup = this.ownerDocument.createElement('div');
604 networkGroup.className = 'network-menu-group';
605 list = this.data.networkList;
606 var empty = !list || list.length == 0;
607 if (list) {
608 var connectedVpnGuid = '';
609 for (var i = 0; i < list.length; i++) {
610 var data = list[i];
611 this.createNetworkOptionsCallback_(networkGroup, data);
612 // For VPN only, append a 'Disconnect' item to the dropdown menu.
613 if (!connectedVpnGuid && data.Type == 'VPN' &&
614 (data.ConnectionState == 'Connected' ||
615 data.ConnectionState == 'Connecting')) {
616 connectedVpnGuid = data.GUID;
619 if (connectedVpnGuid) {
620 var disconnectCallback = function() {
621 sendChromeMetricsAction('Options_NetworkDisconnectVPN');
622 chrome.networkingPrivate.startDisconnect(connectedVpnGuid);
624 // Add separator
625 addendum.push({});
626 addendum.push({label: loadTimeData.getString('disconnectNetwork'),
627 command: disconnectCallback,
628 data: data});
631 if (this.data_.key == 'WiFi' || this.data_.key == 'WiMAX' ||
632 this.data_.key == 'Cellular') {
633 addendum.push({});
634 if (this.data_.key == 'WiFi') {
635 addendum.push({
636 label: loadTimeData.getString('turnOffWifi'),
637 command: function() {
638 sendChromeMetricsAction('Options_NetworkWifiToggle');
639 chrome.networkingPrivate.disableNetworkType('WiFi');
641 data: {}});
642 } else if (this.data_.key == 'WiMAX') {
643 addendum.push({
644 label: loadTimeData.getString('turnOffWimax'),
645 command: function() {
646 chrome.networkingPrivate.disableNetworkType('WiMAX');
648 data: {}});
649 } else if (this.data_.key == 'Cellular') {
650 addendum.push({
651 label: loadTimeData.getString('turnOffCellular'),
652 command: function() {
653 chrome.networkingPrivate.disableNetworkType('Cellular');
655 data: {}});
658 if (!empty)
659 menu.appendChild(networkGroup);
660 if (addendum.length > 0) {
661 var separator = false;
662 if (!empty) {
663 menu.appendChild(MenuItem.createSeparator());
664 separator = true;
666 for (var i = 0; i < addendum.length; i++) {
667 var value = addendum[i];
668 if (value.data) {
669 var item = createCallback_(menu, value.data, value.label,
670 value.command);
671 if (value.tooltip)
672 item.title = value.tooltip;
673 if (value.addClass)
674 item.classList.add(value.addClass);
675 separator = false;
676 } else if (!separator) {
677 menu.appendChild(MenuItem.createSeparator());
678 separator = true;
682 return menu;
685 /** @override */
686 canUpdateMenu: function() {
687 return this.data_.key == 'WiFi' && activeMenu_ == this.getMenuName();
691 * Updates an existing menu. Updated menus preserve ordering of prior
692 * entries. During the update process, the ordering may differ from the
693 * preferred ordering as determined by the network library. If the
694 * ordering becomes potentially out of sync, then the updated menu is
695 * marked for disposal on close. Reopening the menu will force a
696 * regeneration, which will in turn fix the ordering. This method must only
697 * be called if canUpdateMenu() returned |true|.
698 * @return {boolean} True if successfully updated.
700 updateMenu: function() {
701 var oldMenu = $(this.getMenuName());
702 var group = oldMenu.getElementsByClassName('network-menu-group')[0];
703 if (!group)
704 return false;
705 var newMenu = this.createMenu();
706 var discardOnClose = false;
707 var oldNetworkButtons = this.extractNetworkConnectButtons_(oldMenu);
708 var newNetworkButtons = this.extractNetworkConnectButtons_(newMenu);
709 for (var key in oldNetworkButtons) {
710 if (newNetworkButtons[key]) {
711 group.replaceChild(newNetworkButtons[key].button,
712 oldNetworkButtons[key].button);
713 if (newNetworkButtons[key].index != oldNetworkButtons[key].index)
714 discardOnClose = true;
715 newNetworkButtons[key] = null;
716 } else {
717 // Leave item in list to prevent network items from jumping due to
718 // deletions.
719 oldNetworkButtons[key].disabled = true;
720 discardOnClose = true;
723 for (var key in newNetworkButtons) {
724 var entry = newNetworkButtons[key];
725 if (entry) {
726 group.appendChild(entry.button);
727 discardOnClose = true;
730 oldMenu.data = {discardOnClose: discardOnClose};
731 return true;
735 * Extracts a mapping of network names to menu element and position.
736 * @param {!Element} menu The menu to process.
737 * @return {Object<?{index: number, button: Element}>}
738 * Network mapping.
739 * @private
741 extractNetworkConnectButtons_: function(menu) {
742 var group = menu.getElementsByClassName('network-menu-group')[0];
743 var networkButtons = {};
744 if (!group)
745 return networkButtons;
746 var buttons = group.getElementsByClassName('network-menu-item');
747 for (var i = 0; i < buttons.length; i++) {
748 var label = buttons[i].data.label;
749 networkButtons[label] = {index: i, button: buttons[i]};
751 return networkButtons;
755 * Adds a menu item for showing network details.
756 * @param {!Element} parent The parent element.
757 * @param {NetworkProperties} data Description of the network.
758 * @private
760 createNetworkOptionsCallback_: function(parent, data) {
761 var menuItem = createCallback_(parent,
762 data,
763 getNetworkName(data),
764 showDetails.bind(null, data.GUID));
765 if (isManaged(data.Source))
766 menuItem.appendChild(new ManagedNetworkIndicator());
767 if (data.ConnectionState == 'Connected' ||
768 data.ConnectionState == 'Connecting') {
769 var label = menuItem.getElementsByClassName(
770 'network-menu-item-label')[0];
771 label.classList.add('active-network');
777 * Creates a button-like control for configurating internet connectivity.
778 * @param {{key: string, subtitle: string, command: Function}} data
779 * Description of the network control.
780 * @constructor
781 * @extends {NetworkListItem}
783 function NetworkButtonItem(data) {
784 var el = new NetworkListItem(data);
785 el.__proto__ = NetworkButtonItem.prototype;
786 el.decorate();
787 return el;
790 NetworkButtonItem.prototype = {
791 __proto__: NetworkListItem.prototype,
793 /** @override */
794 decorate: function() {
795 if (this.data.subtitle)
796 this.subtitle = this.data.subtitle;
797 else
798 this.subtitle = null;
799 if (this.data.command)
800 this.addEventListener('click', this.data.command);
801 if (this.data.iconData)
802 this.iconData = this.data.iconData;
803 else if (this.data.iconType)
804 this.iconType = this.data.iconType;
805 if (isManaged(this.data.Source))
806 this.showManagedNetworkIndicator();
811 * Adds a command to a menu for modifying network settings.
812 * @param {!Element} menu Parent menu.
813 * @param {?NetworkProperties} data Description of the network.
814 * @param {!string} label Display name for the menu item.
815 * @param {!Function} command Callback function.
816 * @return {!Element} The created menu item.
817 * @private
819 function createCallback_(menu, data, label, command) {
820 var button = menu.ownerDocument.createElement('div');
821 button.className = 'network-menu-item';
823 var buttonIconDiv = menu.ownerDocument.createElement('div');
824 buttonIconDiv.className = 'network-icon';
825 button.appendChild(buttonIconDiv);
826 if (data && isNetworkType(data.Type)) {
827 var networkIcon = /** @type {!CrNetworkIconElement} */ (
828 document.createElement('cr-network-icon'));
829 buttonIconDiv.appendChild(networkIcon);
830 networkIcon.isListItem = true;
831 networkIcon.networkState =
832 /** @type {chrome.networkingPrivate.NetworkStateProperties} */ (data);
835 var buttonLabel = menu.ownerDocument.createElement('span');
836 buttonLabel.className = 'network-menu-item-label';
837 buttonLabel.textContent = label;
838 button.appendChild(buttonLabel);
839 var callback = null;
840 if (command != null) {
841 if (data) {
842 callback = function() {
843 (/** @type {Function} */(command))(data);
844 closeMenu_();
846 } else {
847 callback = function() {
848 (/** @type {Function} */(command))();
849 closeMenu_();
853 if (callback != null)
854 button.addEventListener('activate', callback);
855 else
856 buttonLabel.classList.add('network-disabled-control');
858 button.data = {label: label};
859 MenuItem.decorate(button);
860 menu.appendChild(button);
861 return button;
865 * A list of controls for manipulating network connectivity.
866 * @constructor
867 * @extends {cr.ui.List}
869 var NetworkList = cr.ui.define('list');
871 NetworkList.prototype = {
872 __proto__: List.prototype,
874 /** @override */
875 decorate: function() {
876 List.prototype.decorate.call(this);
877 this.startBatchUpdates();
878 this.autoExpands = true;
879 this.dataModel = new ArrayDataModel([]);
880 this.selectionModel = new ListSingleSelectionModel();
881 this.addEventListener('blur', this.onBlur_.bind(this));
882 this.selectionModel.addEventListener('change',
883 this.onSelectionChange_.bind(this));
885 // Wi-Fi control is always visible.
886 this.update({key: 'WiFi', networkList: []});
888 this.updateAddConnectionMenuEntries_();
890 var prefs = options.Preferences.getInstance();
891 prefs.addEventListener('cros.signed.data_roaming_enabled',
892 function(event) {
893 enableDataRoaming_ = event.value.value;
895 this.endBatchUpdates();
897 this.onNetworkListChanged_(); // Trigger an initial network update
899 chrome.networkingPrivate.onNetworkListChanged.addListener(
900 this.onNetworkListChanged_.bind(this));
901 chrome.networkingPrivate.onDeviceStateListChanged.addListener(
902 this.onNetworkListChanged_.bind(this));
904 chrome.networkingPrivate.requestNetworkScan();
906 options.VPNProviders.addObserver(this.onVPNProvidersChanged_.bind(this));
910 * networkingPrivate event called when the network list has changed.
912 onNetworkListChanged_: function() {
913 var networkList = this;
914 chrome.networkingPrivate.getDeviceStates(function(deviceStates) {
915 var filter = { 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.
925 * @private
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.
943 * @private
945 updateAddConnectionMenuEntries_: function() {
946 var entries = [{
947 label: loadTimeData.getString('addConnectionWifi'),
948 command: createAddNonVPNConnectionCallback_('WiFi')
950 entries = entries.concat(createAddVPNConnectionEntries_());
951 this.update({key: 'addConnection',
952 iconType: 'add-connection',
953 menu: entries
958 * When the list loses focus, unselect all items in the list and close the
959 * active menu.
960 * @private
962 onBlur_: function() {
963 this.selectionModel.unselectAll();
964 closeMenu_();
967 /** @override */
968 handleKeyDown: function(e) {
969 if (activeMenu_) {
970 // keyIdentifier does not report 'Esc' correctly
971 if (e.keyCode == 27 /* Esc */) {
972 closeMenu_();
973 return;
976 if ($(activeMenu_).handleKeyDown(e)) {
977 e.preventDefault();
978 e.stopPropagation();
980 return;
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();
989 return;
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.
999 * @private
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.
1006 if (activeMenu_) {
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_) {
1011 closeMenu_();
1012 return;
1020 * Finds the index of a network item within the data model based on
1021 * category.
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)
1031 return i;
1033 return undefined;
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]) {
1049 data.sortIndex = i;
1050 break;
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)
1057 referenceIndex = i;
1058 else
1059 break;
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);
1067 } else {
1068 // Insert after the reference element.
1069 this.dataModel.splice(referenceIndex + 1, 0, data);
1071 } else {
1072 var entry = this.dataModel.item(index);
1073 data.sortIndex = entry.sortIndex;
1074 this.dataModel.splice(index, 1, data);
1076 this.endBatchUpdates();
1080 * @override
1081 * @param {Object} entry
1083 createItem: function(entry) {
1084 if (entry.networkList)
1085 return new NetworkSelectorItem(
1086 /** @type {{key: string, networkList: Array<!NetworkProperties>}} */
1087 (entry));
1088 if (entry.command)
1089 return new NetworkButtonItem(
1090 /** @type {{key: string, subtitle: string, command: Function}} */(
1091 entry));
1092 if (entry.menu)
1093 return new NetworkMenuItem(entry);
1094 assertNotReached();
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 a toggle button.
1109 * @param {string} key Unique identifier for the element.
1110 * @param {boolean} active Whether the control is active.
1112 updateToggleControl: function(key, active) {
1113 var index = this.indexOf(key);
1114 if (index != undefined) {
1115 var entry = this.dataModel.item(index);
1116 entry.iconType = active ? 'control-active' : 'control-inactive';
1117 this.update(entry);
1122 * Updates the state of network devices and services.
1123 * @param {!Array<{State: string, Type: string}>} deviceStates The result
1124 * from networkingPrivate.getDeviceStates.
1125 * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>}
1126 * networkStates The result from networkingPrivate.getNetworks.
1128 updateNetworkStates: function(deviceStates, networkStates) {
1129 // Update device states.
1130 cellularDeviceState_ = undefined;
1131 wifiDeviceState_ = undefined;
1132 wimaxDeviceState_ = undefined;
1133 for (var i = 0; i < deviceStates.length; ++i) {
1134 var device = deviceStates[i];
1135 var type = device.Type;
1136 var state = device.State;
1137 if (type == 'Cellular')
1138 cellularDeviceState_ = cellularDeviceState_ || state;
1139 else if (type == 'WiFi')
1140 wifiDeviceState_ = wifiDeviceState_ || state;
1141 else if (type == 'WiMAX')
1142 wimaxDeviceState_ = wimaxDeviceState_ || state;
1145 // Update active network states.
1146 cellularNetwork_ = null;
1147 ethernetNetwork_ = null;
1148 for (var i = 0; i < networkStates.length; i++) {
1149 // Note: This cast is valid since
1150 // networkingPrivate.NetworkStateProperties is a subset of
1151 // NetworkProperties and all missing properties are optional.
1152 var entry = /** @type {NetworkProperties} */ (networkStates[i]);
1153 switch (entry.Type) {
1154 case 'Cellular':
1155 cellularNetwork_ = cellularNetwork_ || entry;
1156 break;
1157 case 'Ethernet':
1158 // Ignore any EAP Parameters networks (which lack ConnectionState).
1159 if (entry.ConnectionState)
1160 ethernetNetwork_ = ethernetNetwork_ || entry;
1161 break;
1163 if (cellularNetwork_ && ethernetNetwork_)
1164 break;
1167 if (cellularNetwork_ && cellularNetwork_.GUID) {
1168 // Get the complete set of cellular properties which includes SIM and
1169 // Scan properties.
1170 var networkList = this;
1171 chrome.networkingPrivate.getProperties(
1172 cellularNetwork_.GUID, function(cellular) {
1173 cellularNetwork_ = /** @type {NetworkProperties} */ (cellular);
1174 networkList.updateControls(networkStates);
1176 } else {
1177 this.updateControls(networkStates);
1182 * Updates network controls.
1183 * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>}
1184 * networkStates The result from networkingPrivate.getNetworks.
1186 updateControls: function(networkStates) {
1187 this.startBatchUpdates();
1189 // Only show Ethernet control if available.
1190 if (ethernetNetwork_) {
1191 var ethernetOptions = showDetails.bind(null, ethernetNetwork_.GUID);
1192 var state = ethernetNetwork_.ConnectionState;
1193 var subtitle;
1194 if (state == 'Connected')
1195 subtitle = loadTimeData.getString('OncConnectionStateConnected');
1196 else if (state == 'Connecting')
1197 subtitle = loadTimeData.getString('OncConnectionStateConnecting');
1198 else
1199 subtitle = loadTimeData.getString('OncConnectionStateNotConnected');
1200 this.update(
1201 { key: 'Ethernet',
1202 subtitle: subtitle,
1203 iconData: ethernetNetwork_,
1204 command: ethernetOptions,
1205 Source: ethernetNetwork_.Source }
1207 } else {
1208 this.deleteItem('Ethernet');
1211 if (wifiDeviceState_ == 'Enabled')
1212 loadData_('WiFi', networkStates);
1213 else
1214 addEnableNetworkButton_('WiFi');
1216 // Only show cellular control if available.
1217 if (cellularDeviceState_) {
1218 if (cellularDeviceState_ == 'Enabled' &&
1219 !isCellularSimLocked(cellularNetwork_) &&
1220 !isCellularSimAbsent(cellularNetwork_)) {
1221 loadData_('Cellular', networkStates);
1222 } else {
1223 addEnableNetworkButton_('Cellular');
1225 } else {
1226 this.deleteItem('Cellular');
1229 // Only show wimax control if available. Uses cellular icons.
1230 if (wimaxDeviceState_) {
1231 if (wimaxDeviceState_ == 'Enabled')
1232 loadData_('WiMAX', networkStates);
1233 else
1234 addEnableNetworkButton_('WiMAX');
1235 } else {
1236 this.deleteItem('WiMAX');
1239 // Only show VPN control if there is at least one VPN configured.
1240 if (loadData_('VPN', networkStates) == 0)
1241 this.deleteItem('VPN');
1243 this.endBatchUpdates();
1248 * Replaces a network menu with a button for enabling the network type.
1249 * @param {string} type The type of network (WiFi, Cellular or Wimax).
1250 * @private
1252 function addEnableNetworkButton_(type) {
1253 var subtitle = loadTimeData.getString('networkDisabled');
1254 var enableNetwork = function() {
1255 if (type == 'WiFi')
1256 sendChromeMetricsAction('Options_NetworkWifiToggle');
1257 if (type == 'Cellular') {
1258 if (isCellularSimLocked(cellularNetwork_)) {
1259 chrome.send('simOperation', ['unlock']);
1260 return;
1261 } else if (isCellularSimAbsent(cellularNetwork_)) {
1262 chrome.send('simOperation', ['configure']);
1263 return;
1266 chrome.networkingPrivate.enableNetworkType(type);
1268 $('network-list').update({key: type,
1269 subtitle: subtitle,
1270 iconType: type,
1271 command: enableNetwork});
1275 * Element for indicating a policy managed network.
1276 * @constructor
1277 * @extends {options.ControlledSettingIndicator}
1279 function ManagedNetworkIndicator() {
1280 var el = cr.doc.createElement('span');
1281 el.__proto__ = ManagedNetworkIndicator.prototype;
1282 el.decorate();
1283 return el;
1286 ManagedNetworkIndicator.prototype = {
1287 __proto__: ControlledSettingIndicator.prototype,
1289 /** @override */
1290 decorate: function() {
1291 ControlledSettingIndicator.prototype.decorate.call(this);
1292 this.controlledBy = 'policy';
1293 var policyLabel = loadTimeData.getString('managedNetwork');
1294 this.setAttribute('textPolicy', policyLabel);
1295 this.removeAttribute('tabindex');
1298 /** @override */
1299 handleEvent: function(event) {
1300 // Prevent focus blurring as that would close any currently open menu.
1301 if (event.type == 'mousedown')
1302 return;
1303 ControlledSettingIndicator.prototype.handleEvent.call(this, event);
1307 * Handle mouse events received by the bubble, preventing focus blurring as
1308 * that would close any currently open menu and preventing propagation to
1309 * any elements located behind the bubble.
1310 * @param {Event} event Mouse event.
1312 stopEvent: function(event) {
1313 event.preventDefault();
1314 event.stopPropagation();
1317 /** @override */
1318 toggleBubble: function() {
1319 if (activeMenu_ && !$(activeMenu_).contains(this))
1320 closeMenu_();
1321 ControlledSettingIndicator.prototype.toggleBubble.call(this);
1322 if (this.showingBubble) {
1323 var bubble = PageManager.getVisibleBubble();
1324 bubble.addEventListener('mousedown', this.stopEvent);
1325 bubble.addEventListener('click', this.stopEvent);
1331 * Updates the list of available networks and their status, filtered by
1332 * network type.
1333 * @param {string} type The type of network.
1334 * @param {Array<!chrome.networkingPrivate.NetworkStateProperties>} networks
1335 * The list of network objects.
1336 * @return {number} The number of visible networks matching |type|.
1338 function loadData_(type, networks) {
1339 var res = 0;
1340 var availableNetworks = [];
1341 var rememberedNetworks = [];
1342 for (var i = 0; i < networks.length; i++) {
1343 var network = networks[i];
1344 if (network.Type != type)
1345 continue;
1346 if (networkIsVisible(network)) {
1347 availableNetworks.push(network);
1348 ++res;
1350 if ((type == 'WiFi' || type == 'VPN') && network.Source &&
1351 network.Source != 'None') {
1352 rememberedNetworks.push(network);
1355 var data = {
1356 key: type,
1357 networkList: availableNetworks,
1358 rememberedNetworks: rememberedNetworks
1360 $('network-list').update(data);
1361 return res;
1365 * Hides the currently visible menu.
1366 * @private
1368 function closeMenu_() {
1369 if (activeMenu_) {
1370 var menu = $(activeMenu_);
1371 menu.hidden = true;
1372 if (menu.data && menu.data.discardOnClose)
1373 menu.parentNode.removeChild(menu);
1374 activeMenu_ = null;
1379 * Creates a callback function that adds a new connection of the given type.
1380 * This method may be used for all network types except VPN.
1381 * @param {string} type An ONC network type
1382 * @return {function()} The created callback.
1383 * @private
1385 function createAddNonVPNConnectionCallback_(type) {
1386 return function() {
1387 if (type == 'WiFi')
1388 sendChromeMetricsAction('Options_NetworkJoinOtherWifi');
1389 chrome.send('addNonVPNConnection', [type]);
1394 * Creates a callback function that shows the "add network" dialog for a VPN
1395 * provider. If |opt_extensionID| is omitted, the dialog for the built-in
1396 * OpenVPN/L2TP provider is shown. Otherwise, |opt_extensionID| identifies the
1397 * third-party provider for which the dialog should be shown.
1398 * @param {string=} opt_extensionID Extension ID identifying the third-party
1399 * VPN provider for which the dialog should be shown.
1400 * @return {function()} The created callback.
1401 * @private
1403 function createVPNConnectionCallback_(opt_extensionID) {
1404 return function() {
1405 sendChromeMetricsAction(opt_extensionID ?
1406 'Options_NetworkAddVPNThirdParty' :
1407 'Options_NetworkAddVPNBuiltIn');
1408 chrome.send('addVPNConnection',
1409 opt_extensionID ? [opt_extensionID] : undefined);
1414 * Generates an "add network" entry for each VPN provider currently enabled in
1415 * the primary user's profile.
1416 * @return {!Array<{label: string, command: function(), data: !Object}>} The
1417 * list of entries.
1418 * @private
1420 function createAddVPNConnectionEntries_() {
1421 var entries = [];
1422 var providers = options.VPNProviders.getProviders();
1423 for (var i = 0; i < providers.length; ++i) {
1424 entries.push({
1425 label: loadTimeData.getStringF('addConnectionVPNTemplate',
1426 providers[i].name),
1427 command: createVPNConnectionCallback_(
1428 providers[i].extensionID || undefined),
1429 data: {}
1432 return entries;
1436 * Whether the Network list is disabled. Only used for display purpose.
1438 cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR);
1440 // Export
1441 return {
1442 NetworkList: NetworkList