| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview Builds UI elements shown in chrome://networks debugging page. |
| */ |
| const networkUI = {}; |
| |
| /** @typedef {chromeos.networkConfig.mojom.NetworkStateProperties} */ |
| networkUI.NetworkStateProperties; |
| |
| /** @typedef {chromeos.networkConfig.mojom.DeviceStateProperties} */ |
| networkUI.DeviceStateProperties; |
| |
| /** |
| * @typedef {networkUI.NetworkStateProperties|networkUI.DeviceStateProperties} |
| */ |
| networkUI.StateProperties; |
| |
| const NetworkUI = (function() { |
| 'use strict'; |
| |
| const mojom = chromeos.networkConfig.mojom; |
| |
| CrOncStrings = { |
| OncTypeCellular: loadTimeData.getString('OncTypeCellular'), |
| OncTypeEthernet: loadTimeData.getString('OncTypeEthernet'), |
| OncTypeTether: loadTimeData.getString('OncTypeTether'), |
| OncTypeVPN: loadTimeData.getString('OncTypeVPN'), |
| OncTypeWiFi: loadTimeData.getString('OncTypeWiFi'), |
| OncTypeWiMAX: loadTimeData.getString('OncTypeWiMAX'), |
| networkListItemConnected: |
| loadTimeData.getString('networkListItemConnected'), |
| networkListItemConnecting: |
| loadTimeData.getString('networkListItemConnecting'), |
| networkListItemConnectingTo: |
| loadTimeData.getString('networkListItemConnectingTo'), |
| networkListItemInitializing: |
| loadTimeData.getString('networkListItemInitializing'), |
| networkListItemScanning: loadTimeData.getString('networkListItemScanning'), |
| networkListItemNotConnected: |
| loadTimeData.getString('networkListItemNotConnected'), |
| networkListItemNoNetwork: |
| loadTimeData.getString('networkListItemNoNetwork'), |
| vpnNameTemplate: loadTimeData.getString('vpnNameTemplate'), |
| }; |
| |
| // Properties to display in the network state table. Each entry can be either |
| // a single state field or an array of state fields. If more than one is |
| // specified then the first non empty value is used. |
| const NETWORK_STATE_FIELDS = [ |
| 'guid', 'name', 'type', 'connectionState', 'connectable', 'errorState', |
| 'wifi.security', ['cellular.networkTechnology', 'EAP.EAP'], |
| 'cellular.activationState', 'cellular.roaming', 'wifi.frequency', |
| 'wifi.signalStrength' |
| ]; |
| |
| const FAVORITE_STATE_FIELDS = ['guid', 'name', 'type', 'source']; |
| |
| const DEVICE_STATE_FIELDS = ['type', 'deviceState']; |
| |
| /** |
| * This UI will use both the networkingPrivate extension API and the |
| * networkConfig mojo API until we provide all of the required functionality |
| * in networkConfig. TODO(stevenjb): Remove use of networkingPrivate api. |
| * @type {?mojom.CrosNetworkConfigProxy} |
| */ |
| let networkConfigProxy = null; |
| |
| /** |
| * Creates and returns a typed HTMLTableCellElement. |
| * |
| * @return {!HTMLTableCellElement} A new td element. |
| */ |
| const createTableCellElement = function() { |
| return /** @type {!HTMLTableCellElement} */ (document.createElement('td')); |
| }; |
| |
| /** |
| * Creates and returns a typed HTMLTableRowElement. |
| * |
| * @return {!HTMLTableRowElement} A new tr element. |
| */ |
| const createTableRowElement = function() { |
| return /** @type {!HTMLTableRowElement} */ (document.createElement('tr')); |
| }; |
| |
| /** |
| * Returns the ONC data property for |state| associated with a key. Used |
| * to access properties in the state by |key| which may may refer to a |
| * nested property, e.g. 'WiFi.Security'. If any part of a nested key is |
| * missing, this will return undefined. |
| * |
| * @param {!networkUI.StateProperties} state |
| * @param {string} key The ONC key for the property. |
| * @return {*} The value associated with the property or undefined if the |
| * key (any part of it) is not defined. |
| */ |
| const getOncProperty = function(state, key) { |
| let dict = /** @type {!Object} */ (state); |
| const keys = key.split('.'); |
| while (keys.length > 1) { |
| const k = keys.shift(); |
| dict = dict[k]; |
| if (!dict || typeof dict != 'object') |
| return undefined; |
| } |
| const k = keys.shift(); |
| return OncMojo.getTypeString(k, dict[k]); |
| }; |
| |
| /** |
| * Creates a cell with a button for expanding a network state table row. |
| * |
| * @param {!networkUI.StateProperties} state |
| * @return {!HTMLTableCellElement} The created td element that displays the |
| * given value. |
| */ |
| const createStateTableExpandButton = function(state) { |
| const cell = createTableCellElement(); |
| cell.className = 'state-table-expand-button-cell'; |
| const button = document.createElement('button'); |
| button.addEventListener('click', function(event) { |
| toggleExpandRow(/** @type {!HTMLElement} */ (event.target), state); |
| }); |
| button.className = 'state-table-expand-button'; |
| button.textContent = '+'; |
| cell.appendChild(button); |
| return cell; |
| }; |
| |
| /** |
| * Creates a cell with an icon representing the network state. |
| * |
| * @param {!networkUI.StateProperties} state |
| * @return {!HTMLTableCellElement} The created td element that displays the |
| * icon. |
| */ |
| const createStateTableIcon = function(state) { |
| const cell = createTableCellElement(); |
| cell.className = 'state-table-icon-cell'; |
| const icon = /** @type {!CrNetworkIconElement} */ ( |
| document.createElement('cr-network-icon')); |
| icon.isListItem = true; |
| icon.networkState = { |
| GUID: '', |
| Type: /** @type{chrome.networkingPrivate.NetworkType} */ ( |
| OncMojo.getNetworkTypeString(state.type)), |
| }; |
| cell.appendChild(icon); |
| return cell; |
| }; |
| |
| /** |
| * Creates a cell in the network state table. |
| * |
| * @param {*} value Content in the cell. |
| * @return {!HTMLTableCellElement} The created td element that displays the |
| * given value. |
| */ |
| const createStateTableCell = function(value) { |
| const cell = createTableCellElement(); |
| cell.textContent = value || ''; |
| return cell; |
| }; |
| |
| /** |
| * Creates a row in the network state table. |
| * |
| * @param {Array} stateFields The state fields to use for the row. |
| * @param {!networkUI.StateProperties} state |
| * @return {!HTMLTableRowElement} The created tr element that contains the |
| * network state information. |
| */ |
| const createStateTableRow = function(stateFields, state) { |
| const row = createTableRowElement(); |
| row.className = 'state-table-row'; |
| row.appendChild(createStateTableExpandButton(state)); |
| row.appendChild(createStateTableIcon(state)); |
| for (let i = 0; i < stateFields.length; ++i) { |
| const field = stateFields[i]; |
| let value; |
| if (typeof field == 'string') { |
| value = getOncProperty(state, field); |
| } else { |
| for (let j = 0; j < field.length; ++j) { |
| value = getOncProperty(state, field[j]); |
| if (value != undefined) |
| break; |
| } |
| } |
| if (field == 'guid') |
| value = value.slice(0, 8); |
| row.appendChild(createStateTableCell(value)); |
| } |
| return row; |
| }; |
| |
| /** |
| * Creates a table for networks or favorites. |
| * |
| * @param {string} tablename The name of the table to be created. |
| * @param {!Array<string>} stateFields The list of fields for the table. |
| * @param {!Array<!networkUI.StateProperties>} states |
| */ |
| const createStateTable = function(tablename, stateFields, states) { |
| const table = $(tablename); |
| const oldRows = table.querySelectorAll('.state-table-row'); |
| for (let i = 0; i < oldRows.length; ++i) |
| table.removeChild(oldRows[i]); |
| states.forEach(function(state) { |
| table.appendChild(createStateTableRow(stateFields, state)); |
| }); |
| }; |
| |
| /** |
| * Returns a valid HTMLElement id from |guid|. |
| * |
| * @param {string} guid A GUID which may start with a digit. |
| * @return {string} A valid HTMLElement id. |
| */ |
| const idFromGuid = function(guid) { |
| return '_' + guid.replace(/[{}]/g, ''); |
| }; |
| |
| /** |
| * Returns a valid HTMLElement id from |type|. Note: |type| may be a Shill |
| * type or an ONC type, so strip _ and convert to lowercase to unify them. |
| * |
| * @param {string} type A Shill or ONC network type |
| * @return {string} A valid HTMLElement id. |
| */ |
| const idFromTypeString = function(type) { |
| return '_' + type.replace(/[{}_]/g, '').toLowerCase(); |
| }; |
| |
| /** |
| * @param {!mojom.NetworkType} type |
| * @return {string} A valid HTMLElement id. |
| */ |
| const idFromType = function(type) { |
| return idFromTypeString(OncMojo.getNetworkTypeString(type)); |
| }; |
| |
| /** |
| * This callback function is triggered when visible networks are received. |
| * |
| * @param {!Array<!networkUI.NetworkStateProperties>} states |
| */ |
| const onVisibleNetworksReceived = function(states) { |
| createStateTable('network-state-table', NETWORK_STATE_FIELDS, states); |
| }; |
| |
| /** |
| * This callback function is triggered when favorite networks are received. |
| * |
| * @param {!Array<!networkUI.NetworkStateProperties>} states |
| */ |
| const onFavoriteNetworksReceived = function(states) { |
| createStateTable('favorite-state-table', FAVORITE_STATE_FIELDS, states); |
| }; |
| |
| /** |
| * This callback function is triggered when device states are received. |
| * |
| * @param {!Array<!networkUI.DeviceStateProperties>} states |
| */ |
| const onDeviceStatesReceived = function(states) { |
| createStateTable('device-state-table', DEVICE_STATE_FIELDS, states); |
| }; |
| |
| /** |
| * Toggles the button state and add or remove a row displaying the complete |
| * state information for a row. |
| * |
| * @param {!HTMLElement} btn The button that was clicked. |
| * @param {!networkUI.StateProperties} state |
| */ |
| const toggleExpandRow = function(btn, state) { |
| const cell = btn.parentNode; |
| const row = /** @type {!HTMLTableRowElement} */ (cell.parentNode); |
| if (btn.textContent == '-') { |
| btn.textContent = '+'; |
| row.parentNode.removeChild(row.nextSibling); |
| } else { |
| btn.textContent = '-'; |
| const expandedRow = createExpandedRow(state, row); |
| row.parentNode.insertBefore(expandedRow, row.nextSibling); |
| } |
| }; |
| |
| /** |
| * Creates the expanded row for displaying the complete state as JSON. |
| * |
| * @param {!networkUI.StateProperties} state |
| * @param {!HTMLTableRowElement} baseRow The unexpanded row associated with |
| * the new row. |
| * @return {!HTMLTableRowElement} The created tr element for the expanded row. |
| */ |
| const createExpandedRow = function(state, baseRow) { |
| assert(state); |
| const guid = state.guid || ''; |
| const expandedRow = createTableRowElement(); |
| expandedRow.className = 'state-table-row'; |
| const emptyCell = createTableCellElement(); |
| emptyCell.style.border = 'none'; |
| expandedRow.appendChild(emptyCell); |
| const detailCell = createTableCellElement(); |
| detailCell.id = guid ? idFromGuid(guid) : idFromType(state.type); |
| detailCell.className = 'state-table-expanded-cell'; |
| detailCell.colSpan = baseRow.childNodes.length - 1; |
| expandedRow.appendChild(detailCell); |
| const selected = $('get-property-format').selectedIndex; |
| const selectedId = $('get-property-format').options[selected].value; |
| if (guid) |
| handleNetworkDetail(guid, selectedId, detailCell); |
| else |
| handleDeviceDetail(state, selectedId, detailCell); |
| return expandedRow; |
| }; |
| |
| /** |
| * Requests network details and calls showDetail with the result. |
| * @param {string} guid |
| * @param {string} selectedId |
| * @param {!HTMLTableCellElement} detailCell |
| */ |
| const handleNetworkDetail = function(guid, selectedId, detailCell) { |
| if (selectedId == 'shill') { |
| chrome.send('getShillNetworkProperties', [guid]); |
| } else if (selectedId == 'state') { |
| networkConfigProxy.getNetworkState(guid) |
| .then((responseParams) => { |
| if (responseParams && responseParams.result) { |
| showDetail(detailCell, responseParams.result); |
| } else { |
| showDetailError( |
| detailCell, 'GetNetworkState(' + guid + ') failed'); |
| } |
| }) |
| .catch((error) => { |
| showDetailError(detailCell, 'Mojo service failure: ' + error); |
| }); |
| } else if (selectedId == 'managed') { |
| chrome.networkingPrivate.getManagedProperties(guid, function(properties) { |
| showDetail(detailCell, properties, chrome.runtime.lastError); |
| }); |
| } else { |
| chrome.networkingPrivate.getProperties(guid, function(properties) { |
| showDetail(detailCell, properties, chrome.runtime.lastError); |
| }); |
| } |
| }; |
| |
| /** |
| * Requests network details and calls showDetail with the result. |
| * @param {!networkUI.StateProperties} state |
| * @param {string} selectedId |
| * @param {!HTMLTableCellElement} detailCell |
| */ |
| const handleDeviceDetail = function(state, selectedId, detailCell) { |
| if (selectedId == 'shill') { |
| chrome.send('getShillDeviceProperties', [state.type]); |
| } else { |
| showDetail(detailCell, state); |
| } |
| }; |
| |
| /** |
| * @param {!HTMLTableCellElement} detailCell |
| * @param {!networkUI.NetworkStateProperties|!networkUI.DeviceStateProperties| |
| * !chrome.networkingPrivate.ManagedProperties| |
| * !chrome.networkingPrivate.NetworkProperties} state |
| * @param {!Object=} error |
| */ |
| const showDetail = function(detailCell, state, error) { |
| if (error && error.message) { |
| showDetailError(detailCell, error.message); |
| return; |
| } |
| detailCell.textContent = JSON.stringify(state, null, '\t'); |
| }; |
| |
| /** |
| * @param {!HTMLTableCellElement} detailCell |
| * @param {string} error |
| */ |
| const showDetailError = function(detailCell, error) { |
| detailCell.textContent = error; |
| }; |
| |
| /** |
| * Callback invoked by Chrome after a getShillNetworkProperties call. |
| * |
| * @param {Array} args The requested Shill properties. Will contain |
| * just the 'GUID' and 'ShillError' properties if the call failed. |
| */ |
| const getShillNetworkPropertiesResult = function(args) { |
| const properties = args.shift(); |
| const guid = properties['GUID']; |
| if (!guid) { |
| console.error('No GUID in getShillNetworkPropertiesResult'); |
| return; |
| } |
| |
| const detailCell = document.querySelector('td#' + idFromGuid(guid)); |
| if (!detailCell) { |
| console.error('No cell for GUID: ' + guid); |
| return; |
| } |
| |
| if (properties['ShillError']) |
| detailCell.textContent = properties['ShillError']; |
| else |
| detailCell.textContent = JSON.stringify(properties, null, '\t'); |
| }; |
| |
| /** |
| * Callback invoked by Chrome after a getShillDeviceProperties call. |
| * |
| * @param {Array} args The requested Shill properties. Will contain |
| * just the 'Type' and 'ShillError' properties if the call failed. |
| */ |
| const getShillDevicePropertiesResult = function(args) { |
| const properties = args.shift(); |
| const type = properties['Type']; |
| if (!type) { |
| console.error('No Type in getShillDevicePropertiesResult'); |
| return; |
| } |
| |
| const detailCell = document.querySelector('td#' + idFromTypeString(type)); |
| if (!detailCell) { |
| console.error('No cell for Type: ' + type); |
| return; |
| } |
| |
| if (properties['ShillError']) |
| detailCell.textContent = properties['ShillError']; |
| else |
| detailCell.textContent = JSON.stringify(properties, null, '\t'); |
| }; |
| |
| /** |
| * Callback invoked by Chrome after a openCellularActivationUi call. |
| * @param {boolean} didOpenActivationUi Whether the activation UI was actually |
| * opened. If this value is false, it means that no cellular network was |
| * available to be activated. |
| */ |
| const openCellularActivationUiResult = function(didOpenActivationUi) { |
| $('cellular-error-text').hidden = didOpenActivationUi; |
| }; |
| |
| /** |
| * Requests that the cellular activation UI be displayed. |
| */ |
| const openCellularActivationUi = function() { |
| chrome.send('openCellularActivationUi'); |
| }; |
| |
| /** |
| * Requests an update of all network info. |
| */ |
| const requestNetworks = function() { |
| networkConfigProxy |
| .getNetworkStateList({ |
| filter: mojom.FilterType.kVisible, |
| networkType: mojom.NetworkType.kAll, |
| limit: mojom.kNoLimit, |
| }) |
| .then((responseParams) => { |
| onVisibleNetworksReceived(responseParams.result); |
| }); |
| |
| networkConfigProxy |
| .getNetworkStateList({ |
| filter: mojom.FilterType.kConfigured, |
| networkType: mojom.NetworkType.kAll, |
| limit: mojom.kNoLimit, |
| }) |
| .then((responseParams) => { |
| onFavoriteNetworksReceived(responseParams.result); |
| }); |
| |
| networkConfigProxy.getDeviceStateList().then((responseParams) => { |
| onDeviceStatesReceived(responseParams.result); |
| }); |
| }; |
| |
| /** |
| * Requests the global policy dictionary and updates the page. |
| */ |
| const requestGlobalPolicy = function() { |
| chrome.networkingPrivate.getGlobalPolicy(function(policy) { |
| document.querySelector('#global-policy').textContent = |
| JSON.stringify(policy, null, '\t'); |
| }); |
| }; |
| |
| /** Initialize NetworkUI state. */ |
| const init = function() { |
| networkConfigProxy = network_config.MojoInterfaceProviderImpl.getInstance() |
| .getMojoServiceProxy(); |
| |
| /** Set the refresh rate if the interval is found in the url. */ |
| const interval = parseQueryParams(window.location)['refresh']; |
| if (interval && interval != '') |
| setInterval(requestNetworks, parseInt(interval, 10) * 1000); |
| }; |
| |
| /** |
| * Handles clicks on network items in the <cr-network-select> element by |
| * attempting a connection to the selected network or requesting a password |
| * if the network requires a password. |
| * @param {!Event<!CrOnc.NetworkStateProperties>} event |
| */ |
| const onNetworkItemSelected = function(event) { |
| const state = event.detail; |
| |
| // If the network is already connected, show network details. |
| if (state.ConnectionState == CrOnc.ConnectionState.CONNECTED) { |
| chrome.send('showNetworkDetails', [state.GUID]); |
| return; |
| } |
| |
| // If the network is not connectable, show a configuration dialog. |
| if (state.Connectable === false || state.ErrorState) { |
| chrome.send('showNetworkConfig', [state.GUID]); |
| return; |
| } |
| |
| // Otherwise, connect. |
| chrome.networkingPrivate.startConnect(state.GUID, () => { |
| const lastError = chrome.runtime.lastError; |
| if (!lastError) |
| return; |
| const message = lastError.message; |
| if (message == 'connecting' || message == 'connect-canceled' || |
| message == 'connected' || message == 'Error.InvalidNetworkGuid') { |
| return; |
| } |
| console.error( |
| 'networkingPrivate.startConnect error: ' + message + |
| ' For: ' + state.GUID); |
| chrome.send('showNetworkConfig', [state.GUID]); |
| }); |
| }; |
| |
| /** |
| * Gets network information from WebUI and sets custom items. |
| */ |
| document.addEventListener('DOMContentLoaded', function() { |
| const select = document.querySelector('cr-network-select'); |
| select.customItems = [ |
| {customItemName: 'Add WiFi', polymerIcon: 'cr:add', customData: 'WiFi'}, |
| {customItemName: 'Add VPN', polymerIcon: 'cr:add', customData: 'VPN'} |
| ]; |
| select.addEventListener('network-item-selected', onNetworkItemSelected); |
| $('cellular-activation-button').onclick = openCellularActivationUi; |
| $('refresh').onclick = requestNetworks; |
| init(); |
| requestNetworks(); |
| requestGlobalPolicy(); |
| }); |
| |
| document.addEventListener('custom-item-selected', function(event) { |
| chrome.send('addNetwork', [event.detail.customData]); |
| }); |
| |
| return { |
| getShillNetworkPropertiesResult: getShillNetworkPropertiesResult, |
| getShillDevicePropertiesResult: getShillDevicePropertiesResult, |
| openCellularActivationUiResult: openCellularActivationUiResult |
| }; |
| })(); |