• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5cr.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});
1177