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