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