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