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