Upstream version 11.39.250.0
[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       } else if (this.data_.key == 'VPN') {
515         addendum.push({
516           label: loadTimeData.getString('joinOtherNetwork'),
517           command: createAddConnectionCallback_('VPN'),
518           data: {}
519         });
520       }
521
522       var list = this.data.rememberedNetworks;
523       if (list && list.length > 0) {
524         var callback = function(list) {
525           $('remembered-network-list').clear();
526           var dialog = options.PreferredNetworks.getInstance();
527           PageManager.showPageByName('preferredNetworksPage', false);
528           dialog.update(list);
529           sendChromeMetricsAction('Options_NetworkShowPreferred');
530         };
531         addendum.push({label: loadTimeData.getString('preferredNetworks'),
532                        command: callback,
533                        data: list});
534       }
535
536       var networkGroup = this.ownerDocument.createElement('div');
537       networkGroup.className = 'network-menu-group';
538       list = this.data.networkList;
539       var empty = !list || list.length == 0;
540       if (list) {
541         var connectedVpnServicePath = '';
542         for (var i = 0; i < list.length; i++) {
543           var data = list[i];
544           this.createNetworkOptionsCallback_(networkGroup, data);
545           // For VPN only, append a 'Disconnect' item to the dropdown menu.
546           if (!connectedVpnServicePath && data.Type == 'VPN' &&
547               (data.ConnectionState == 'Connected' ||
548                data.ConnectionState == 'Connecting')) {
549             connectedVpnServicePath = data.servicePath;
550           }
551         }
552         if (connectedVpnServicePath) {
553           var disconnectCallback = function() {
554             sendChromeMetricsAction('Options_NetworkDisconnectVPN');
555             // TODO(stevenjb): chrome.networkingPrivate.startDisconnect
556             chrome.send('startDisconnect', [connectedVpnServicePath]);
557           };
558           // Add separator
559           addendum.push({});
560           addendum.push({label: loadTimeData.getString('disconnectNetwork'),
561                          command: disconnectCallback,
562                          data: data});
563         }
564       }
565       if (this.data_.key == 'WiFi' || this.data_.key == 'WiMAX' ||
566           this.data_.key == 'Cellular') {
567         addendum.push({});
568         if (this.data_.key == 'WiFi') {
569           addendum.push({
570             label: loadTimeData.getString('turnOffWifi'),
571             command: function() {
572               sendChromeMetricsAction('Options_NetworkWifiToggle');
573               // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
574               chrome.send('disableNetworkType', ['WiFi']);
575             },
576             data: {}});
577         } else if (this.data_.key == 'WiMAX') {
578           addendum.push({
579             label: loadTimeData.getString('turnOffWimax'),
580             command: function() {
581               // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
582               chrome.send('disableNetworkType', ['WiMAX']);
583             },
584             data: {}});
585         } else if (this.data_.key == 'Cellular') {
586           addendum.push({
587             label: loadTimeData.getString('turnOffCellular'),
588             command: function() {
589               // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType
590               chrome.send('disableNetworkType', ['Cellular']);
591             },
592             data: {}});
593         }
594       }
595       if (!empty)
596         menu.appendChild(networkGroup);
597       if (addendum.length > 0) {
598         var separator = false;
599         if (!empty) {
600           menu.appendChild(MenuItem.createSeparator());
601           separator = true;
602         }
603         for (var i = 0; i < addendum.length; i++) {
604           var value = addendum[i];
605           if (value.data) {
606             var item = createCallback_(menu, value.data, value.label,
607                                        value.command);
608             if (value.tooltip)
609               item.title = value.tooltip;
610             if (value.addClass)
611               item.classList.add(value.addClass);
612             separator = false;
613           } else if (!separator) {
614             menu.appendChild(MenuItem.createSeparator());
615             separator = true;
616           }
617         }
618       }
619       return menu;
620     },
621
622     /**
623      * Determines if a menu can be updated on the fly. Menus that cannot be
624      * updated are fully regenerated using createMenu. The advantage of
625      * updating a menu is that it can preserve ordering of networks avoiding
626      * entries from jumping around after an update.
627      */
628     canUpdateMenu: function() {
629       return this.data_.key == 'WiFi' && activeMenu_ == this.getMenuName();
630     },
631
632     /**
633      * Updates an existing menu.  Updated menus preserve ordering of prior
634      * entries.  During the update process, the ordering may differ from the
635      * preferred ordering as determined by the network library.  If the
636      * ordering becomes potentially out of sync, then the updated menu is
637      * marked for disposal on close.  Reopening the menu will force a
638      * regeneration, which will in turn fix the ordering.
639      * @return {boolean} True if successfully updated.
640      */
641     updateMenu: function() {
642       if (!this.canUpdateMenu())
643         return false;
644       var oldMenu = $(this.getMenuName());
645       var group = oldMenu.getElementsByClassName('network-menu-group')[0];
646       if (!group)
647         return false;
648       var newMenu = this.createMenu();
649       var discardOnClose = false;
650       var oldNetworkButtons = this.extractNetworkConnectButtons_(oldMenu);
651       var newNetworkButtons = this.extractNetworkConnectButtons_(newMenu);
652       for (var key in oldNetworkButtons) {
653         if (newNetworkButtons[key]) {
654           group.replaceChild(newNetworkButtons[key].button,
655                              oldNetworkButtons[key].button);
656           if (newNetworkButtons[key].index != oldNetworkButtons[key].index)
657             discardOnClose = true;
658           newNetworkButtons[key] = null;
659         } else {
660           // Leave item in list to prevent network items from jumping due to
661           // deletions.
662           oldNetworkButtons[key].disabled = true;
663           discardOnClose = true;
664         }
665       }
666       for (var key in newNetworkButtons) {
667         var entry = newNetworkButtons[key];
668         if (entry) {
669           group.appendChild(entry.button);
670           discardOnClose = true;
671         }
672       }
673       oldMenu.data = {discardOnClose: discardOnClose};
674       return true;
675     },
676
677     /**
678      * Extracts a mapping of network names to menu element and position.
679      * @param {!Element} menu The menu to process.
680      * @return {Object.<string, ?{index: number, button: Element}>}
681      *     Network mapping.
682      * @private
683      */
684     extractNetworkConnectButtons_: function(menu) {
685       var group = menu.getElementsByClassName('network-menu-group')[0];
686       var networkButtons = {};
687       if (!group)
688         return networkButtons;
689       var buttons = group.getElementsByClassName('network-menu-item');
690       for (var i = 0; i < buttons.length; i++) {
691         var label = buttons[i].data.label;
692         networkButtons[label] = {index: i, button: buttons[i]};
693       }
694       return networkButtons;
695     },
696
697     /**
698      * Adds a menu item for showing network details.
699      * @param {!Element} parent The parent element.
700      * @param {Object} data Description of the network.
701      * @private
702      */
703     createNetworkOptionsCallback_: function(parent, data) {
704       var menuItem = createCallback_(parent,
705                                      data,
706                                      getNetworkName(data),
707                                      'showDetails',
708                                      data.iconURL);
709       if (data.policyManaged)
710         menuItem.appendChild(new ManagedNetworkIndicator());
711       if (data.ConnectionState == 'Connected' ||
712           data.ConnectionState == 'Connecting') {
713         var label = menuItem.getElementsByClassName(
714             'network-menu-item-label')[0];
715         label.classList.add('active-network');
716       }
717     }
718   };
719
720   /**
721    * Creates a button-like control for configurating internet connectivity.
722    * @param {{key: string, subtitle: string, command: Function}} data
723    *     Description of the network control.
724    * @constructor
725    * @extends {NetworkListItem}
726    */
727   function NetworkButtonItem(data) {
728     var el = new NetworkListItem(data);
729     el.__proto__ = NetworkButtonItem.prototype;
730     el.decorate();
731     return el;
732   }
733
734   NetworkButtonItem.prototype = {
735     __proto__: NetworkListItem.prototype,
736
737     /** @override */
738     decorate: function() {
739       if (this.data.subtitle)
740         this.subtitle = this.data.subtitle;
741       else
742        this.subtitle = null;
743       if (this.data.command)
744         this.addEventListener('click', this.data.command);
745       if (this.data.iconURL)
746         this.iconURL = this.data.iconURL;
747       else if (this.data.iconType)
748         this.iconType = this.data.iconType;
749       if (this.data.policyManaged)
750         this.showManagedNetworkIndicator();
751     },
752   };
753
754   /**
755    * Adds a command to a menu for modifying network settings.
756    * @param {!Element} menu Parent menu.
757    * @param {Object} data Description of the network.
758    * @param {!string} label Display name for the menu item.
759    * @param {?(string|!Function)} command Callback function or name
760    *     of the command for |networkCommand|.
761    * @param {string=} opt_iconURL Optional URL to an icon for the menu item.
762    * @return {!Element} The created menu item.
763    * @private
764    */
765   function createCallback_(menu, data, label, command, opt_iconURL) {
766     var button = menu.ownerDocument.createElement('div');
767     button.className = 'network-menu-item';
768
769     var buttonIcon = menu.ownerDocument.createElement('div');
770     buttonIcon.className = 'network-menu-item-icon';
771     button.appendChild(buttonIcon);
772     if (opt_iconURL)
773       buttonIcon.style.backgroundImage = url(opt_iconURL);
774
775     var buttonLabel = menu.ownerDocument.createElement('span');
776     buttonLabel.className = 'network-menu-item-label';
777     buttonLabel.textContent = label;
778     button.appendChild(buttonLabel);
779     var callback = null;
780     if (typeof command == 'string') {
781       var type = data.Type;
782       var path = data.servicePath;
783       callback = function() {
784         chrome.send('networkCommand', [type, path, command]);
785         closeMenu_();
786       };
787     } else if (command != null) {
788       if (data) {
789         callback = function() {
790           (/** @type {Function} */(command))(data);
791           closeMenu_();
792         };
793       } else {
794         callback = function() {
795           (/** @type {Function} */(command))();
796           closeMenu_();
797         };
798       }
799     }
800     if (callback != null)
801       button.addEventListener('click', callback);
802     else
803       buttonLabel.classList.add('network-disabled-control');
804
805     button.data = {label: label};
806     MenuItem.decorate(button);
807     menu.appendChild(button);
808     return button;
809   }
810
811   /**
812    * A list of controls for manipulating network connectivity.
813    * @constructor
814    * @extends {cr.ui.List}
815    */
816   var NetworkList = cr.ui.define('list');
817
818   NetworkList.prototype = {
819     __proto__: List.prototype,
820
821     /** @override */
822     decorate: function() {
823       List.prototype.decorate.call(this);
824       this.startBatchUpdates();
825       this.autoExpands = true;
826       this.dataModel = new ArrayDataModel([]);
827       this.selectionModel = new ListSingleSelectionModel();
828       this.addEventListener('blur', this.onBlur_.bind(this));
829       this.selectionModel.addEventListener('change',
830                                            this.onSelectionChange_.bind(this));
831
832       // Wi-Fi control is always visible.
833       this.update({key: 'WiFi', networkList: []});
834
835       var entryAddWifi = {
836         label: loadTimeData.getString('addConnectionWifi'),
837         command: createAddConnectionCallback_('WiFi')
838       };
839       var entryAddVPN = {
840         label: loadTimeData.getString('addConnectionVPN'),
841         command: createAddConnectionCallback_('VPN')
842       };
843       this.update({key: 'addConnection',
844                    iconType: 'add-connection',
845                    menu: [entryAddWifi, entryAddVPN]
846                   });
847
848       var prefs = options.Preferences.getInstance();
849       prefs.addEventListener('cros.signed.data_roaming_enabled',
850           function(event) {
851             enableDataRoaming_ = event.value.value;
852           });
853       this.endBatchUpdates();
854     },
855
856     /**
857      * When the list loses focus, unselect all items in the list and close the
858      * active menu.
859      * @private
860      */
861     onBlur_: function() {
862       this.selectionModel.unselectAll();
863       closeMenu_();
864     },
865
866     /**
867      * Close bubble and menu when a different list item is selected.
868      * @param {Event} event Event detailing the selection change.
869      * @private
870      */
871     onSelectionChange_: function(event) {
872       PageManager.hideBubble();
873       // A list item may temporarily become unselected while it is constructing
874       // its menu. The menu should therefore only be closed if a different item
875       // is selected, not when the menu's owner item is deselected.
876       if (activeMenu_) {
877         for (var i = 0; i < event.changes.length; ++i) {
878           if (event.changes[i].selected) {
879             var item = this.dataModel.item(event.changes[i].index);
880             if (!item.getMenuName || item.getMenuName() != activeMenu_) {
881               closeMenu_();
882               return;
883             }
884           }
885         }
886       }
887     },
888
889     /**
890      * Finds the index of a network item within the data model based on
891      * category.
892      * @param {string} key Unique key for the item in the list.
893      * @return {(number|undefined)} The index of the network item, or
894      *     |undefined| if it is not found.
895      */
896     indexOf: function(key) {
897       var size = this.dataModel.length;
898       for (var i = 0; i < size; i++) {
899         var entry = this.dataModel.item(i);
900         if (entry.key == key)
901           return i;
902       }
903       return undefined;
904     },
905
906     /**
907      * Updates a network control.
908      * @param {Object.<string,string>} data Description of the entry.
909      */
910     update: function(data) {
911       this.startBatchUpdates();
912       var index = this.indexOf(data.key);
913       if (index == undefined) {
914         // Find reference position for adding the element.  We cannot hide
915         // individual list elements, thus we need to conditionally add or
916         // remove elements and cannot rely on any element having a fixed index.
917         for (var i = 0; i < Constants.NETWORK_ORDER.length; i++) {
918           if (data.key == Constants.NETWORK_ORDER[i]) {
919             data.sortIndex = i;
920             break;
921           }
922         }
923         var referenceIndex = -1;
924         for (var i = 0; i < this.dataModel.length; i++) {
925           var entry = this.dataModel.item(i);
926           if (entry.sortIndex < data.sortIndex)
927             referenceIndex = i;
928           else
929             break;
930         }
931         if (referenceIndex == -1) {
932           // Prepend to the start of the list.
933           this.dataModel.splice(0, 0, data);
934         } else if (referenceIndex == this.dataModel.length) {
935           // Append to the end of the list.
936           this.dataModel.push(data);
937         } else {
938           // Insert after the reference element.
939           this.dataModel.splice(referenceIndex + 1, 0, data);
940         }
941       } else {
942         var entry = this.dataModel.item(index);
943         data.sortIndex = entry.sortIndex;
944         this.dataModel.splice(index, 1, data);
945       }
946       this.endBatchUpdates();
947     },
948
949     /**
950      * @override
951      * @param {Object} entry
952      */
953     createItem: function(entry) {
954       if (entry.networkList)
955         return new NetworkSelectorItem(
956             /** @type {{key: string, networkList: Array.<NetworkInfo>}} */(
957                 entry));
958       if (entry.command)
959         return new NetworkButtonItem(
960             /** @type {{key: string, subtitle: string, command: Function}} */(
961                 entry));
962       if (entry.menu)
963         return new NetworkMenuItem(entry);
964       return undefined;
965     },
966
967     /**
968      * Deletes an element from the list.
969      * @param {string} key  Unique identifier for the element.
970      */
971     deleteItem: function(key) {
972       var index = this.indexOf(key);
973       if (index != undefined)
974         this.dataModel.splice(index, 1);
975     },
976
977     /**
978      * Updates the state of a toggle button.
979      * @param {string} key Unique identifier for the element.
980      * @param {boolean} active Whether the control is active.
981      */
982     updateToggleControl: function(key, active) {
983       var index = this.indexOf(key);
984       if (index != undefined) {
985         var entry = this.dataModel.item(index);
986         entry.iconType = active ? 'control-active' :
987             'control-inactive';
988         this.update(entry);
989       }
990     }
991   };
992
993   /**
994    * Sets the default icon to use for each network type if disconnected.
995    * @param {!Object.<string, string>} data Mapping of network type to icon
996    *     data url.
997    */
998   NetworkList.setDefaultNetworkIcons = function(data) {
999     defaultIcons_ = Object.create(data);
1000   };
1001
1002   /**
1003    * Chrome callback for updating network controls.
1004    * @param {{wiredList: Array.<NetworkInfo>, wirelessList: Array.<NetworkInfo>,
1005    *     vpnList: Array.<NetworkInfo>, rememberedList: Array.<NetworkInfo>,
1006    *     wifiAvailable: boolean, wifiEnabled: boolean, wimaxAvailable: boolean,
1007    *     wimaxEnabled: boolean, cellularAvailable: boolean,
1008    *     cellularEnabled: boolean, cellularSupportsScan: boolean}} data
1009    *     Description of available network devices and their corresponding state.
1010    */
1011   NetworkList.refreshNetworkData = function(data) {
1012     var networkList = $('network-list');
1013     networkList.startBatchUpdates();
1014     cellularAvailable_ = data.cellularAvailable;
1015     cellularEnabled_ = data.cellularEnabled;
1016     cellularSupportsScan_ = data.cellularSupportsScan;
1017     cellularSimAbsent_ = data.cellularSimAbsent;
1018     cellularSimLockType_ = data.cellularSimLockType;
1019     wimaxAvailable_ = data.wimaxAvailable;
1020     wimaxEnabled_ = data.wimaxEnabled;
1021
1022     // Only show Ethernet control if connected.
1023     var ethernetConnection = getConnection_(data.wiredList);
1024     if (ethernetConnection) {
1025       var type = String('Ethernet');
1026       var path = ethernetConnection.servicePath;
1027       var ethernetOptions = function() {
1028         chrome.send('networkCommand', [type, path, 'showDetails']);
1029       };
1030       networkList.update(
1031           { key: 'Ethernet',
1032             subtitle: loadTimeData.getString('OncConnectionStateConnected'),
1033             iconURL: ethernetConnection.iconURL,
1034             command: ethernetOptions,
1035             policyManaged: ethernetConnection.policyManaged }
1036           );
1037     } else {
1038       networkList.deleteItem('Ethernet');
1039     }
1040
1041     if (data.wifiEnabled)
1042       loadData_('WiFi', data.wirelessList, data.rememberedList);
1043     else
1044       addEnableNetworkButton_('WiFi');
1045
1046     // Only show cellular control if available.
1047     if (data.cellularAvailable) {
1048       if (data.cellularEnabled)
1049         loadData_('Cellular', data.wirelessList, data.rememberedList);
1050       else
1051         addEnableNetworkButton_('Cellular');
1052     } else {
1053       networkList.deleteItem('Cellular');
1054     }
1055
1056     // Only show wimax control if available. Uses cellular icons.
1057     if (data.wimaxAvailable) {
1058       if (data.wimaxEnabled)
1059         loadData_('WiMAX', data.wirelessList, data.rememberedList);
1060       else
1061         addEnableNetworkButton_('WiMAX');
1062     } else {
1063       networkList.deleteItem('WiMAX');
1064     }
1065
1066     // Only show VPN control if there is at least one VPN configured.
1067     if (data.vpnList.length > 0)
1068       loadData_('VPN', data.vpnList, data.rememberedList);
1069     else
1070       networkList.deleteItem('VPN');
1071     networkList.endBatchUpdates();
1072   };
1073
1074   /**
1075    * Replaces a network menu with a button for enabling the network type.
1076    * @param {string} type The type of network (WiFi, Cellular or Wimax).
1077    * @private
1078    */
1079   function addEnableNetworkButton_(type) {
1080     var subtitle = loadTimeData.getString('networkDisabled');
1081     var icon = (type == 'WiMAX') ? 'Cellular' : type;
1082     var enableNetwork = function() {
1083       if (type == 'WiFi')
1084         sendChromeMetricsAction('Options_NetworkWifiToggle');
1085       if (type == 'Cellular') {
1086         if (cellularSimLockType_) {
1087           chrome.send('simOperation', ['unlock']);
1088           return;
1089         } else if (cellularEnabled_ && cellularSimAbsent_) {
1090           chrome.send('simOperation', ['configure']);
1091           return;
1092         }
1093       }
1094       // TODO(stevenjb): chrome.networkingPrivate.enableNetworkType
1095       chrome.send('enableNetworkType', [type]);
1096     };
1097     $('network-list').update({key: type,
1098                               subtitle: subtitle,
1099                               iconType: icon,
1100                               command: enableNetwork});
1101   }
1102
1103   /**
1104    * Element for indicating a policy managed network.
1105    * @constructor
1106    * @extends {options.ControlledSettingIndicator}
1107    */
1108   function ManagedNetworkIndicator() {
1109     var el = cr.doc.createElement('span');
1110     el.__proto__ = ManagedNetworkIndicator.prototype;
1111     el.decorate();
1112     return el;
1113   }
1114
1115   ManagedNetworkIndicator.prototype = {
1116     __proto__: ControlledSettingIndicator.prototype,
1117
1118     /** @override */
1119     decorate: function() {
1120       ControlledSettingIndicator.prototype.decorate.call(this);
1121       this.controlledBy = 'policy';
1122       var policyLabel = loadTimeData.getString('managedNetwork');
1123       this.setAttribute('textPolicy', policyLabel);
1124       this.removeAttribute('tabindex');
1125     },
1126
1127     /** @override */
1128     handleEvent: function(event) {
1129       // Prevent focus blurring as that would close any currently open menu.
1130       if (event.type == 'mousedown')
1131         return;
1132       ControlledSettingIndicator.prototype.handleEvent.call(this, event);
1133     },
1134
1135     /**
1136      * Handle mouse events received by the bubble, preventing focus blurring as
1137      * that would close any currently open menu and preventing propagation to
1138      * any elements located behind the bubble.
1139      * @param {Event} event Mouse event.
1140      */
1141     stopEvent: function(event) {
1142       event.preventDefault();
1143       event.stopPropagation();
1144     },
1145
1146     /** @override */
1147     toggleBubble: function() {
1148       if (activeMenu_ && !$(activeMenu_).contains(this))
1149         closeMenu_();
1150       ControlledSettingIndicator.prototype.toggleBubble.call(this);
1151       if (this.showingBubble) {
1152         var bubble = PageManager.getVisibleBubble();
1153         bubble.addEventListener('mousedown', this.stopEvent);
1154         bubble.addEventListener('click', this.stopEvent);
1155       }
1156     }
1157   };
1158
1159   /**
1160    * Updates the list of available networks and their status, filtered by
1161    * network type.
1162    * @param {string} type The type of network.
1163    * @param {Array} available The list of available networks and their status.
1164    * @param {Array} remembered The list of remmebered networks.
1165    */
1166   function loadData_(type, available, remembered) {
1167     var data = {key: type};
1168     var availableNetworks = [];
1169     for (var i = 0; i < available.length; i++) {
1170       if (available[i].Type == type)
1171         availableNetworks.push(available[i]);
1172     }
1173     data.networkList = availableNetworks;
1174     if (remembered) {
1175       var rememberedNetworks = [];
1176       for (var i = 0; i < remembered.length; i++) {
1177         if (remembered[i].Type == type)
1178           rememberedNetworks.push(remembered[i]);
1179       }
1180       data.rememberedNetworks = rememberedNetworks;
1181     }
1182     $('network-list').update(data);
1183   }
1184
1185   /**
1186    * Hides the currently visible menu.
1187    * @private
1188    */
1189   function closeMenu_() {
1190     if (activeMenu_) {
1191       var menu = $(activeMenu_);
1192       menu.hidden = true;
1193       if (menu.data && menu.data.discardOnClose)
1194         menu.parentNode.removeChild(menu);
1195       activeMenu_ = null;
1196     }
1197   }
1198
1199   /**
1200    * Fetches the active connection.
1201    * @param {Array.<Object>} networkList List of networks.
1202    * @return {Object}
1203    * @private
1204    */
1205   function getConnection_(networkList) {
1206     if (!networkList)
1207       return null;
1208     for (var i = 0; i < networkList.length; i++) {
1209       var entry = networkList[i];
1210       if (entry.ConnectionState == 'Connected' ||
1211           entry.ConnectionState == 'Connecting')
1212         return entry;
1213     }
1214     return null;
1215   }
1216
1217   /**
1218    * Create a callback function that adds a new connection of the given type.
1219    * @param {string} type An ONC network type
1220    * @return {function()} The created callback.
1221    * @private
1222    */
1223   function createAddConnectionCallback_(type) {
1224     return function() {
1225       if (type == 'WiFi')
1226         sendChromeMetricsAction('Options_NetworkJoinOtherWifi');
1227       else if (type == 'VPN')
1228         sendChromeMetricsAction('Options_NetworkJoinOtherVPN');
1229       chrome.send('networkCommand', [type, '', 'add']);
1230     };
1231   }
1232
1233   /**
1234    * Whether the Network list is disabled. Only used for display purpose.
1235    */
1236   cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR);
1237
1238   // Export
1239   return {
1240     NetworkList: NetworkList
1241   };
1242 });