8ba909c409d75145e09dc26d6a0130b469f05ebf
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / options / chromeos / network_list.js
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.
4
5 /**
6  * @typedef {{
7  *   ConnectionState: string,
8  *   iconURL: string,
9  *   policyManaged: boolean,
10  *   servicePath: string
11  * }}
12  * @see chrome/browser/ui/webui/options/chromeos/internet_options_handler.cc
13  */
14 var NetworkInfo;
15
16 cr.define('options.network', function() {
17   var ArrayDataModel = cr.ui.ArrayDataModel;
18   var List = cr.ui.List;
19   var ListItem = cr.ui.ListItem;
20   var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
21   var Menu = cr.ui.Menu;
22   var MenuItem = cr.ui.MenuItem;
23   var ControlledSettingIndicator = options.ControlledSettingIndicator;
24
25   /**
26    * Network settings constants. These enums usually match their C++
27    * counterparts.
28    */
29   function Constants() {}
30
31   // Cellular activation states:
32   Constants.ACTIVATION_STATE_UNKNOWN = 0;
33   Constants.ACTIVATION_STATE_ACTIVATED = 1;
34   Constants.ACTIVATION_STATE_ACTIVATING = 2;
35   Constants.ACTIVATION_STATE_NOT_ACTIVATED = 3;
36   Constants.ACTIVATION_STATE_PARTIALLY_ACTIVATED = 4;
37
38   /**
39    * Order in which controls are to appear in the network list sorted by key.
40    */
41   Constants.NETWORK_ORDER = ['Ethernet',
42                              'WiFi',
43                              'WiMAX',
44                              'Cellular',
45                              'VPN',
46                              'addConnection'];
47
48   /**
49    * ID of the menu that is currently visible.
50    * @type {?string}
51    * @private
52    */
53   var activeMenu_ = null;
54
55   /**
56    * Indicates if cellular networks are available.
57    * @type {boolean}
58    * @private
59    */
60   var cellularAvailable_ = false;
61
62   /**
63    * Indicates if cellular networks are enabled.
64    * @type {boolean}
65    * @private
66    */
67   var cellularEnabled_ = false;
68
69   /**
70    * Indicates if cellular device supports network scanning.
71    * @type {boolean}
72    * @private
73    */
74   var cellularSupportsScan_ = false;
75
76   /**
77    * Indicates the current SIM lock type of the cellular device.
78    * @type {boolean}
79    * @private
80    */
81   var cellularSimLockType_ = '';
82
83   /**
84    * Indicates whether the SIM card is absent on the cellular device.
85    * @type {boolean}
86    * @private
87    */
88   var cellularSimAbsent_ = false;
89
90   /**
91    * Indicates if WiMAX networks are available.
92    * @type {boolean}
93    * @private
94    */
95   var wimaxAvailable_ = false;
96
97   /**
98    * Indicates if WiMAX networks are enabled.
99    * @type {boolean}
100    * @private
101    */
102   var wimaxEnabled_ = false;
103
104   /**
105    * Indicates if mobile data roaming is enabled.
106    * @type {boolean}
107    * @private
108    */
109   var enableDataRoaming_ = false;
110
111   /**
112    * Icon to use when not connected to a particular type of network.
113    * @type {!Object.<string, string>} Mapping of network type to icon data url.
114    * @private
115    */
116   var defaultIcons_ = {};
117
118   /**
119    * Returns the display name for 'network'.
120    * @param {Object} data The network data dictionary.
121    */
122   function getNetworkName(data) {
123     if (data.Type == 'Ethernet')
124       return loadTimeData.getString('ethernetName');
125     return data.Name;
126   }
127
128   /**
129    * Create an element in the network list for controlling network
130    * connectivity.
131    * @param {Object} data Description of the network list or command.
132    * @constructor
133    * @extends {cr.ui.ListItem}
134    */
135   function NetworkListItem(data) {
136     var el = cr.doc.createElement('li');
137     el.data_ = {};
138     for (var key in data)
139       el.data_[key] = data[key];
140     NetworkListItem.decorate(el);
141     return el;
142   }
143
144   /**
145    * @param {string} action An action to send to coreOptionsUserMetricsAction.
146    */
147   function sendChromeMetricsAction(action) {
148     chrome.send('coreOptionsUserMetricsAction', [action]);
149   }
150
151   /**
152    * Decorate an element as a NetworkListItem.
153    * @param {!Element} el The element to decorate.
154    */
155   NetworkListItem.decorate = function(el) {
156     el.__proto__ = NetworkListItem.prototype;
157     el.decorate();
158   };
159
160   NetworkListItem.prototype = {
161     __proto__: ListItem.prototype,
162
163     /**
164      * Description of the network group or control.
165      * @type {Object.<string,Object>}
166      * @private
167      */
168     data_: null,
169
170     /**
171      * Element for the control's subtitle.
172      * @type {?Element}
173      * @private
174      */
175     subtitle_: null,
176
177     /**
178      * Icon for the network control.
179      * @type {?Element}
180      * @private
181      */
182     icon_: null,
183
184     /**
185      * Indicates if in the process of connecting to a network.
186      * @type {boolean}
187      * @private
188      */
189     connecting_: false,
190
191     /**
192      * Description of the network control.
193      * @type {Object}
194      */
195     get data() {
196       return this.data_;
197     },
198
199     /**
200      * Text label for the subtitle.
201      * @type {string}
202      */
203     set subtitle(text) {
204       if (text)
205         this.subtitle_.textContent = text;
206       this.subtitle_.hidden = !text;
207     },
208
209     /**
210      * URL for the network icon.
211      * @type {string}
212      */
213     set iconURL(iconURL) {
214       this.icon_.style.backgroundImage = url(iconURL);
215     },
216
217     /**
218      * Type of network icon.  Each type corresponds to a CSS rule.
219      * @type {string}
220      */
221     set iconType(type) {
222       if (defaultIcons_[type])
223         this.iconURL = defaultIcons_[type];
224       else
225         this.icon_.classList.add('network-' + type.toLowerCase());
226     },
227
228     /**
229      * Indicates if the network is in the process of being connected.
230      * @type {boolean}
231      */
232     set connecting(state) {
233       this.connecting_ = state;
234       if (state)
235         this.icon_.classList.add('network-connecting');
236       else
237         this.icon_.classList.remove('network-connecting');
238     },
239
240     /**
241      * Indicates if the network is in the process of being connected.
242      * @type {boolean}
243      */
244     get connecting() {
245       return this.connecting_;
246     },
247
248     /**
249      * Set the direction of the text.
250      * @param {string} direction The direction of the text, e.g. 'ltr'.
251      */
252     setSubtitleDirection: function(direction) {
253       this.subtitle_.dir = direction;
254     },
255
256     /**
257      * Indicate that the selector arrow should be shown.
258      */
259     showSelector: function() {
260       this.subtitle_.classList.add('network-selector');
261     },
262
263     /**
264      * Adds an indicator to show that the network is policy managed.
265      */
266     showManagedNetworkIndicator: function() {
267       this.appendChild(new ManagedNetworkIndicator());
268     },
269
270     /** @override */
271     decorate: function() {
272       ListItem.prototype.decorate.call(this);
273       this.className = 'network-group';
274       this.icon_ = this.ownerDocument.createElement('div');
275       this.icon_.className = 'network-icon';
276       this.appendChild(this.icon_);
277       var textContent = this.ownerDocument.createElement('div');
278       textContent.className = 'network-group-labels';
279       this.appendChild(textContent);
280       var categoryLabel = this.ownerDocument.createElement('div');
281       var title;
282       if (this.data_.key == 'addConnection')
283         title = 'addConnectionTitle';
284       else
285         title = this.data_.key.toLowerCase() + 'Title';
286       categoryLabel.className = 'network-title';
287       categoryLabel.textContent = loadTimeData.getString(title);
288       textContent.appendChild(categoryLabel);
289       this.subtitle_ = this.ownerDocument.createElement('div');
290       this.subtitle_.className = 'network-subtitle';
291       textContent.appendChild(this.subtitle_);
292     },
293   };
294
295   /**
296    * Creates a control that displays a popup menu when clicked.
297    * @param {Object} data  Description of the control.
298    * @constructor
299    * @extends {NetworkListItem}
300    */
301   function NetworkMenuItem(data) {
302     var el = new NetworkListItem(data);
303     el.__proto__ = NetworkMenuItem.prototype;
304     el.decorate();
305     return el;
306   }
307
308   NetworkMenuItem.prototype = {
309     __proto__: NetworkListItem.prototype,
310
311     /**
312      * Popup menu element.
313      * @type {?Element}
314      * @private
315      */
316     menu_: null,
317
318     /** @override */
319     decorate: function() {
320       this.subtitle = null;
321       if (this.data.iconType)
322         this.iconType = this.data.iconType;
323       this.addEventListener('click', function() {
324         this.showMenu();
325       });
326     },
327
328     /**
329      * Retrieves the ID for the menu.
330      */
331     getMenuName: function() {
332       return this.data_.key.toLowerCase() + '-network-menu';
333     },
334
335     /**
336      * Creates a popup menu for the control.
337      * @return {Element} The newly created menu.
338      */
339     createMenu: function() {
340       if (this.data.menu) {
341         var menu = this.ownerDocument.createElement('div');
342         menu.id = this.getMenuName();
343         menu.className = 'network-menu';
344         menu.hidden = true;
345         Menu.decorate(menu);
346         for (var i = 0; i < this.data.menu.length; i++) {
347           var entry = this.data.menu[i];
348           createCallback_(menu, null, entry.label, entry.command);
349         }
350         return menu;
351       }
352       return null;
353     },
354
355     canUpdateMenu: function() {
356       return false;
357     },
358
359     /**
360      * Displays a popup menu.
361      */
362     showMenu: function() {
363       var rebuild = false;
364       // Force a rescan if opening the menu for WiFi networks to ensure the
365       // list is up to date. Networks are periodically rescanned, but depending
366       // on timing, there could be an excessive delay before the first rescan
367       // unless forced.
368       var rescan = !activeMenu_ && this.data_.key == 'WiFi';
369       if (!this.menu_) {
370         rebuild = true;
371         var existing = $(this.getMenuName());
372         if (existing) {
373           if (this.updateMenu())
374             return;
375           closeMenu_();
376         }
377         this.menu_ = this.createMenu();
378         this.menu_.addEventListener('mousedown', function(e) {
379           // Prevent blurring of list, which would close the menu.
380           e.preventDefault();
381         });
382         var parent = $('network-menus');
383         if (existing)
384           parent.replaceChild(this.menu_, existing);
385         else
386           parent.appendChild(this.menu_);
387       }
388       var top = this.offsetTop + this.clientHeight;
389       var menuId = this.getMenuName();
390       if (menuId != activeMenu_ || rebuild) {
391         closeMenu_();
392         activeMenu_ = menuId;
393         this.menu_.style.setProperty('top', top + 'px');
394         this.menu_.hidden = false;
395       }
396       if (rescan) {
397         // TODO(stevenjb): chrome.networkingPrivate.requestNetworkScan
398         chrome.send('requestNetworkScan');
399       }
400     }
401   };
402
403   /**
404    * Creates a control for selecting or configuring a network connection based
405    * on the type of connection (e.g. wifi versus vpn).
406    * @param {{key: string, networkList: Array.<NetworkInfo>}} data Description
407    *     of the network.
408    * @constructor
409    * @extends {NetworkMenuItem}
410    */
411   function NetworkSelectorItem(data) {
412     var el = new NetworkMenuItem(data);
413     el.__proto__ = NetworkSelectorItem.prototype;
414     el.decorate();
415     return el;
416   }
417
418   NetworkSelectorItem.prototype = {
419     __proto__: NetworkMenuItem.prototype,
420
421     /** @override */
422     decorate: function() {
423       // TODO(kevers): Generalize method of setting default label.
424       var policyManaged = false;
425       this.subtitle = loadTimeData.getString('OncConnectionStateNotConnected');
426       var list = this.data_.networkList;
427       var candidateURL = null;
428       for (var i = 0; i < list.length; i++) {
429         var networkDetails = list[i];
430         if (networkDetails.ConnectionState == 'Connecting' ||
431             networkDetails.ConnectionState == 'Connected') {
432           this.subtitle = getNetworkName(networkDetails);
433           this.setSubtitleDirection('ltr');
434           policyManaged = networkDetails.policyManaged;
435           candidateURL = networkDetails.iconURL;
436           // Only break when we see a connecting network as it is possible to
437           // have a connected network and a connecting network at the same
438           // time.
439           if (networkDetails.ConnectionState == 'Connecting') {
440             this.connecting = true;
441             candidateURL = null;
442             break;
443           }
444         }
445       }
446       if (candidateURL)
447         this.iconURL = candidateURL;
448       else
449         this.iconType = this.data.key;
450
451       this.showSelector();
452
453       if (policyManaged)
454         this.showManagedNetworkIndicator();
455
456       if (activeMenu_ == this.getMenuName()) {
457         // Menu is already showing and needs to be updated. Explicitly calling
458         // show menu will force the existing menu to be replaced.  The call
459         // is deferred in order to ensure that position of this element has
460         // beem properly updated.
461         var self = this;
462         setTimeout(function() {self.showMenu();}, 0);
463       }
464     },
465
466     /**
467      * Creates a menu for selecting, configuring or disconnecting from a
468      * network.
469      * @return {!Element} The newly created menu.
470      */
471     createMenu: function() {
472       var menu = this.ownerDocument.createElement('div');
473       menu.id = this.getMenuName();
474       menu.className = 'network-menu';
475       menu.hidden = true;
476       Menu.decorate(menu);
477       var addendum = [];
478       if (this.data_.key == 'WiFi') {
479         addendum.push({
480           label: loadTimeData.getString('joinOtherNetwork'),
481           command: createAddConnectionCallback_('WiFi'),
482           data: {}
483         });
484       } else if (this.data_.key == 'Cellular') {
485         if (cellularEnabled_ && cellularSupportsScan_) {
486           addendum.push({
487             label: loadTimeData.getString('otherCellularNetworks'),
488             command: createAddConnectionCallback_('Cellular'),
489             addClass: ['other-cellulars'],
490             data: {}
491           });
492         }
493
494         var label = enableDataRoaming_ ? 'disableDataRoaming' :
495             'enableDataRoaming';
496         var disabled = !loadTimeData.getValue('loggedInAsOwner');
497         var entry = {label: loadTimeData.getString(label),
498                      data: {}};
499         if (disabled) {
500           entry.command = null;
501           entry.tooltip =
502               loadTimeData.getString('dataRoamingDisableToggleTooltip');
503         } else {
504           var self = this;
505           entry.command = function() {
506             options.Preferences.setBooleanPref(
507                 'cros.signed.data_roaming_enabled',
508                 !enableDataRoaming_, true);
509             // Force revalidation of the menu the next time it is displayed.
510             self.menu_ = null;
511           };
512         }
513         addendum.push(entry);
514       }
515       var list = this.data.rememberedNetworks;
516       if (list && list.length > 0) {
517         var callback = function(list) {
518           $('remembered-network-list').clear();
519           var dialog = options.PreferredNetworks.getInstance();
520           PageManager.showPageByName('preferredNetworksPage', false);
521           dialog.update(list);
522           sendChromeMetricsAction('Options_NetworkShowPreferred');
523         };
524         addendum.push({label: loadTimeData.getString('preferredNetworks'),
525                        command: callback,
526                        data: list});
527       }
528
529       var networkGroup = this.ownerDocument.createElement('div');
530       networkGroup.className = 'network-menu-group';
531       list = this.data.networkList;
532       var empty = !list || list.length == 0;
533       if (list) {
534         for (var i = 0; i < list.length; i++) {
535           var data = list[i];
536           this.createNetworkOptionsCallback_(networkGroup, data);
537           if (data.ConnectionState == 'Connected') {
538             if (data.Type == 'VPN') {
539               var disconnectCallback = function() {
540                 sendChromeMetricsAction('Options_NetworkDisconnectVPN');
541                 // TODO(stevenjb): chrome.networkingPrivate.startDisconnect
542                 chrome.send('startDisconnect', [data.servicePath]);
543               };
544               // Add separator
545               addendum.push({});
546               addendum.push({label: loadTimeData.getString('disconnectNetwork'),
547                              command: disconnectCallback,
548                              data: data});
549             }
550           }
551         }
552       }
553       if (this.data_.key == 'WiFi' || this.data_.key == 'WiMAX' ||
554           this.data_.key == 'Cellular') {
555         addendum.push({});
556         if (this.data_.key == 'WiFi') {
557           addendum.push({
558             label: loadTimeData.getString('turnOffWifi'),
559             command: function() {
560               sendChromeMetricsAction('Options_NetworkWifiToggle');
561               // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
562               chrome.send('disableNetworkType', ['WiFi']);
563             },
564             data: {}});
565         } else if (this.data_.key == 'WiMAX') {
566           addendum.push({
567             label: loadTimeData.getString('turnOffWimax'),
568             command: function() {
569               // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
570               chrome.send('disableNetworkType', ['WiMAX']);
571             },
572             data: {}});
573         } else if (this.data_.key == 'Cellular') {
574           addendum.push({
575             label: loadTimeData.getString('turnOffCellular'),
576             command: function() {
577               // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
578               chrome.send('disableNetworkType', ['Cellular']);
579             },
580             data: {}});
581         }
582       }
583       if (!empty)
584         menu.appendChild(networkGroup);
585       if (addendum.length > 0) {
586         var separator = false;
587         if (!empty) {
588           menu.appendChild(MenuItem.createSeparator());
589           separator = true;
590         }
591         for (var i = 0; i < addendum.length; i++) {
592           var value = addendum[i];
593           if (value.data) {
594             var item = createCallback_(menu, value.data, value.label,
595                                        value.command);
596             if (value.tooltip)
597               item.title = value.tooltip;
598             if (value.addClass)
599               item.classList.add(value.addClass);
600             separator = false;
601           } else if (!separator) {
602             menu.appendChild(MenuItem.createSeparator());
603             separator = true;
604           }
605         }
606       }
607       return menu;
608     },
609
610     /**
611      * Determines if a menu can be updated on the fly. Menus that cannot be
612      * updated are fully regenerated using createMenu. The advantage of
613      * updating a menu is that it can preserve ordering of networks avoiding
614      * entries from jumping around after an update.
615      */
616     canUpdateMenu: function() {
617       return this.data_.key == 'WiFi' && activeMenu_ == this.getMenuName();
618     },
619
620     /**
621      * Updates an existing menu.  Updated menus preserve ordering of prior
622      * entries.  During the update process, the ordering may differ from the
623      * preferred ordering as determined by the network library.  If the
624      * ordering becomes potentially out of sync, then the updated menu is
625      * marked for disposal on close.  Reopening the menu will force a
626      * regeneration, which will in turn fix the ordering.
627      * @return {boolean} True if successfully updated.
628      */
629     updateMenu: function() {
630       if (!this.canUpdateMenu())
631         return false;
632       var oldMenu = $(this.getMenuName());
633       var group = oldMenu.getElementsByClassName('network-menu-group')[0];
634       if (!group)
635         return false;
636       var newMenu = this.createMenu();
637       var discardOnClose = false;
638       var oldNetworkButtons = this.extractNetworkConnectButtons_(oldMenu);
639       var newNetworkButtons = this.extractNetworkConnectButtons_(newMenu);
640       for (var key in oldNetworkButtons) {
641         if (newNetworkButtons[key]) {
642           group.replaceChild(newNetworkButtons[key].button,
643                              oldNetworkButtons[key].button);
644           if (newNetworkButtons[key].index != oldNetworkButtons[key].index)
645             discardOnClose = true;
646           newNetworkButtons[key] = null;
647         } else {
648           // Leave item in list to prevent network items from jumping due to
649           // deletions.
650           oldNetworkButtons[key].disabled = true;
651           discardOnClose = true;
652         }
653       }
654       for (var key in newNetworkButtons) {
655         var entry = newNetworkButtons[key];
656         if (entry) {
657           group.appendChild(entry.button);
658           discardOnClose = true;
659         }
660       }
661       oldMenu.data = {discardOnClose: discardOnClose};
662       return true;
663     },
664
665     /**
666      * Extracts a mapping of network names to menu element and position.
667      * @param {!Element} menu The menu to process.
668      * @return {Object.<string, ?{index: number, button: Element}>}
669      *     Network mapping.
670      * @private
671      */
672     extractNetworkConnectButtons_: function(menu) {
673       var group = menu.getElementsByClassName('network-menu-group')[0];
674       var networkButtons = {};
675       if (!group)
676         return networkButtons;
677       var buttons = group.getElementsByClassName('network-menu-item');
678       for (var i = 0; i < buttons.length; i++) {
679         var label = buttons[i].data.label;
680         networkButtons[label] = {index: i, button: buttons[i]};
681       }
682       return networkButtons;
683     },
684
685     /**
686      * Adds a menu item for showing network details.
687      * @param {!Element} parent The parent element.
688      * @param {Object} data Description of the network.
689      * @private
690      */
691     createNetworkOptionsCallback_: function(parent, data) {
692       var menuItem = createCallback_(parent,
693                                      data,
694                                      getNetworkName(data),
695                                      'showDetails',
696                                      data.iconURL);
697       if (data.policyManaged)
698         menuItem.appendChild(new ManagedNetworkIndicator());
699       if (data.ConnectionState == 'Connected' ||
700           data.ConnectionState == 'Connecting') {
701         var label = menuItem.getElementsByClassName(
702             'network-menu-item-label')[0];
703         label.classList.add('active-network');
704       }
705     }
706   };
707
708   /**
709    * Creates a button-like control for configurating internet connectivity.
710    * @param {{key: string, subtitle: string, command: Function}} data
711    *     Description of the network control.
712    * @constructor
713    * @extends {NetworkListItem}
714    */
715   function NetworkButtonItem(data) {
716     var el = new NetworkListItem(data);
717     el.__proto__ = NetworkButtonItem.prototype;
718     el.decorate();
719     return el;
720   }
721
722   NetworkButtonItem.prototype = {
723     __proto__: NetworkListItem.prototype,
724
725     /** @override */
726     decorate: function() {
727       if (this.data.subtitle)
728         this.subtitle = this.data.subtitle;
729       else
730        this.subtitle = null;
731       if (this.data.command)
732         this.addEventListener('click', this.data.command);
733       if (this.data.iconURL)
734         this.iconURL = this.data.iconURL;
735       else if (this.data.iconType)
736         this.iconType = this.data.iconType;
737       if (this.data.policyManaged)
738         this.showManagedNetworkIndicator();
739     },
740   };
741
742   /**
743    * Adds a command to a menu for modifying network settings.
744    * @param {!Element} menu Parent menu.
745    * @param {Object} data Description of the network.
746    * @param {!string} label Display name for the menu item.
747    * @param {?(string|!Function)} command Callback function or name
748    *     of the command for |networkCommand|.
749    * @param {string=} opt_iconURL Optional URL to an icon for the menu item.
750    * @return {!Element} The created menu item.
751    * @private
752    */
753   function createCallback_(menu, data, label, command, opt_iconURL) {
754     var button = menu.ownerDocument.createElement('div');
755     button.className = 'network-menu-item';
756
757     var buttonIcon = menu.ownerDocument.createElement('div');
758     buttonIcon.className = 'network-menu-item-icon';
759     button.appendChild(buttonIcon);
760     if (opt_iconURL)
761       buttonIcon.style.backgroundImage = url(opt_iconURL);
762
763     var buttonLabel = menu.ownerDocument.createElement('span');
764     buttonLabel.className = 'network-menu-item-label';
765     buttonLabel.textContent = label;
766     button.appendChild(buttonLabel);
767     var callback = null;
768     if (typeof command == 'string') {
769       var type = data.Type;
770       var path = data.servicePath;
771       callback = function() {
772         chrome.send('networkCommand', [type, path, command]);
773         closeMenu_();
774       };
775     } else if (command != null) {
776       if (data) {
777         callback = function() {
778           (/** @type {Function} */(command))(data);
779           closeMenu_();
780         };
781       } else {
782         callback = function() {
783           (/** @type {Function} */(command))();
784           closeMenu_();
785         };
786       }
787     }
788     if (callback != null)
789       button.addEventListener('click', callback);
790     else
791       buttonLabel.classList.add('network-disabled-control');
792
793     button.data = {label: label};
794     MenuItem.decorate(button);
795     menu.appendChild(button);
796     return button;
797   }
798
799   /**
800    * A list of controls for manipulating network connectivity.
801    * @constructor
802    * @extends {cr.ui.List}
803    */
804   var NetworkList = cr.ui.define('list');
805
806   NetworkList.prototype = {
807     __proto__: List.prototype,
808
809     /** @override */
810     decorate: function() {
811       List.prototype.decorate.call(this);
812       this.startBatchUpdates();
813       this.autoExpands = true;
814       this.dataModel = new ArrayDataModel([]);
815       this.selectionModel = new ListSingleSelectionModel();
816       this.addEventListener('blur', this.onBlur_.bind(this));
817       this.selectionModel.addEventListener('change',
818                                            this.onSelectionChange_.bind(this));
819
820       // Wi-Fi control is always visible.
821       this.update({key: 'WiFi', networkList: []});
822
823       var entryAddWifi = {
824         label: loadTimeData.getString('addConnectionWifi'),
825         command: createAddConnectionCallback_('WiFi')
826       };
827       var entryAddVPN = {
828         label: loadTimeData.getString('addConnectionVPN'),
829         command: createAddConnectionCallback_('VPN')
830       };
831       this.update({key: 'addConnection',
832                    iconType: 'add-connection',
833                    menu: [entryAddWifi, entryAddVPN]
834                   });
835
836       var prefs = options.Preferences.getInstance();
837       prefs.addEventListener('cros.signed.data_roaming_enabled',
838           function(event) {
839             enableDataRoaming_ = event.value.value;
840           });
841       this.endBatchUpdates();
842     },
843
844     /**
845      * When the list loses focus, unselect all items in the list and close the
846      * active menu.
847      * @private
848      */
849     onBlur_: function() {
850       this.selectionModel.unselectAll();
851       closeMenu_();
852     },
853
854     /**
855      * Close bubble and menu when a different list item is selected.
856      * @param {Event} event Event detailing the selection change.
857      * @private
858      */
859     onSelectionChange_: function(event) {
860       PageManager.hideBubble();
861       // A list item may temporarily become unselected while it is constructing
862       // its menu. The menu should therefore only be closed if a different item
863       // is selected, not when the menu's owner item is deselected.
864       if (activeMenu_) {
865         for (var i = 0; i < event.changes.length; ++i) {
866           if (event.changes[i].selected) {
867             var item = this.dataModel.item(event.changes[i].index);
868             if (!item.getMenuName || item.getMenuName() != activeMenu_) {
869               closeMenu_();
870               return;
871             }
872           }
873         }
874       }
875     },
876
877     /**
878      * Finds the index of a network item within the data model based on
879      * category.
880      * @param {string} key Unique key for the item in the list.
881      * @return {(number|undefined)} The index of the network item, or
882      *     |undefined| if it is not found.
883      */
884     indexOf: function(key) {
885       var size = this.dataModel.length;
886       for (var i = 0; i < size; i++) {
887         var entry = this.dataModel.item(i);
888         if (entry.key == key)
889           return i;
890       }
891       return undefined;
892     },
893
894     /**
895      * Updates a network control.
896      * @param {Object.<string,string>} data Description of the entry.
897      */
898     update: function(data) {
899       this.startBatchUpdates();
900       var index = this.indexOf(data.key);
901       if (index == undefined) {
902         // Find reference position for adding the element.  We cannot hide
903         // individual list elements, thus we need to conditionally add or
904         // remove elements and cannot rely on any element having a fixed index.
905         for (var i = 0; i < Constants.NETWORK_ORDER.length; i++) {
906           if (data.key == Constants.NETWORK_ORDER[i]) {
907             data.sortIndex = i;
908             break;
909           }
910         }
911         var referenceIndex = -1;
912         for (var i = 0; i < this.dataModel.length; i++) {
913           var entry = this.dataModel.item(i);
914           if (entry.sortIndex < data.sortIndex)
915             referenceIndex = i;
916           else
917             break;
918         }
919         if (referenceIndex == -1) {
920           // Prepend to the start of the list.
921           this.dataModel.splice(0, 0, data);
922         } else if (referenceIndex == this.dataModel.length) {
923           // Append to the end of the list.
924           this.dataModel.push(data);
925         } else {
926           // Insert after the reference element.
927           this.dataModel.splice(referenceIndex + 1, 0, data);
928         }
929       } else {
930         var entry = this.dataModel.item(index);
931         data.sortIndex = entry.sortIndex;
932         this.dataModel.splice(index, 1, data);
933       }
934       this.endBatchUpdates();
935     },
936
937     /**
938      * @override
939      * @param {Object} entry
940      */
941     createItem: function(entry) {
942       if (entry.networkList)
943         return new NetworkSelectorItem(
944             /** @type {{key: string, networkList: Array.<NetworkInfo>}} */(
945                 entry));
946       if (entry.command)
947         return new NetworkButtonItem(
948             /** @type {{key: string, subtitle: string, command: Function}} */(
949                 entry));
950       if (entry.menu)
951         return new NetworkMenuItem(entry);
952       return undefined;
953     },
954
955     /**
956      * Deletes an element from the list.
957      * @param {string} key  Unique identifier for the element.
958      */
959     deleteItem: function(key) {
960       var index = this.indexOf(key);
961       if (index != undefined)
962         this.dataModel.splice(index, 1);
963     },
964
965     /**
966      * Updates the state of a toggle button.
967      * @param {string} key Unique identifier for the element.
968      * @param {boolean} active Whether the control is active.
969      */
970     updateToggleControl: function(key, active) {
971       var index = this.indexOf(key);
972       if (index != undefined) {
973         var entry = this.dataModel.item(index);
974         entry.iconType = active ? 'control-active' :
975             'control-inactive';
976         this.update(entry);
977       }
978     }
979   };
980
981   /**
982    * Sets the default icon to use for each network type if disconnected.
983    * @param {!Object.<string, string>} data Mapping of network type to icon
984    *     data url.
985    */
986   NetworkList.setDefaultNetworkIcons = function(data) {
987     defaultIcons_ = Object.create(data);
988   };
989
990   /**
991    * Chrome callback for updating network controls.
992    * @param {{wiredList: Array.<NetworkInfo>, wirelessList: Array.<NetworkInfo>,
993    *     vpnList: Array.<NetworkInfo>, rememberedList: Array.<NetworkInfo>,
994    *     wifiAvailable: boolean, wifiEnabled: boolean, wimaxAvailable: boolean,
995    *     wimaxEnabled: boolean, cellularAvailable: boolean,
996    *     cellularEnabled: boolean, cellularSupportsScan: boolean}} data
997    *     Description of available network devices and their corresponding state.
998    */
999   NetworkList.refreshNetworkData = function(data) {
1000     var networkList = $('network-list');
1001     networkList.startBatchUpdates();
1002     cellularAvailable_ = data.cellularAvailable;
1003     cellularEnabled_ = data.cellularEnabled;
1004     cellularSupportsScan_ = data.cellularSupportsScan;
1005     cellularSimAbsent_ = data.cellularSimAbsent;
1006     cellularSimLockType_ = data.cellularSimLockType;
1007     wimaxAvailable_ = data.wimaxAvailable;
1008     wimaxEnabled_ = data.wimaxEnabled;
1009
1010     // Only show Ethernet control if connected.
1011     var ethernetConnection = getConnection_(data.wiredList);
1012     if (ethernetConnection) {
1013       var type = String('Ethernet');
1014       var path = ethernetConnection.servicePath;
1015       var ethernetOptions = function() {
1016         chrome.send('networkCommand', [type, path, 'showDetails']);
1017       };
1018       networkList.update(
1019           { key: 'Ethernet',
1020             subtitle: loadTimeData.getString('OncConnectionStateConnected'),
1021             iconURL: ethernetConnection.iconURL,
1022             command: ethernetOptions,
1023             policyManaged: ethernetConnection.policyManaged }
1024           );
1025     } else {
1026       networkList.deleteItem('Ethernet');
1027     }
1028
1029     if (data.wifiEnabled)
1030       loadData_('WiFi', data.wirelessList, data.rememberedList);
1031     else
1032       addEnableNetworkButton_('WiFi');
1033
1034     // Only show cellular control if available.
1035     if (data.cellularAvailable) {
1036       if (data.cellularEnabled)
1037         loadData_('Cellular', data.wirelessList, data.rememberedList);
1038       else
1039         addEnableNetworkButton_('Cellular');
1040     } else {
1041       networkList.deleteItem('Cellular');
1042     }
1043
1044     // Only show wimax control if available. Uses cellular icons.
1045     if (data.wimaxAvailable) {
1046       if (data.wimaxEnabled)
1047         loadData_('WiMAX', data.wirelessList, data.rememberedList);
1048       else
1049         addEnableNetworkButton_('WiMAX');
1050     } else {
1051       networkList.deleteItem('WiMAX');
1052     }
1053
1054     // Only show VPN control if there is at least one VPN configured.
1055     if (data.vpnList.length > 0)
1056       loadData_('VPN', data.vpnList, data.rememberedList);
1057     else
1058       networkList.deleteItem('VPN');
1059     networkList.endBatchUpdates();
1060   };
1061
1062   /**
1063    * Replaces a network menu with a button for enabling the network type.
1064    * @param {string} type The type of network (WiFi, Cellular or Wimax).
1065    * @private
1066    */
1067   function addEnableNetworkButton_(type) {
1068     var subtitle = loadTimeData.getString('networkDisabled');
1069     var icon = (type == 'WiMAX') ? 'Cellular' : type;
1070     var enableNetwork = function() {
1071       if (type == 'WiFi')
1072         sendChromeMetricsAction('Options_NetworkWifiToggle');
1073       if (type == 'Cellular') {
1074         if (cellularSimLockType_) {
1075           chrome.send('simOperation', ['unlock']);
1076           return;
1077         } else if (cellularEnabled_ && cellularSimAbsent_) {
1078           chrome.send('simOperation', ['configure']);
1079           return;
1080         }
1081       }
1082       // TODO(stevenjb): chrome.networkingPrivate.enableNetworkType
1083       chrome.send('enableNetworkType', [type]);
1084     };
1085     $('network-list').update({key: type,
1086                               subtitle: subtitle,
1087                               iconType: icon,
1088                               command: enableNetwork});
1089   }
1090
1091   /**
1092    * Element for indicating a policy managed network.
1093    * @constructor
1094    * @extends {options.ControlledSettingIndicator}
1095    */
1096   function ManagedNetworkIndicator() {
1097     var el = cr.doc.createElement('span');
1098     el.__proto__ = ManagedNetworkIndicator.prototype;
1099     el.decorate();
1100     return el;
1101   }
1102
1103   ManagedNetworkIndicator.prototype = {
1104     __proto__: ControlledSettingIndicator.prototype,
1105
1106     /** @override */
1107     decorate: function() {
1108       ControlledSettingIndicator.prototype.decorate.call(this);
1109       this.controlledBy = 'policy';
1110       var policyLabel = loadTimeData.getString('managedNetwork');
1111       this.setAttribute('textPolicy', policyLabel);
1112       this.removeAttribute('tabindex');
1113     },
1114
1115     /** @override */
1116     handleEvent: function(event) {
1117       // Prevent focus blurring as that would close any currently open menu.
1118       if (event.type == 'mousedown')
1119         return;
1120       ControlledSettingIndicator.prototype.handleEvent.call(this, event);
1121     },
1122
1123     /**
1124      * Handle mouse events received by the bubble, preventing focus blurring as
1125      * that would close any currently open menu and preventing propagation to
1126      * any elements located behind the bubble.
1127      * @param {Event} event Mouse event.
1128      */
1129     stopEvent: function(event) {
1130       event.preventDefault();
1131       event.stopPropagation();
1132     },
1133
1134     /** @override */
1135     toggleBubble: function() {
1136       if (activeMenu_ && !$(activeMenu_).contains(this))
1137         closeMenu_();
1138       ControlledSettingIndicator.prototype.toggleBubble.call(this);
1139       if (this.showingBubble) {
1140         var bubble = PageManager.getVisibleBubble();
1141         bubble.addEventListener('mousedown', this.stopEvent);
1142         bubble.addEventListener('click', this.stopEvent);
1143       }
1144     }
1145   };
1146
1147   /**
1148    * Updates the list of available networks and their status, filtered by
1149    * network type.
1150    * @param {string} type The type of network.
1151    * @param {Array} available The list of available networks and their status.
1152    * @param {Array} remembered The list of remmebered networks.
1153    */
1154   function loadData_(type, available, remembered) {
1155     var data = {key: type};
1156     var availableNetworks = [];
1157     for (var i = 0; i < available.length; i++) {
1158       if (available[i].Type == type)
1159         availableNetworks.push(available[i]);
1160     }
1161     data.networkList = availableNetworks;
1162     if (remembered) {
1163       var rememberedNetworks = [];
1164       for (var i = 0; i < remembered.length; i++) {
1165         if (remembered[i].Type == type)
1166           rememberedNetworks.push(remembered[i]);
1167       }
1168       data.rememberedNetworks = rememberedNetworks;
1169     }
1170     $('network-list').update(data);
1171   }
1172
1173   /**
1174    * Hides the currently visible menu.
1175    * @private
1176    */
1177   function closeMenu_() {
1178     if (activeMenu_) {
1179       var menu = $(activeMenu_);
1180       menu.hidden = true;
1181       if (menu.data && menu.data.discardOnClose)
1182         menu.parentNode.removeChild(menu);
1183       activeMenu_ = null;
1184     }
1185   }
1186
1187   /**
1188    * Fetches the active connection.
1189    * @param {Array.<Object>} networkList List of networks.
1190    * @return {Object}
1191    * @private
1192    */
1193   function getConnection_(networkList) {
1194     if (!networkList)
1195       return null;
1196     for (var i = 0; i < networkList.length; i++) {
1197       var entry = networkList[i];
1198       if (entry.ConnectionState == 'Connected' ||
1199           entry.ConnectionState == 'Connecting')
1200         return entry;
1201     }
1202     return null;
1203   }
1204
1205   /**
1206    * Create a callback function that adds a new connection of the given type.
1207    * @param {string} type An ONC network type
1208    * @return {function()} The created callback.
1209    * @private
1210    */
1211   function createAddConnectionCallback_(type) {
1212     return function() {
1213       if (type == 'WiFi')
1214         sendChromeMetricsAction('Options_NetworkJoinOtherWifi');
1215       else if (type == 'VPN')
1216         sendChromeMetricsAction('Options_NetworkJoinOtherVPN');
1217       chrome.send('networkCommand', [type, '', 'add']);
1218     };
1219   }
1220
1221   /**
1222    * Whether the Network list is disabled. Only used for display purpose.
1223    */
1224   cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR);
1225
1226   // Export
1227   return {
1228     NetworkList: NetworkList
1229   };
1230 });