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 {string|undefined}
77 var cellularDeviceState_
= undefined;
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 {?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
)
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
)
487 var simLockStatus
= cellular
.Cellular
.SIMLockStatus
;
488 return !!(simLockStatus
&& simLockStatus
.LockType
);
491 NetworkSelectorItem
.prototype = {
492 __proto__
: NetworkMenuItem
.prototype,
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
510 if (networkDetails
.ConnectionState
== 'Connecting')
515 this.iconData
= candidateData
;
517 this.iconType
= this.data
.key
;
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.
530 setTimeout(function() {self
.showMenu();}, 0);
535 * Creates a menu for selecting, configuring or disconnecting from a
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';
545 menu
.menuItemSelector
= '.network-menu-item';
547 if (this.data_
.key
== 'WiFi') {
549 label
: loadTimeData
.getString('joinOtherNetwork'),
550 command
: createAddNonVPNConnectionCallback_('WiFi'),
553 } else if (this.data_
.key
== 'Cellular') {
554 if (cellularDeviceState_
== 'Enabled' &&
555 cellularNetwork_
&& cellularNetwork_
.Cellular
&&
556 cellularNetwork_
.Cellular
.SupportNetworkScan
) {
558 label
: loadTimeData
.getString('otherCellularNetworks'),
559 command
: createAddNonVPNConnectionCallback_('Cellular'),
560 addClass
: ['other-cellulars'],
565 var label
= enableDataRoaming_
? 'disableDataRoaming' :
567 var disabled
= !loadTimeData
.getValue('loggedInAsOwner');
568 var entry
= {label
: loadTimeData
.getString(label
),
571 entry
.command
= null;
573 loadTimeData
.getString('dataRoamingDisableToggleTooltip');
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.
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);
596 sendChromeMetricsAction('Options_NetworkShowPreferred');
598 addendum
.push({label
: loadTimeData
.getString('preferredNetworks'),
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;
608 var connectedVpnGuid
= '';
609 for (var i
= 0; i
< list
.length
; 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
);
626 addendum
.push({label
: loadTimeData
.getString('disconnectNetwork'),
627 command
: disconnectCallback
,
631 if (this.data_
.key
== 'WiFi' || this.data_
.key
== 'WiMAX' ||
632 this.data_
.key
== 'Cellular') {
634 if (this.data_
.key
== 'WiFi') {
636 label
: loadTimeData
.getString('turnOffWifi'),
637 command: function() {
638 sendChromeMetricsAction('Options_NetworkWifiToggle');
639 chrome
.networkingPrivate
.disableNetworkType('WiFi');
642 } else if (this.data_
.key
== 'WiMAX') {
644 label
: loadTimeData
.getString('turnOffWimax'),
645 command: function() {
646 chrome
.networkingPrivate
.disableNetworkType('WiMAX');
649 } else if (this.data_
.key
== 'Cellular') {
651 label
: loadTimeData
.getString('turnOffCellular'),
652 command: function() {
653 chrome
.networkingPrivate
.disableNetworkType('Cellular');
659 menu
.appendChild(networkGroup
);
660 if (addendum
.length
> 0) {
661 var separator
= false;
663 menu
.appendChild(MenuItem
.createSeparator());
666 for (var i
= 0; i
< addendum
.length
; i
++) {
667 var value
= addendum
[i
];
669 var item
= createCallback_(menu
, value
.data
, value
.label
,
672 item
.title
= value
.tooltip
;
674 item
.classList
.add(value
.addClass
);
676 } else if (!separator
) {
677 menu
.appendChild(MenuItem
.createSeparator());
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];
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;
717 // Leave item in list to prevent network items from jumping due to
719 oldNetworkButtons
[key
].disabled
= true;
720 discardOnClose
= true;
723 for (var key
in newNetworkButtons
) {
724 var entry
= newNetworkButtons
[key
];
726 group
.appendChild(entry
.button
);
727 discardOnClose
= true;
730 oldMenu
.data
= {discardOnClose
: discardOnClose
};
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}>}
741 extractNetworkConnectButtons_: function(menu
) {
742 var group
= menu
.getElementsByClassName('network-menu-group')[0];
743 var networkButtons
= {};
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.
760 createNetworkOptionsCallback_: function(parent
, data
) {
761 var menuItem
= createCallback_(parent
,
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.
781 * @extends {NetworkListItem}
783 function NetworkButtonItem(data
) {
784 var el
= new NetworkListItem(data
);
785 el
.__proto__
= NetworkButtonItem
.prototype;
790 NetworkButtonItem
.prototype = {
791 __proto__
: NetworkListItem
.prototype,
794 decorate: function() {
795 if (this.data
.subtitle
)
796 this.subtitle
= this.data
.subtitle
;
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.
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
);
840 if (command
!= null) {
842 callback = function() {
843 (/** @type {Function} */(command
))(data
);
847 callback = function() {
848 (/** @type {Function} */(command
))();
853 if (callback
!= null)
854 button
.addEventListener('activate', callback
);
856 buttonLabel
.classList
.add('network-disabled-control');
858 button
.data
= {label
: label
};
859 MenuItem
.decorate(button
);
860 menu
.appendChild(button
);
865 * A list of controls for manipulating network connectivity.
867 * @extends {cr.ui.List}
869 var NetworkList
= cr
.ui
.define('list');
871 NetworkList
.prototype = {
872 __proto__
: List
.prototype,
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',
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.
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 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';
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
) {
1155 cellularNetwork_
= cellularNetwork_
|| entry
;
1158 // Ignore any EAP Parameters networks (which lack ConnectionState).
1159 if (entry
.ConnectionState
)
1160 ethernetNetwork_
= ethernetNetwork_
|| entry
;
1163 if (cellularNetwork_
&& ethernetNetwork_
)
1167 if (cellularNetwork_
&& cellularNetwork_
.GUID
) {
1168 // Get the complete set of cellular properties which includes SIM and
1170 var networkList
= this;
1171 chrome
.networkingPrivate
.getProperties(
1172 cellularNetwork_
.GUID
, function(cellular
) {
1173 cellularNetwork_
= /** @type {NetworkProperties} */ (cellular
);
1174 networkList
.updateControls(networkStates
);
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
;
1194 if (state
== 'Connected')
1195 subtitle
= loadTimeData
.getString('OncConnectionStateConnected');
1196 else if (state
== 'Connecting')
1197 subtitle
= loadTimeData
.getString('OncConnectionStateConnecting');
1199 subtitle
= loadTimeData
.getString('OncConnectionStateNotConnected');
1203 iconData
: ethernetNetwork_
,
1204 command
: ethernetOptions
,
1205 Source
: ethernetNetwork_
.Source
}
1208 this.deleteItem('Ethernet');
1211 if (wifiDeviceState_
== 'Enabled')
1212 loadData_('WiFi', networkStates
);
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
);
1223 addEnableNetworkButton_('Cellular');
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
);
1234 addEnableNetworkButton_('WiMAX');
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).
1252 function addEnableNetworkButton_(type
) {
1253 var subtitle
= loadTimeData
.getString('networkDisabled');
1254 var enableNetwork = function() {
1256 sendChromeMetricsAction('Options_NetworkWifiToggle');
1257 if (type
== 'Cellular') {
1258 if (isCellularSimLocked(cellularNetwork_
)) {
1259 chrome
.send('simOperation', ['unlock']);
1261 } else if (isCellularSimAbsent(cellularNetwork_
)) {
1262 chrome
.send('simOperation', ['configure']);
1266 chrome
.networkingPrivate
.enableNetworkType(type
);
1268 $('network-list').update({key
: type
,
1271 command
: enableNetwork
});
1275 * Element for indicating a policy managed network.
1277 * @extends {options.ControlledSettingIndicator}
1279 function ManagedNetworkIndicator() {
1280 var el
= cr
.doc
.createElement('span');
1281 el
.__proto__
= ManagedNetworkIndicator
.prototype;
1286 ManagedNetworkIndicator
.prototype = {
1287 __proto__
: ControlledSettingIndicator
.prototype,
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');
1299 handleEvent: function(event
) {
1300 // Prevent focus blurring as that would close any currently open menu.
1301 if (event
.type
== 'mousedown')
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();
1318 toggleBubble: function() {
1319 if (activeMenu_
&& !$(activeMenu_
).contains(this))
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
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
) {
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
)
1346 if (networkIsVisible(network
)) {
1347 availableNetworks
.push(network
);
1350 if ((type
== 'WiFi' || type
== 'VPN') && network
.Source
&&
1351 network
.Source
!= 'None') {
1352 rememberedNetworks
.push(network
);
1357 networkList
: availableNetworks
,
1358 rememberedNetworks
: rememberedNetworks
1360 $('network-list').update(data
);
1365 * Hides the currently visible menu.
1368 function closeMenu_() {
1370 var menu
= $(activeMenu_
);
1372 if (menu
.data
&& menu
.data
.discardOnClose
)
1373 menu
.parentNode
.removeChild(menu
);
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.
1385 function createAddNonVPNConnectionCallback_(type
) {
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.
1403 function createVPNConnectionCallback_(opt_extensionID
) {
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
1420 function createAddVPNConnectionEntries_() {
1422 var providers
= options
.VPNProviders
.getProviders();
1423 for (var i
= 0; i
< providers
.length
; ++i
) {
1425 label
: loadTimeData
.getStringF('addConnectionVPNTemplate',
1427 command
: createVPNConnectionCallback_(
1428 providers
[i
].extensionID
|| undefined),
1436 * Whether the Network list is disabled. Only used for display purpose.
1438 cr
.defineProperty(NetworkList
, 'disabled', cr
.PropertyKind
.BOOL_ATTR
);
1442 NetworkList
: NetworkList