Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / options / chromeos / network_list.js
blob3d81804712cf04d1db75802dbfd7413af8047113
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  *   VPN: ?{
22  *     Type: string,
23  *     ThirdPartyVPN: chrome.networkingPrivate.ThirdPartyVPNProperties
24  *   }
25  * }}
26  * @see extensions/common/api/networking_private.idl
27  */
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;
41   /**
42    * Network settings constants. These enums usually match their C++
43    * counterparts.
44    */
45   function Constants() {}
47   /**
48    * Valid network type names.
49    */
50   Constants.NETWORK_TYPES = ['Ethernet', 'WiFi', 'WiMAX', 'Cellular', 'VPN'];
52   /**
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.
56    */
57   function isNetworkType(type) {
58     return (Constants.NETWORK_TYPES.indexOf(type) != -1);
59   }
61   /**
62    * Order in which controls are to appear in the network list sorted by key.
63    */
64   Constants.NETWORK_ORDER = ['Ethernet',
65                              'WiFi',
66                              'WiMAX',
67                              'Cellular',
68                              'VPN',
69                              'addConnection'];
71   /**
72    * ID of the menu that is currently visible.
73    * @type {?string}
74    * @private
75    */
76   var activeMenu_ = null;
78   /**
79    * The state of the cellular device or undefined if not available.
80    * @type {?chrome.networkingPrivate.DeviceStateProperties}
81    * @private
82    */
83   var cellularDevice_ = null;
85   /**
86    * The active cellular network or null if none.
87    * @type {?NetworkProperties}
88    * @private
89    */
90   var cellularNetwork_ = null;
92   /**
93    * The active ethernet network or null if none.
94    * @type {?NetworkProperties}
95    * @private
96    */
97   var ethernetNetwork_ = null;
99   /**
100    * The state of the WiFi device or undefined if not available.
101    * @type {string|undefined}
102    * @private
103    */
104   var wifiDeviceState_ = undefined;
106   /**
107    * The state of the WiMAX device or undefined if not available.
108    * @type {string|undefined}
109    * @private
110    */
111   var wimaxDeviceState_ = undefined;
113   /**
114    * The current list of third-party VPN providers.
115    * @type {!Array<!chrome.networkingPrivate.ThirdPartyVPNProperties>}}
116    * @private
117    */
118   var vpnProviders_ = [];
120   /**
121    * Indicates if mobile data roaming is enabled.
122    * @type {boolean}
123    * @private
124    */
125   var enableDataRoaming_ = false;
127   /**
128    * Returns the display name for 'network'.
129    * @param {NetworkProperties} data The network data dictionary.
130    */
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;
138       if (providerName)
139         return loadTimeData.getStringF('vpnNameTemplate', providerName, name);
140     }
141     return name;
142   }
144   /**
145    * Create an element in the network list for controlling network
146    * connectivity.
147    * @param {Object} data Description of the network list or command.
148    * @constructor
149    * @extends {cr.ui.ListItem}
150    */
151   function NetworkListItem(data) {
152     var el = cr.doc.createElement('li');
153     el.data_ = {};
154     for (var key in data)
155       el.data_[key] = data[key];
156     NetworkListItem.decorate(el);
157     return el;
158   }
160   /**
161    * @param {string} action An action to send to coreOptionsUserMetricsAction.
162    */
163   function sendChromeMetricsAction(action) {
164     chrome.send('coreOptionsUserMetricsAction', [action]);
165   }
167   /**
168    * @param {string} guid The network GUID.
169    */
170   function showDetails(guid) {
171     chrome.networkingPrivate.getManagedProperties(
172       guid, DetailsInternetPage.initializeDetailsPage);
173   }
175   /**
176    * Decorate an element as a NetworkListItem.
177    * @param {!Element} el The element to decorate.
178    */
179   NetworkListItem.decorate = function(el) {
180     el.__proto__ = NetworkListItem.prototype;
181     el.decorate();
182   };
184   NetworkListItem.prototype = {
185     __proto__: ListItem.prototype,
187     /**
188      * Description of the network group or control.
189      * @type {Object<Object>}
190      * @private
191      */
192     data_: null,
194     /**
195      * Element for the control's subtitle.
196      * @type {?Element}
197      * @private
198      */
199     subtitle_: null,
201     /**
202      * Div containing the list item icon.
203      * @type {?Element}
204      * @private
205      */
206     iconDiv_: null,
208     /**
209      * Description of the network control.
210      * @type {Object}
211      */
212     get data() {
213       return this.data_;
214     },
216     /**
217      * Text label for the subtitle.
218      * @type {string}
219      */
220     set subtitle(text) {
221       if (text)
222         this.subtitle_.textContent = text;
223       this.subtitle_.hidden = !text;
224     },
226     /**
227      * Sets the icon based on a network state object.
228      * @param {!NetworkProperties} data Network state properties.
229      */
230     set iconData(data) {
231       if (!isNetworkType(data.Type))
232         return;
233       var networkIcon = this.getNetworkIcon();
234       networkIcon.networkState =
235           /** @type {chrome.networkingPrivate.NetworkStateProperties} */ (data);
236     },
238     /**
239      * Sets the icon based on a network type or a special type indecator, e.g.
240      * 'add-connection'
241      * @type {string}
242      */
243     set iconType(type) {
244       if (isNetworkType(type)) {
245         var networkIcon = this.getNetworkIcon();
246         networkIcon.networkType = type;
247       } else {
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'));
252         if (oldIcon)
253           this.iconDiv_.removeChild(oldIcon);
254         this.iconDiv_.classList.add('network-' + type.toLowerCase());
255       }
256     },
258     /**
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.
261      */
262     getNetworkIcon: function() {
263       var networkIcon = /** @type {CrNetworkIconElement} */ (
264           this.iconDiv_.querySelector('cr-network-icon'));
265       if (!networkIcon) {
266         networkIcon = /** @type {!CrNetworkIconElement} */ (
267             document.createElement('cr-network-icon'));
268         networkIcon.isListItem = false;
269         this.iconDiv_.appendChild(networkIcon);
270       }
271       return networkIcon;
272     },
274     /**
275      * Set the direction of the text.
276      * @param {string} direction The direction of the text, e.g. 'ltr'.
277      */
278     setSubtitleDirection: function(direction) {
279       this.subtitle_.dir = direction;
280     },
282     /**
283      * Indicate that the selector arrow should be shown.
284      */
285     showSelector: function() {
286       this.subtitle_.classList.add('network-selector');
287     },
289     /**
290      * Adds an indicator to show that the network is policy managed.
291      */
292     showManagedNetworkIndicator: function() {
293       this.appendChild(new ManagedNetworkIndicator());
294     },
296     /** @override */
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');
307       var title;
308       if (this.data_.key == 'addConnection')
309         title = 'addConnectionTitle';
310       else
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_);
318     },
319   };
321   /**
322    * Creates a control that displays a popup menu when clicked.
323    * @param {Object} data  Description of the control.
324    * @constructor
325    * @extends {NetworkListItem}
326    */
327   function NetworkMenuItem(data) {
328     var el = new NetworkListItem(data);
329     el.__proto__ = NetworkMenuItem.prototype;
330     el.decorate();
331     return el;
332   }
334   NetworkMenuItem.prototype = {
335     __proto__: NetworkListItem.prototype,
337     /**
338      * Popup menu element.
339      * @type {?Element}
340      * @private
341      */
342     menu_: null,
344     /** @override */
345     decorate: function() {
346       this.subtitle = null;
347       if (this.data.iconType)
348         this.iconType = this.data.iconType;
349       this.addEventListener('click', (function() {
350         this.showMenu();
351       }).bind(this));
352     },
354     /**
355      * Retrieves the ID for the menu.
356      */
357     getMenuName: function() {
358       return this.data_.key.toLowerCase() + '-network-menu';
359     },
361     /**
362      * Creates a popup menu for the control.
363      * @return {Element} The newly created menu.
364      */
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';
370         menu.hidden = true;
371         Menu.decorate(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);
376         }
377         return menu;
378       }
379       return null;
380     },
382     /**
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.
388      */
389     canUpdateMenu: function() {
390       return false;
391     },
393     /**
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.
397      */
398     refreshMenu: function() {
399       this.menu_ = null;
400       if (activeMenu_ == this.getMenuName())
401         this.showMenu();
402     },
404     /**
405      * Displays a popup menu.
406      */
407     showMenu: function() {
408       var rebuild = false;
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
412       // unless forced.
413       var rescan = !activeMenu_ && this.data_.key == 'WiFi';
414       if (!this.menu_) {
415         rebuild = true;
416         var existing = $(this.getMenuName());
417         if (existing) {
418           if (this.canUpdateMenu() && this.updateMenu())
419             return;
420           closeMenu_();
421         }
422         this.menu_ = this.createMenu();
423         this.menu_.addEventListener('mousedown', function(e) {
424           // Prevent blurring of list, which would close the menu.
425           e.preventDefault();
426         });
427         var parent = $('network-menus');
428         if (existing)
429           parent.replaceChild(this.menu_, existing);
430         else
431           parent.appendChild(this.menu_);
432       }
433       var top = this.offsetTop + this.clientHeight;
434       var menuId = this.getMenuName();
435       if (menuId != activeMenu_ || rebuild) {
436         closeMenu_();
437         activeMenu_ = menuId;
438         this.menu_.style.setProperty('top', top + 'px');
439         this.menu_.hidden = false;
440       }
441       if (rescan) {
442         chrome.networkingPrivate.requestNetworkScan();
443       }
444     }
445   };
447   /**
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.
452    * @constructor
453    * @extends {NetworkMenuItem}
454    */
455   function NetworkSelectorItem(data) {
456     var el = new NetworkMenuItem(data);
457     el.__proto__ = NetworkSelectorItem.prototype;
458     el.decorate();
459     return el;
460   }
462   /**
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.
466    */
467   function isManaged(source) {
468     return (source == 'DevicePolicy' || source == 'UserPolicy');
469   }
471   /**
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.
476    */
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'.
483     return true;
484   }
486   /**
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.
490    */
491   function isCellularSimAbsent(cellularDevice) {
492     return !!cellularDevice && cellularDevice.SimPresent === false;
493   }
495   /**
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.
499    */
500   function isCellularSimLocked(cellularDevice) {
501     return !!cellularDevice && !!cellularDevice.SimLockType;
502   }
504   NetworkSelectorItem.prototype = {
505     __proto__: NetworkMenuItem.prototype,
507     /** @override */
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
522           // time.
523           if (networkDetails.ConnectionState == 'Connecting')
524             break;
525         }
526       }
527       if (candidateData)
528         this.iconData = candidateData;
529       else
530         this.iconType = this.data.key;
532       this.showSelector();
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.
542         var self = this;
543         setTimeout(function() {self.showMenu();}, 0);
544       }
545     },
547     /**
548      * Creates a menu for selecting, configuring or disconnecting from a
549      * network.
550      * @return {!Element} The newly created menu.
551      */
552     createMenu: function() {
553       var menu = this.ownerDocument.createElement('div');
554       menu.id = this.getMenuName();
555       menu.className = 'network-menu';
556       menu.hidden = true;
557       Menu.decorate(menu);
558       menu.menuItemSelector = '.network-menu-item';
559       var addendum = [];
560       if (this.data_.key == 'WiFi') {
561         addendum.push({
562           label: loadTimeData.getString('joinOtherNetwork'),
563           command: createAddNonVPNConnectionCallback_('WiFi'),
564           data: {}
565         });
566       } else if (this.data_.key == 'Cellular') {
567         if (cellularDevice_.State == 'Enabled' &&
568             cellularNetwork_ && cellularNetwork_.Cellular &&
569             cellularNetwork_.Cellular.SupportNetworkScan) {
570           addendum.push({
571             label: loadTimeData.getString('otherCellularNetworks'),
572             command: createAddNonVPNConnectionCallback_('Cellular'),
573             addClass: ['other-cellulars'],
574             data: {}
575           });
576         }
578         var label = enableDataRoaming_ ? 'disableDataRoaming' :
579             'enableDataRoaming';
580         var disabled = !loadTimeData.getValue('loggedInAsOwner');
581         var entry = {label: loadTimeData.getString(label),
582                      data: {}};
583         if (disabled) {
584           entry.command = null;
585           entry.tooltip =
586               loadTimeData.getString('dataRoamingDisableToggleTooltip');
587         } else {
588           var self = this;
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.
594             self.menu_ = null;
595           };
596         }
597         addendum.push(entry);
598       } else if (this.data_.key == 'VPN') {
599         addendum = addendum.concat(createAddVPNConnectionEntries_());
600       }
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);
608           dialog.update(list);
609           sendChromeMetricsAction('Options_NetworkShowPreferred');
610         };
611         addendum.push({label: loadTimeData.getString('preferredNetworks'),
612                        command: callback,
613                        data: list});
614       }
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;
620       if (list) {
621         var connectedVpnGuid = '';
622         for (var i = 0; i < list.length; i++) {
623           var data = list[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;
630           }
631         }
632         if (connectedVpnGuid) {
633           var disconnectCallback = function() {
634             sendChromeMetricsAction('Options_NetworkDisconnectVPN');
635             chrome.networkingPrivate.startDisconnect(connectedVpnGuid);
636           };
637           // Add separator
638           addendum.push({});
639           addendum.push({label: loadTimeData.getString('disconnectNetwork'),
640                          command: disconnectCallback,
641                          data: data});
642         }
643       }
644       if (this.data_.key == 'WiFi' || this.data_.key == 'WiMAX' ||
645           this.data_.key == 'Cellular') {
646         addendum.push({});
647         if (this.data_.key == 'WiFi') {
648           addendum.push({
649             label: loadTimeData.getString('turnOffWifi'),
650             command: function() {
651               sendChromeMetricsAction('Options_NetworkWifiToggle');
652               chrome.networkingPrivate.disableNetworkType(
653                   chrome.networkingPrivate.NetworkType.WI_FI);
654             },
655             data: {}});
656         } else if (this.data_.key == 'WiMAX') {
657           addendum.push({
658             label: loadTimeData.getString('turnOffWimax'),
659             command: function() {
660               chrome.networkingPrivate.disableNetworkType(
661                   chrome.networkingPrivate.NetworkType.WI_MAX);
662             },
663             data: {}});
664         } else if (this.data_.key == 'Cellular') {
665           addendum.push({
666             label: loadTimeData.getString('turnOffCellular'),
667             command: function() {
668               chrome.networkingPrivate.disableNetworkType(
669                   chrome.networkingPrivate.NetworkType.CELLULAR);
670             },
671             data: {}});
672         }
673       }
674       if (!empty)
675         menu.appendChild(networkGroup);
676       if (addendum.length > 0) {
677         var separator = false;
678         if (!empty) {
679           menu.appendChild(MenuItem.createSeparator());
680           separator = true;
681         }
682         for (var i = 0; i < addendum.length; i++) {
683           var value = addendum[i];
684           if (value.data) {
685             var item = createCallback_(menu, value.data, value.label,
686                                        value.command);
687             if (value.tooltip)
688               item.title = value.tooltip;
689             if (value.addClass)
690               item.classList.add(value.addClass);
691             separator = false;
692           } else if (!separator) {
693             menu.appendChild(MenuItem.createSeparator());
694             separator = true;
695           }
696         }
697       }
698       return menu;
699     },
701     /** @override */
702     canUpdateMenu: function() {
703       return this.data_.key == 'WiFi' && activeMenu_ == this.getMenuName();
704     },
706     /**
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.
715      */
716     updateMenu: function() {
717       var oldMenu = $(this.getMenuName());
718       var group = oldMenu.getElementsByClassName('network-menu-group')[0];
719       if (!group)
720         return false;
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;
732         } else {
733           // Leave item in list to prevent network items from jumping due to
734           // deletions.
735           oldNetworkButtons[key].disabled = true;
736           discardOnClose = true;
737         }
738       }
739       for (var key in newNetworkButtons) {
740         var entry = newNetworkButtons[key];
741         if (entry) {
742           group.appendChild(entry.button);
743           discardOnClose = true;
744         }
745       }
746       oldMenu.data = {discardOnClose: discardOnClose};
747       return true;
748     },
750     /**
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}>}
754      *     Network mapping.
755      * @private
756      */
757     extractNetworkConnectButtons_: function(menu) {
758       var group = menu.getElementsByClassName('network-menu-group')[0];
759       var networkButtons = {};
760       if (!group)
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]};
766       }
767       return networkButtons;
768     },
770     /**
771      * Adds a menu item for showing network details.
772      * @param {!Element} parent The parent element.
773      * @param {NetworkProperties} data Description of the network.
774      * @private
775      */
776     createNetworkOptionsCallback_: function(parent, data) {
777       var menuItem = createCallback_(parent,
778                                      data,
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');
788       }
789     }
790   };
792   /**
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.
796    * @constructor
797    * @extends {NetworkListItem}
798    */
799   function NetworkButtonItem(data) {
800     var el = new NetworkListItem(data);
801     el.__proto__ = NetworkButtonItem.prototype;
802     el.decorate();
803     return el;
804   }
806   NetworkButtonItem.prototype = {
807     __proto__: NetworkListItem.prototype,
809     /** @override */
810     decorate: function() {
811       if (this.data.subtitle)
812         this.subtitle = this.data.subtitle;
813       else
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();
823     },
824   };
826   /**
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.
833    * @private
834    */
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);
849     }
851     var buttonLabel = menu.ownerDocument.createElement('span');
852     buttonLabel.className = 'network-menu-item-label';
853     buttonLabel.textContent = label;
854     button.appendChild(buttonLabel);
855     var callback = null;
856     if (command != null) {
857       if (data) {
858         callback = function() {
859           (/** @type {Function} */(command))(data);
860           closeMenu_();
861         };
862       } else {
863         callback = function() {
864           (/** @type {Function} */(command))();
865           closeMenu_();
866         };
867       }
868     }
869     if (callback != null)
870       button.addEventListener('activate', callback);
871     else
872       buttonLabel.classList.add('network-disabled-control');
874     button.data = {label: label};
875     MenuItem.decorate(button);
876     menu.appendChild(button);
877     return button;
878   }
880   /**
881    * A list of controls for manipulating network connectivity.
882    * @constructor
883    * @extends {cr.ui.List}
884    */
885   var NetworkList = cr.ui.define('list');
887   NetworkList.prototype = {
888     __proto__: List.prototype,
890     /** @override */
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',
908           function(event) {
909             enableDataRoaming_ = event.value.value;
910           });
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);
928       }.bind(this));
930       chrome.management.getAll(this.onGetAllExtensions_.bind(this));
931       chrome.networkingPrivate.requestNetworkScan();
932     },
934     /**
935      * networkingPrivate event called when the network list has changed.
936      */
937     onNetworkListChanged_: function() {
938       var networkList = this;
939       chrome.networkingPrivate.getDeviceStates(function(deviceStates) {
940         var filter = {
941           networkType: chrome.networkingPrivate.NetworkType.ALL
942         };
943         chrome.networkingPrivate.getNetworks(filter, function(networkStates) {
944           networkList.updateNetworkStates(deviceStates, networkStates);
945         });
946       });
947     },
949     /**
950      * chrome.management.getAll callback.
951      * @param {!Array<!ExtensionInfo>} extensions
952      * @private
953      */
954     onGetAllExtensions_: function(extensions) {
955       vpnProviders_ = [];
956       for (var extension of extensions)
957         this.addVpnProvider_(extension);
958     },
960     /**
961      * If |extension| is a third-party VPN provider, add it to vpnProviders_.
962      * @param {!ExtensionInfo} extension
963      * @private
964      */
965     addVpnProvider_: function(extension) {
966       if (!extension.enabled ||
967           extension.permissions.indexOf('vpnProvider') == -1) {
968         return;
969       }
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)
975           return;
976       }
977       var newProvider = {
978         ExtensionID: extension.id,
979         ProviderName: extension.name
980       };
981       vpnProviders_.push(newProvider);
982       this.refreshVpnProviders_();
983     },
985     /**
986      * chrome.management.onInstalled or onEnabled event.
987      * @param {!ExtensionInfo} extension
988      * @private
989      */
990     onExtensionAdded_: function(extension) {
991       this.addVpnProvider_(extension);
992     },
994     /**
995      * chrome.management.onUninstalled or onDisabled event.
996      * @param {string} extensionId
997      * @private
998      */
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_();
1005           break;
1006         }
1007       }
1008     },
1010     /**
1011      * Rebuilds the list of VPN providers.
1012      * @private
1013      */
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();
1025     },
1027     /**
1028      * Updates the entries in the "add connection" menu, based on the VPN
1029      * providers currently enabled in the user's profile.
1030      * @private
1031      */
1032     updateAddConnectionMenuEntries_: function() {
1033       var entries = [{
1034         label: loadTimeData.getString('addConnectionWifi'),
1035         command: createAddNonVPNConnectionCallback_('WiFi')
1036       }];
1037       entries = entries.concat(createAddVPNConnectionEntries_());
1038       this.update({key: 'addConnection',
1039                    iconType: 'add-connection',
1040                    menu: entries
1041                   });
1042     },
1044     /**
1045      * When the list loses focus, unselect all items in the list and close the
1046      * active menu.
1047      * @private
1048      */
1049     onBlur_: function() {
1050       this.selectionModel.unselectAll();
1051       closeMenu_();
1052     },
1054     /** @override */
1055     handleKeyDown: function(e) {
1056       if (activeMenu_) {
1057         // keyIdentifier does not report 'Esc' correctly
1058         if (e.keyCode == 27 /* Esc */) {
1059           closeMenu_();
1060           return;
1061         }
1063         if ($(activeMenu_).handleKeyDown(e)) {
1064           e.preventDefault();
1065           e.stopPropagation();
1066         }
1067         return;
1068       }
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();
1076           return;
1077         }
1078       }
1080       List.prototype.handleKeyDown.call(this, e);
1081     },
1083     /**
1084      * Close bubble and menu when a different list item is selected.
1085      * @param {Event} event Event detailing the selection change.
1086      * @private
1087      */
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.
1093       if (activeMenu_) {
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_) {
1098               closeMenu_();
1099               return;
1100             }
1101           }
1102         }
1103       }
1104     },
1106     /**
1107      * Finds the index of a network item within the data model based on
1108      * category.
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.
1112      */
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)
1118           return i;
1119       }
1120       return undefined;
1121     },
1123     /**
1124      * Updates a network control.
1125      * @param {Object} data Description of the entry.
1126      */
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]) {
1136             data.sortIndex = i;
1137             break;
1138           }
1139         }
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)
1144             referenceIndex = i;
1145           else
1146             break;
1147         }
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);
1154         } else {
1155           // Insert after the reference element.
1156           this.dataModel.splice(referenceIndex + 1, 0, data);
1157         }
1158       } else {
1159         var entry = this.dataModel.item(index);
1160         data.sortIndex = entry.sortIndex;
1161         this.dataModel.splice(index, 1, data);
1162       }
1163       this.endBatchUpdates();
1164     },
1166     /**
1167      * @override
1168      * @param {Object} entry
1169      */
1170     createItem: function(entry) {
1171       if (entry.networkList)
1172         return new NetworkSelectorItem(
1173             /** @type {{key: string, networkList: Array<!NetworkProperties>}} */
1174             (entry));
1175       if (entry.command)
1176         return new NetworkButtonItem(
1177             /** @type {{key: string, subtitle: string, command: Function}} */(
1178                 entry));
1179       if (entry.menu)
1180         return new NetworkMenuItem(entry);
1181       assertNotReached();
1182     },
1184     /**
1185      * Deletes an element from the list.
1186      * @param {string} key  Unique identifier for the element.
1187      */
1188     deleteItem: function(key) {
1189       var index = this.indexOf(key);
1190       if (index != undefined)
1191         this.dataModel.splice(index, 1);
1192     },
1194     /**
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.
1200      */
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;
1216       }
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) {
1227           case 'Cellular':
1228             cellularNetwork_ = cellularNetwork_ || entry;
1229             break;
1230           case 'Ethernet':
1231             // Ignore any EAP Parameters networks (which lack ConnectionState).
1232             if (entry.ConnectionState)
1233               ethernetNetwork_ = ethernetNetwork_ || entry;
1234             break;
1235         }
1236         if (cellularNetwork_ && ethernetNetwork_)
1237           break;
1238       }
1240       if (cellularNetwork_ && cellularNetwork_.GUID) {
1241         // Get the complete set of cellular properties which includes SIM and
1242         // Scan properties.
1243         var networkList = this;
1244         chrome.networkingPrivate.getProperties(
1245             cellularNetwork_.GUID, function(cellular) {
1246               cellularNetwork_ = /** @type {NetworkProperties} */ (cellular);
1247               networkList.updateControls(networkStates);
1248             });
1249       } else {
1250         this.updateControls(networkStates);
1251       }
1252     },
1254     /**
1255      * Updates network controls.
1256      * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>}
1257      *     networkStates The result from networkingPrivate.getNetworks.
1258      */
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;
1266         var subtitle;
1267         if (state == 'Connected')
1268           subtitle = loadTimeData.getString('OncConnectionStateConnected');
1269         else if (state == 'Connecting')
1270           subtitle = loadTimeData.getString('OncConnectionStateConnecting');
1271         else
1272           subtitle = loadTimeData.getString('OncConnectionStateNotConnected');
1273         this.update(
1274           { key: 'Ethernet',
1275             subtitle: subtitle,
1276             iconData: ethernetNetwork_,
1277             command: ethernetOptions,
1278             Source: ethernetNetwork_.Source }
1279         );
1280       } else {
1281         this.deleteItem('Ethernet');
1282       }
1284       if (wifiDeviceState_ == 'Enabled')
1285         loadData_('WiFi', networkStates);
1286       else
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);
1295         } else {
1296           addEnableNetworkButton_(
1297               chrome.networkingPrivate.NetworkType.CELLULAR);
1298         }
1299       } else {
1300         this.deleteItem('Cellular');
1301       }
1303       // Only show wimax control if available. Uses cellular icons.
1304       if (wimaxDeviceState_) {
1305         if (wimaxDeviceState_ == 'Enabled')
1306           loadData_('WiMAX', networkStates);
1307         else
1308           addEnableNetworkButton_(chrome.networkingPrivate.NetworkType.WI_MAX);
1309       } else {
1310         this.deleteItem('WiMAX');
1311       }
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();
1318     }
1319   };
1321   /**
1322    * Replaces a network menu with a button for enabling the network type.
1323    * @param {chrome.networkingPrivate.NetworkType} type
1324    * @private
1325    */
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']);
1334           return;
1335         } else if (isCellularSimAbsent(cellularDevice_)) {
1336           chrome.send('simOperation', ['configure']);
1337           return;
1338         }
1339       }
1340       chrome.networkingPrivate.enableNetworkType(type);
1341     };
1342     $('network-list').update({key: type,
1343                               subtitle: subtitle,
1344                               iconType: type,
1345                               command: enableNetwork});
1346   }
1348   /**
1349    * Element for indicating a policy managed network.
1350    * @constructor
1351    * @extends {options.ControlledSettingIndicator}
1352    */
1353   function ManagedNetworkIndicator() {
1354     var el = cr.doc.createElement('span');
1355     el.__proto__ = ManagedNetworkIndicator.prototype;
1356     el.decorate();
1357     return el;
1358   }
1360   ManagedNetworkIndicator.prototype = {
1361     __proto__: ControlledSettingIndicator.prototype,
1363     /** @override */
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');
1370     },
1372     /** @override */
1373     handleEvent: function(event) {
1374       // Prevent focus blurring as that would close any currently open menu.
1375       if (event.type == 'mousedown')
1376         return;
1377       ControlledSettingIndicator.prototype.handleEvent.call(this, event);
1378     },
1380     /**
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.
1385      */
1386     stopEvent: function(event) {
1387       event.preventDefault();
1388       event.stopPropagation();
1389     },
1391     /** @override */
1392     toggleBubble: function() {
1393       if (activeMenu_ && !$(activeMenu_).contains(this))
1394         closeMenu_();
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);
1400       }
1401     }
1402   };
1404   /**
1405    * Updates the list of available networks and their status, filtered by
1406    * network type.
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|.
1411    */
1412   function loadData_(type, networks) {
1413     var res = 0;
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)
1419         continue;
1420       if (networkIsVisible(network)) {
1421         availableNetworks.push(network);
1422         ++res;
1423       }
1424       if ((type == 'WiFi' || type == 'VPN') && network.Source &&
1425           network.Source != 'None') {
1426         rememberedNetworks.push(network);
1427       }
1428     }
1429     var data = {
1430       key: type,
1431       networkList: availableNetworks,
1432       rememberedNetworks: rememberedNetworks
1433     };
1434     $('network-list').update(data);
1435     return res;
1436   }
1438   /**
1439    * Hides the currently visible menu.
1440    * @private
1441    */
1442   function closeMenu_() {
1443     if (activeMenu_) {
1444       var menu = $(activeMenu_);
1445       menu.hidden = true;
1446       if (menu.data && menu.data.discardOnClose)
1447         menu.parentNode.removeChild(menu);
1448       activeMenu_ = null;
1449     }
1450   }
1452   /**
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.
1457    * @private
1458    */
1459   function createAddNonVPNConnectionCallback_(type) {
1460     return function() {
1461       if (type == 'WiFi')
1462         sendChromeMetricsAction('Options_NetworkJoinOtherWifi');
1463       chrome.send('addNonVPNConnection', [type]);
1464     };
1465   }
1467   /**
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.
1475    * @private
1476    */
1477   function createVPNConnectionCallback_(opt_extensionID) {
1478     return function() {
1479       sendChromeMetricsAction(opt_extensionID ?
1480           'Options_NetworkAddVPNThirdParty' :
1481           'Options_NetworkAddVPNBuiltIn');
1482       chrome.send('addVPNConnection',
1483                   opt_extensionID ? [opt_extensionID] : undefined);
1484     };
1485   }
1487   /**
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
1491    *     list of entries.
1492    * @private
1493    */
1494   function createAddVPNConnectionEntries_() {
1495     var entries = [];
1496     for (var i = 0; i < vpnProviders_.length; ++i) {
1497       var provider = vpnProviders_[i];
1498       entries.push({
1499         label: loadTimeData.getStringF('addConnectionVPNTemplate',
1500                                        provider.ProviderName),
1501         command: createVPNConnectionCallback_(provider.ExtensionID),
1502         data: {}
1503       });
1504     }
1505     // Add an entry for the built-in OpenVPN/L2TP provider.
1506     entries.push({
1507       label: loadTimeData.getString('vpnBuiltInProvider'),
1508       command: createVPNConnectionCallback_(),
1509       data: {}
1510     });
1511     return entries;
1512   }
1514   /**
1515    * Whether the Network list is disabled. Only used for display purpose.
1516    */
1517   cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR);
1519   // Export
1520   return {
1521     NetworkList: NetworkList
1522   };