diff options
Diffstat (limited to 'chromium/chrome/browser/resources/options/chromeos')
19 files changed, 1411 insertions, 668 deletions
diff --git a/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css b/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css index bba94eacfa5..365775759e5 100644 --- a/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css +++ b/chromium/chrome/browser/resources/options/chromeos/accounts_options_page.css @@ -24,13 +24,9 @@ width: 26px; } -.user-email-label { - -webkit-margin-start: 10px; -} - +.user-email-label, .user-name-label { -webkit-margin-start: 10px; - color: darkgray; } .user-email-name-block { @@ -43,17 +39,17 @@ .remove-user-button { background-image: -webkit-image-set( - url('../../../../../ui/resources/default_100_percent/close_2.png') 1x, - url('../../../../../ui/resources/default_200_percent/close_2.png') 2x); + url(../../../../../ui/resources/default_100_percent/close_2.png) 1x, + url(../../../../../ui/resources/default_200_percent/close_2.png) 2x); height: 16px; width: 16px; } .remove-user-button:hover { background-image: -webkit-image-set( - url('../../../../../ui/resources/default_100_percent/close_2_hover.png') + url(../../../../../ui/resources/default_100_percent/close_2_hover.png) 1x, - url('../../../../../ui/resources/default_200_percent/close_2_hover.png') + url(../../../../../ui/resources/default_200_percent/close_2_hover.png) 2x); } @@ -85,7 +81,7 @@ #ownerOnlyWarning { -webkit-padding-start: 20px; - background-image: url('warning.png'); + background-image: url(warning.png); background-repeat: no-repeat; margin-bottom: 10px; margin-top: 10px; diff --git a/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js b/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js index 018f63cf768..8bb3e3f0fbe 100644 --- a/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js +++ b/chromium/chrome/browser/resources/options/chromeos/accounts_user_list.js @@ -81,7 +81,7 @@ cr.define('options.accounts', function() { /** * Loads given user list. - * @param {!Array.<Object>} users An array of user info objects. + * @param {!Array<Object>} users An array of user info objects. * @private */ load_: function(users) { diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js b/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js index fa91e6919c2..675e08772c9 100644 --- a/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js +++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_add_device_overlay.js @@ -42,6 +42,8 @@ cr.define('options', function() { var self = this; $('bluetooth-add-device-apply-button').onclick = function(event) { + chrome.send('coreOptionsUserMetricsAction', + ['Options_BluetoothConnectNewDevice']); var device = self.deviceList_.selectedItem; var address = device.address; PageManager.closeOverlay(); diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js b/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js index 8aa6f88018e..7db87b0e94d 100644 --- a/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js +++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_device_list.js @@ -299,6 +299,9 @@ cr.define('options.system.bluetooth', function() { // forgetting the device. chrome.send('updateBluetoothDevice', [item.data.address, item.connected ? 'disconnect' : 'forget']); + + chrome.send('coreOptionsUserMetricsAction', + ['Options_BluetoothRemoveDevice']); } }, diff --git a/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js b/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js index 35c5b20e91d..63b9ee4a708 100644 --- a/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js +++ b/chromium/chrome/browser/resources/options/chromeos/bluetooth_pair_device_overlay.js @@ -27,7 +27,7 @@ cr.define('options', function() { /** * List of IDs for conditionally visible elements in the dialog. - * @type {Array.<string>} + * @type {Array<string>} * @const */ var ELEMENTS = ['bluetooth-pairing-passkey-display', @@ -233,7 +233,7 @@ cr.define('options', function() { /** * Updates the visibility of elements in the dialog. - * @param {Array.<string>} list List of conditionally visible elements that + * @param {Array<string>} list List of conditionally visible elements that * are to be made visible. * @private */ diff --git a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css index d68291a6e9c..9ca91f86f46 100644 --- a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css +++ b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.css @@ -141,7 +141,7 @@ #flip-photo { -webkit-transition: opacity 75ms linear; - background: url('chrome://theme/IDR_MIRROR_FLIP') no-repeat; + background: url(chrome://theme/IDR_MIRROR_FLIP) no-repeat; border: none; bottom: 44px; /* 8px + image bottom. */ display: block; @@ -178,13 +178,13 @@ html[dir=rtl] #flip-photo { } .camera:not(.live) #discard-photo { - background: url('chrome://theme/IDR_USER_IMAGE_RECYCLE') + background: url(chrome://theme/IDR_USER_IMAGE_RECYCLE) no-repeat center center; display: block; } .camera.live.online #take-photo { - background: url('chrome://theme/IDR_USER_IMAGE_CAPTURE') + background: url(chrome://theme/IDR_USER_IMAGE_CAPTURE) no-repeat center -1px; display: block; } diff --git a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js index 506e483c12d..de6670a4c7b 100644 --- a/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js +++ b/chromium/chrome/browser/resources/options/chromeos/change_picture_options.js @@ -10,7 +10,7 @@ cr.define('options', function() { /** * Array of button URLs used on this page. - * @type {Array.<string>} + * @type {Array<string>} * @const */ var ButtonImageUrls = [ @@ -303,19 +303,13 @@ cr.define('options', function() { /** * Appends default images to the image grid. Should only be called once. - * @param {Array.<{url: string, author: string, website: string}>} + * @param {Array<{url: string, author: string, website: string}>} * imagesData An array of default images data, including URL, author and * website. * @private */ setDefaultImages_: function(imagesData) { - var imageGrid = $('user-image-grid'); - for (var i = 0, data; data = imagesData[i]; i++) { - var item = imageGrid.addItem(data.url, data.title); - item.type = 'default'; - item.author = data.author || ''; - item.website = data.website || ''; - } + $('user-image-grid').setDefaultImages(imagesData); }, }; diff --git a/chromium/chrome/browser/resources/options/chromeos/display_options.css b/chromium/chrome/browser/resources/options/chromeos/display_options.css index 3f738791e5c..73a78e45833 100644 --- a/chromium/chrome/browser/resources/options/chromeos/display_options.css +++ b/chromium/chrome/browser/resources/options/chromeos/display_options.css @@ -20,16 +20,10 @@ width: 100%; } -#display-options-displays-view-mirroring { - margin: 20px 0 20px 0; -} - #display-configurations { -webkit-padding-end: 0; -webkit-padding-start: 15px; - background-color: white; border-top: 1px solid lightgrey; - padding-bottom: 15px; padding-top: 15px; } @@ -38,7 +32,6 @@ * upper-half, which were left/top before the rotation. */ #display-configuration-arrow { -webkit-transform: rotate(45deg); - background-color: white; border-left: 1px solid lightgrey; border-top: 1px solid lightgrey; height: 20px; @@ -47,6 +40,19 @@ z-index: 1; } +#display-configurations, +#display-configuration-arrow, +#display-options-page .action-area { + background-color: white; +} + +#display-options-page .action-area { + /* Because this element has a background-color, we need to emulate the + * parent's border-radius (otherwise there's sharp corners). */ + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; +} + #selected-display-data-container { z-index: 2; } diff --git a/chromium/chrome/browser/resources/options/chromeos/display_options.html b/chromium/chrome/browser/resources/options/chromeos/display_options.html index 87d23de74c5..8e1f9d049b6 100644 --- a/chromium/chrome/browser/resources/options/chromeos/display_options.html +++ b/chromium/chrome/browser/resources/options/chromeos/display_options.html @@ -16,6 +16,18 @@ </button> <button id="display-options-set-primary" class="display-options-button" i18n-content="setPrimary"> + </button> + </div> + <div id="display-options-unified-desktop" + class="checkbox selected-display-option-row" hidden> + <!-- intentionally blank for the title column space. --> + <div class="selected-display-option-title"> + </div> + <label> + <input id="display-options-toggle-unified-desktop" type="checkbox"> + <span class="controlled-setting-with-label" + i18n-content="enableUnifiedDesktop"></span> + </label> </div> <div class="selected-display-option-row"> <div class="selected-display-option-title" @@ -47,7 +59,7 @@ </button> </div> <div class="selected-display-option-row" - id="selected-display-color-profile-row"> + id="selected-display-color-profile-row" hidden> <div class="selected-display-option-title" i18n-content="selectedDisplayColorProfile"> </div> @@ -62,4 +74,10 @@ <div id="display-configuration-arrow"> </div> </div> + <div class="action-area"> + <div class="button-strip"> + <button id="display-options-done" i18n-content="done" + class="default-button"></button> + </div> + </div> </div> diff --git a/chromium/chrome/browser/resources/options/chromeos/display_options.js b/chromium/chrome/browser/resources/options/chromeos/display_options.js index 756e564534b..bf316214fe8 100644 --- a/chromium/chrome/browser/resources/options/chromeos/display_options.js +++ b/chromium/chrome/browser/resources/options/chromeos/display_options.js @@ -6,13 +6,13 @@ cr.exportPath('options'); /** * @typedef {{ - * availableColorProfiles: Array.<{profileId: number, name: string}>, + * availableColorProfiles: Array<{profileId: number, name: string}>, * colorProfile: number, * height: number, * id: string, * isInternal: boolean, * isPrimary: boolean, - * resolutions: Array.<{width: number, height: number, originalWidth: number, + * resolutions: Array<{width: number, height: number, originalWidth: number, * originalHeight: number, deviceScaleFactor: number, scale: number, * refreshRate: number, isBest: boolean, selected: boolean}>, * name: string, @@ -36,6 +36,17 @@ options.SecondaryDisplayLayout = { LEFT: 3 }; +/** + * Enumeration of multi display mode. The value has to be same as the + * values in ash/display/display_manager.. + * @enum {number} + */ +options.MultiDisplayMode = { + EXTENDED: 0, + MIRRORING: 1, + UNIFIED: 2, +}; + cr.define('options', function() { var Page = cr.ui.pageManager.Page; var PageManager = cr.ui.pageManager.PageManager; @@ -116,6 +127,18 @@ cr.define('options', function() { */ mirroring_: false, + /* + * Whether the unified desktop is enable or not. + * @private + */ + unifiedDesktopEnabled_: false, + + /* + * Whether the unified desktop option should be present. + * @private + */ + showUnifiedDesktopOption_: false, + /** * The current secondary display layout. * @private @@ -125,7 +148,7 @@ cr.define('options', function() { /** * The array of current output displays. It also contains the display * rectangles currently rendered on screen. - * @type {Array.<options.DisplayInfo>} + * @type {Array<options.DisplayInfo>} * @private */ displays_: [], @@ -170,6 +193,12 @@ cr.define('options', function() { */ lastTouchLocation_: null, + /** + * Whether the display settings can be shown. + * @private + */ + enabled_: true, + /** @override */ initializePage: function() { Page.prototype.initializePage.call(this); @@ -210,6 +239,16 @@ cr.define('options', function() { chrome.send('coreOptionsUserMetricsAction', ['Options_DisplaySetOverscan']); }).bind(this); + + $('display-options-done').onclick = function() { + PageManager.closeOverlay(); + }; + + $('display-options-toggle-unified-desktop').onclick = (function() { + this.unifiedDesktopEnabled_ = !this.unifiedDesktopEnabled_; + chrome.send('setUnifiedDesktopEnabled', + [this.unifiedDesktopEnabled_]); + }).bind(this); }, /** @override */ @@ -224,6 +263,29 @@ cr.define('options', function() { chrome.send('getDisplayInfo'); }, + /** @override */ + canShowPage: function() { + return this.enabled_; + }, + + /** + * Enables or disables the page. When disabled, the page will not be able to + * open, and will close if currently opened. + * @param {boolean} enabled Whether the page should be enabled. + * @param {boolean} showUnifiedDesktop Whether the unified desktop option + * should be present. + */ + setEnabled: function(enabled, showUnifiedDesktop) { + if (this.enabled_ == enabled && + this.showUnifiedDesktopOption_ == showUnifiedDesktop) { + return; + } + this.enabled_ = enabled; + this.showUnifiedDesktopOption_ = showUnifiedDesktop; + if (!enabled && this.visible) + PageManager.closeOverlay(); + }, + /** * Mouse move handler for dragging display rectangle. * @param {Event} e The mouse move event. @@ -743,8 +805,6 @@ cr.define('options', function() { var totalHeight = height + numDisplays * MIRRORING_OFFSET_PIXELS; this.displaysView_.style.height = totalHeight + 'px'; - this.displaysView_.classList.add( - 'display-options-displays-view-mirroring'); // The displays should be centered. var offsetX = @@ -766,6 +826,37 @@ cr.define('options', function() { }, /** + * Creates a div element representing the specified display. + * @param {Object} display The display object. + * @param {boolean} focused True if it's focused. + * @private + */ + createDisplayRectangle_: function(display, focused) { + var div = document.createElement('div'); + display.div = div; + div.className = 'displays-display'; + if (focused) + div.classList.add('displays-focused'); + + // div needs to be added to the DOM tree first, otherwise offsetHeight for + // nameContainer below cannot be computed. + this.displaysView_.appendChild(div); + + var nameContainer = document.createElement('div'); + nameContainer.textContent = display.name; + div.appendChild(nameContainer); + div.style.width = Math.floor(display.width * this.visualScale_) + 'px'; + var newHeight = Math.floor(display.height * this.visualScale_); + div.style.height = newHeight + 'px'; + nameContainer.style.marginTop = + (newHeight - nameContainer.offsetHeight) / 2 + 'px'; + + div.onmousedown = this.onMouseDown_.bind(this); + div.ontouchstart = this.onTouchStart_.bind(this); + return div; + }, + + /** * Layouts the display rectangles according to the current layout_. * @private */ @@ -773,8 +864,18 @@ cr.define('options', function() { var maxWidth = 0; var maxHeight = 0; var boundingBox = {left: 0, right: 0, top: 0, bottom: 0}; + this.primaryDisplay_ = null; + this.secondaryDisplay_ = null; + var focusedDisplay = null; for (var i = 0; i < this.displays_.length; i++) { var display = this.displays_[i]; + if (display.isPrimary) + this.primaryDisplay_ = display; + else + this.secondaryDisplay_ = display; + if (i == this.focusedIndex_) + focusedDisplay = display; + boundingBox.left = Math.min(boundingBox.left, display.x); boundingBox.right = Math.max( boundingBox.right, display.x + display.width); @@ -784,6 +885,8 @@ cr.define('options', function() { maxWidth = Math.max(maxWidth, display.width); maxHeight = Math.max(maxHeight, display.height); } + if (!this.primaryDisplay_) + return; // Make the margin around the bounding box. var areaWidth = boundingBox.right - boundingBox.left + maxWidth; @@ -798,13 +901,6 @@ cr.define('options', function() { this.displaysView_.style.height = Math.ceil(areaHeight * this.visualScale_) + 'px'; - var boundingCenter = { - x: Math.floor((boundingBox.right + boundingBox.left) * - this.visualScale_ / 2), - y: Math.floor((boundingBox.bottom + boundingBox.top) * - this.visualScale_ / 2) - }; - // Centering the bounding box of the display rectangles. var offset = { x: Math.floor(this.displaysView_.offsetWidth / 2 - @@ -813,59 +909,70 @@ cr.define('options', function() { (boundingBox.bottom + boundingBox.top) * this.visualScale_ / 2) }; - for (var i = 0; i < this.displays_.length; i++) { - var display = this.displays_[i]; - var div = document.createElement('div'); - display.div = div; - - div.className = 'displays-display'; - if (i == this.focusedIndex_) - div.classList.add('displays-focused'); - - if (display.isPrimary) { - this.primaryDisplay_ = display; - } else { - this.secondaryDisplay_ = display; + // Layouting the display rectangles. First layout the primaryDisplay and + // then layout the secondary which is attaching to the primary. + var primaryDiv = this.createDisplayRectangle_( + this.primaryDisplay_, this.primaryDisplay_ == focusedDisplay); + primaryDiv.style.left = + Math.floor(this.primaryDisplay_.x * this.visualScale_) + + offset.x + 'px'; + primaryDiv.style.top = + Math.floor(this.primaryDisplay_.y * this.visualScale_) + + offset.y + 'px'; + this.primaryDisplay_.originalPosition = { + x: primaryDiv.offsetLeft, y: primaryDiv.offsetTop}; + + if (this.secondaryDisplay_) { + var secondaryDiv = this.createDisplayRectangle_( + this.secondaryDisplay_, this.secondaryDisplay_ == focusedDisplay); + // Don't trust the secondary display's x or y, because it may cause a + // 1px gap due to rounding, which will create a fake update on end + // dragging. See crbug.com/386401 + switch (this.layout_) { + case options.SecondaryDisplayLayout.TOP: + secondaryDiv.style.left = + Math.floor(this.secondaryDisplay_.x * this.visualScale_) + + offset.x + 'px'; + secondaryDiv.style.top = + primaryDiv.offsetTop - secondaryDiv.offsetHeight + 'px'; + break; + case options.SecondaryDisplayLayout.RIGHT: + secondaryDiv.style.left = + primaryDiv.offsetLeft + primaryDiv.offsetWidth + 'px'; + secondaryDiv.style.top = + Math.floor(this.secondaryDisplay_.y * this.visualScale_) + + offset.y + 'px'; + break; + case options.SecondaryDisplayLayout.BOTTOM: + secondaryDiv.style.left = + Math.floor(this.secondaryDisplay_.x * this.visualScale_) + + offset.x + 'px'; + secondaryDiv.style.top = + primaryDiv.offsetTop + primaryDiv.offsetHeight + 'px'; + break; + case options.SecondaryDisplayLayout.LEFT: + secondaryDiv.style.left = + primaryDiv.offsetLeft - secondaryDiv.offsetWidth + 'px'; + secondaryDiv.style.top = + Math.floor(this.secondaryDisplay_.y * this.visualScale_) + + offset.y + 'px'; + break; } - var displayNameContainer = document.createElement('div'); - displayNameContainer.textContent = display.name; - div.appendChild(displayNameContainer); - display.nameContainer = displayNameContainer; - display.div.style.width = - Math.floor(display.width * this.visualScale_) + 'px'; - var newHeight = Math.floor(display.height * this.visualScale_); - display.div.style.height = newHeight + 'px'; - div.style.left = - Math.floor(display.x * this.visualScale_) + offset.x + 'px'; - div.style.top = - Math.floor(display.y * this.visualScale_) + offset.y + 'px'; - display.nameContainer.style.marginTop = - (newHeight - display.nameContainer.offsetHeight) / 2 + 'px'; - - div.onmousedown = this.onMouseDown_.bind(this); - div.ontouchstart = this.onTouchStart_.bind(this); - - this.displaysView_.appendChild(div); - - // Set the margin top to place the display name at the middle of the - // rectangle. Note that this has to be done after it's added into the - // |displaysView_|. Otherwise its offsetHeight is yet 0. - displayNameContainer.style.marginTop = - (div.offsetHeight - displayNameContainer.offsetHeight) / 2 + 'px'; - display.originalPosition = {x: div.offsetLeft, y: div.offsetTop}; + this.secondaryDisplay_.originalPosition = { + x: secondaryDiv.offsetLeft, y: secondaryDiv.offsetTop}; } }, /** * Called when the display arrangement has changed. - * @param {boolean} mirroring Whether current mode is mirroring or not. - * @param {Array.<options.DisplayInfo>} displays The list of the display + * @param {options.MultiDisplayMode} multi display mode. + * @param {Array<options.DisplayInfo>} displays The list of the display * information. * @param {options.SecondaryDisplayLayout} layout The layout strategy. * @param {number} offset The offset of the secondary display. * @private */ - onDisplayChanged_: function(mirroring, displays, layout, offset) { + onDisplayChanged_: function(mode, displays, layout, offset) { if (!this.visible) return; @@ -879,20 +986,25 @@ cr.define('options', function() { this.layout_ = layout; + var mirroring = mode == options.MultiDisplayMode.MIRRORING; + var unifiedDesktopEnabled = mode == options.MultiDisplayMode.UNIFIED; + $('display-options-toggle-mirroring').textContent = loadTimeData.getString( mirroring ? 'stopMirroring' : 'startMirroring'); // Focus to the first display next to the primary one when |displays| list // is updated. - if (mirroring) { + if (mirroring || unifiedDesktopEnabled) { this.focusedIndex_ = null; } else if (this.mirroring_ != mirroring || + this.unifiedDesktopEnabled_ != unifiedDesktopEnabled || this.displays_.length != displays.length) { this.focusedIndex_ = 0; } this.mirroring_ = mirroring; + this.unifiedDesktopEnabled_ = unifiedDesktopEnabled; this.displays_ = displays; this.resetDisplaysView_(); @@ -901,14 +1013,28 @@ cr.define('options', function() { else this.layoutDisplays_(); + $('display-options-unified-desktop').hidden = + !this.showUnifiedDesktopOption_; + + $('display-options-toggle-unified-desktop').checked = + this.unifiedDesktopEnabled_; + + var disableUnifiedDesktopOption = + (this.mirroring_ || + (!this.unifiedDesktopEnabled_ && + this.displays_.length == 1)); + + $('display-options-toggle-unified-desktop').disabled = + disableUnifiedDesktopOption; + this.updateSelectedDisplayDescription_(); } }; DisplayOptions.setDisplayInfo = function( - mirroring, displays, layout, offset) { + mode, displays, layout, offset) { DisplayOptions.getInstance().onDisplayChanged_( - mirroring, displays, layout, offset); + mode, displays, layout, offset); }; // Export diff --git a/chromium/chrome/browser/resources/options/chromeos/display_overscan.css b/chromium/chrome/browser/resources/options/chromeos/display_overscan.css index 3a09e408e12..6069a806044 100644 --- a/chromium/chrome/browser/resources/options/chromeos/display_overscan.css +++ b/chromium/chrome/browser/resources/options/chromeos/display_overscan.css @@ -28,8 +28,8 @@ #display-overscan-operation-arrows { background-image: -webkit-image-set( - url('overscan_arrows.png') 1x, - url('overscan_arrows_2x.png') 2x); + url(overscan_arrows.png) 1x, + url(overscan_arrows_2x.png) 2x); background-position: center; background-repeat: no-repeat; height: 51px; @@ -38,8 +38,8 @@ #display-overscan-operation-shift { background-image: -webkit-image-set( - url('overscan_shift.png') 1x, - url('overscan_shift_2x.png') 2x); + url(overscan_shift.png) 1x, + url(overscan_shift_2x.png) 2x); background-position: center; background-repeat: no-repeat; height: 23px; @@ -48,8 +48,8 @@ html[dir=rtl] #display-overscan-operation-shift { background-image: -webkit-image-set( - url('overscan_shift_rtl.png') 1x, - url('overscan_shift_rtl_2x.png') 2x); + url(overscan_shift_rtl.png) 1x, + url(overscan_shift_rtl_2x.png) 2x); } #display-overscan-button-strip { diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail.css b/chromium/chrome/browser/resources/options/chromeos/internet_detail.css index a027a56259f..1528bc08781 100644 --- a/chromium/chrome/browser/resources/options/chromeos/internet_detail.css +++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail.css @@ -53,6 +53,14 @@ padding: 4px; } +#vpn-tab.third-party-vpn-provider tr.built-in-vpn-provider-only { + display: none; +} + +#vpn-tab:not(.third-party-vpn-provider) tr.third-party-vpn-provider-only { + display: none; +} + #ip-config-list { min-height: 96px !important; } diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail.html b/chromium/chrome/browser/resources/options/chromeos/internet_detail.html index 788c41ae149..09e25672699 100644 --- a/chromium/chrome/browser/resources/options/chromeos/internet_detail.html +++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail.html @@ -73,7 +73,8 @@ <span> <span i18n-content="inetPreferredNetwork"></span> <span class="controlled-setting-indicator" - managed="Priority"></span> + managed="Priority" + internet-detail-for="prefer-network-wifi"></span> </span> </label> </div> @@ -87,7 +88,9 @@ <span> <span i18n-content="inetAutoConnectNetwork"></span> <span class="controlled-setting-indicator" - managed="WiFi.AutoConnect"></span> + managed="WiFi.AutoConnect" + internet-detail-for="auto-connect-network-wifi"> + </span> </span> </label> </div> @@ -157,7 +160,9 @@ <span> <span i18n-content="inetAutoConnectNetwork"></span> <span class="controlled-setting-indicator" - managed="WiMAX.AutoConnect"></span> + managed="WiMAX.AutoConnect" + internet-detail-for="auto-connect-network-wimax"> + </span> </span> </label> </div> @@ -198,7 +203,7 @@ <div id="vpn-tab" class="subpages-tab-contents vpn-details"> <section> <table class="option-control-table"> - <tr class="auto-connect-network"> + <tr class="auto-connect-network built-in-vpn-provider-only"> <td> <div class="checkbox controlled-setting-with-label"> <label> @@ -206,7 +211,8 @@ <span> <span i18n-content="inetAutoConnectNetwork"></span> <span class="controlled-setting-indicator" - managed="VPN.AutoConnect"></span> + managed="VPN.AutoConnect" + internet-detail-for="auto-connect-network-vpn"></span> </span> </label> </div> @@ -216,19 +222,24 @@ <td class="option-name" i18n-content="inetServiceName"></td> <td id="inet-service-name" class="option-value"></td> </tr> - <tr> + <tr class="built-in-vpn-provider-only"> <td class="option-name" i18n-content="inetServerHostname"></td> <td> <input class="option-value" id="inet-server-hostname"></input> <span class="controlled-setting-indicator" - managed="VPN.Host"></span> + managed="VPN.Host" + internet-detail-for="inet-server-hostname"></span> </td> </tr> <tr> <td class="option-name" i18n-content="inetProviderType"></td> <td id="inet-provider-type" class="option-value"></td> </tr> - <tr> + <tr class="third-party-vpn-provider-only"> + <td class="option-name" i18n-content="inetProviderName"></td> + <td id="inet-provider-name" class="option-value"></td> + </tr> + <tr class="built-in-vpn-provider-only"> <td class="option-name" i18n-content="inetUsername"></td> <td id="inet-username" class="option-value"></td> </tr> @@ -283,7 +294,8 @@ </option> </select> <span class="controlled-setting-indicator" - managed="Cellular.APN"></span> + managed="Cellular.APN" + internet-detail-for="select-apn"></span> </td> </tr> <tr class="gsm-only apn-details-view"> @@ -323,7 +335,9 @@ <span> <span i18n-content="inetAutoConnectNetwork"></span> <span class="controlled-setting-indicator" - managed="Cellular.AutoConnect"></span> + managed="Cellular.AutoConnect" + internet-detail-for="auto-connect-network-cellular"> + </span> </span> </label> </div> @@ -409,7 +423,7 @@ <span> <span i18n-content="ipAutomaticConfiguration"></span> <span class="controlled-setting-indicator" - managed="StaticIPConfig.IPAddress"></span> + managed="IPAddressConfigType"></span> </span> </label> </div> @@ -438,7 +452,11 @@ <label> <input id="automatic-dns-radio" type="radio" name="dnstype" value="automatic"> - <span i18n-content="automaticNameServers"></span> + <span> + <span i18n-content="automaticNameServers"></span> + <span class="controlled-setting-indicator" + managed="NameServersConfigType"></span> + </span> </label> </div> <div id="automatic-dns-display" class="dns-display"></div> @@ -497,7 +515,8 @@ <span> <span i18n-content="lockSimCard"></span> <span class="controlled-setting-indicator" - managed="Cellular.SIMLockStatus.LockEnabled"></span> + managed="Cellular.SIMLockStatus.LockEnabled" + internet-detail-for="sim-card-lock-enabled"></span> </span> </label> </div> @@ -506,7 +525,8 @@ <div id="change-pin-area"> <button id="change-pin" i18n-content="changePinButton"></button> <span class="controlled-setting-indicator" - managed="Cellular.SIMLockStatus.LockType"></span> + managed="Cellular.SIMLockStatus.LockType" + internet-detail-for="change-pin"></span> </div> </section> </div> diff --git a/chromium/chrome/browser/resources/options/chromeos/internet_detail.js b/chromium/chrome/browser/resources/options/chromeos/internet_detail.js index 3fdc203184e..fa636df9ad9 100644 --- a/chromium/chrome/browser/resources/options/chromeos/internet_detail.js +++ b/chromium/chrome/browser/resources/options/chromeos/internet_detail.js @@ -7,27 +7,19 @@ // NOTE(stevenjb): This code is in the process of being converted to be // compatible with the networkingPrivate extension API: // * The network property dictionaries are being converted to use ONC values. -// * chrome.send calls will be replaced with an API object that simulates the -// networkingPrivate API. See network_config.js. +// * chrome.send calls will be replaced with chrome.networkingPrivate calls. // See crbug.com/279351 for more info. -/** @typedef {{address: (string|undefined), - * gateway: (string|undefined), - * nameServers: (string|undefined), - * netmask: (string|undefined), - * prefixLength: (number|undefined)}} - * @see chrome/browser/ui/webui/options/chromeos/internet_options_handler.cc - */ -var IPInfo; - cr.define('options.internet', function() { var OncData = cr.onc.OncData; var Page = cr.ui.pageManager.Page; var PageManager = cr.ui.pageManager.PageManager; /** @const */ var IPAddressField = options.internet.IPAddressField; - /** @const */ var GoogleNameServersString = '8.8.4.4,8.8.8.8'; + /** @const */ var GoogleNameServers = ['8.8.4.4', '8.8.8.8']; /** @const */ var CarrierGenericUMTS = 'Generic UMTS'; + /** @const */ var CarrierSprint = 'Sprint'; + /** @const */ var CarrierVerizon = 'Verizon Wireless'; /** * Helper function to set hidden attribute for elements matching a selector. @@ -88,22 +80,6 @@ cr.define('options.internet', function() { } /** - * Sends the 'checked' state of a control to chrome for a network. - * @param {string} path The service path of the network. - * @param {string} message The message to send to chrome. - * @param {string} checkboxId The id of the checkbox with the value to send. - * @param {string=} opt_action Optional action to record. - */ - function sendCheckedIfEnabled(path, message, checkboxId, opt_action) { - var checkbox = assertInstanceof($(checkboxId), HTMLInputElement); - if (!checkbox.hidden && !checkbox.disabled) { - chrome.send(message, [path, !!checkbox.checked]); - if (opt_action) - sendChromeMetricsAction(opt_action); - } - } - - /** * Send metrics to Chrome when the detailed page is opened. * @param {string} type The ONC type of the network being shown. * @param {string} state The ONC network state. @@ -152,6 +128,78 @@ cr.define('options.internet', function() { return netmask; } + /** + * Returns the prefix length from the netmask string. + * @param {string} netmask The netmask string, e.g. 255.255.255.0. + * @return {number} The corresponding netmask or -1 if invalid. + */ + function netmaskToPrefixLength(netmask) { + var prefixLength = 0; + var tokens = netmask.split('.'); + if (tokens.length != 4) + return -1; + for (var i = 0; i < tokens.length; ++i) { + var token = tokens[i]; + // If we already found the last mask and the current one is not + // '0' then the netmask is invalid. For example, 255.224.255.0 + if (prefixLength / 8 != i) { + if (token != '0') + return -1; + } else if (token == '255') { + prefixLength += 8; + } else if (token == '254') { + prefixLength += 7; + } else if (token == '252') { + prefixLength += 6; + } else if (token == '248') { + prefixLength += 5; + } else if (token == '240') { + prefixLength += 4; + } else if (token == '224') { + prefixLength += 3; + } else if (token == '192') { + prefixLength += 2; + } else if (token == '128') { + prefixLength += 1; + } else if (token == '0') { + prefixLength += 0; + } else { + // mask is not a valid number. + return -1; + } + } + return prefixLength; + } + + // Returns true if we should show the 'View Account' button for |onc|. + // TODO(stevenjb): We should query the Mobile Config API for whether or not to + // show the 'View Account' button once it is integrated with Settings. + function shouldShowViewAccountButton(onc) { + var activationState = onc.getActiveValue('Cellular.ActivationState'); + if (activationState != 'Activating' && activationState != 'Activated') + return false; + + // If no online payment URL was provided by Shill, only show 'View Account' + // for Verizon Wireless. + if (!onc.getActiveValue('Cellular.PaymentPortal.Url') && + onc.getActiveValue('Cellular.Carrier') != CarrierVerizon) { + return false; + } + + // 'View Account' should only be shown for connected networks, or + // disconnected LTE networks with a valid MDN. + var connectionState = onc.getActiveValue('ConnectionState'); + if (connectionState != 'Connected') { + var technology = onc.getActiveValue('Cellular.NetworkTechnology'); + if (technology != 'LTE' && technology != 'LTEAdvanced') + return false; + if (!onc.getActiveValue('Cellular.MDN')) + return false; + } + + return true; + } + ///////////////////////////////////////////////////////////////////////////// // DetailsInternetPage class: @@ -161,10 +209,16 @@ cr.define('options.internet', function() { * @extends {cr.ui.pageManager.Page} */ function DetailsInternetPage() { - // Cached Apn properties + // If non-negative, indicates a custom entry in select-apn. this.userApnIndex_ = -1; - this.selectedApnIndex_ = -1; + + // The custom APN properties associated with entry |userApnIndex_|. this.userApn_ = {}; + + // The currently selected APN entry in $('select-apn') (which may or may not + // == userApnIndex_). + this.selectedApnIndex_ = -1; + // We show the Proxy configuration tab for remembered networks and when // configuring a proxy from the login screen. this.showProxy_ = false; @@ -181,20 +235,40 @@ cr.define('options.internet', function() { initializePage: function() { Page.prototype.initializePage.call(this); this.initializePageContents_(); + + chrome.networkingPrivate.onNetworksChanged.addListener( + this.onNetworksChanged_.bind(this)); + this.showNetworkDetails_(); }, /** - * Auto-activates the network details dialog if network information + * Automatically shows the network details dialog if network information * is included in the URL. */ showNetworkDetails_: function() { - var servicePath = parseQueryParams(window.location).servicePath; - if (!servicePath || !servicePath.length) + var guid = parseQueryParams(window.location).guid; + if (!guid || !guid.length) + return; + chrome.send('loadVPNProviders'); + chrome.networkingPrivate.getManagedProperties( + guid, DetailsInternetPage.initializeDetailsPage); + }, + + /** + * networkingPrivate callback when networks change. + * @param {Array<string>} changes List of GUIDs whose properties have + * changed. + * @private + */ + onNetworksChanged_: function(changes) { + if (!this.onc_) return; - // TODO(stevenjb): chrome.networkingPrivate.getManagedProperties - // with initializeDetailsPage as the callback. - chrome.send('getManagedProperties', [servicePath]); + var guid = this.onc_.guid(); + if (changes.indexOf(guid) != -1) { + chrome.networkingPrivate.getManagedProperties( + guid, DetailsInternetPage.updateConnectionData); + } }, /** @@ -228,7 +302,7 @@ cr.define('options.internet', function() { $('view-account-details').addEventListener('click', function(event) { chrome.send('showMorePlanInfo', - [DetailsInternetPage.getInstance().servicePath_]); + [DetailsInternetPage.getInstance().onc_.guid()]); PageManager.closeOverlay(); }); @@ -370,33 +444,51 @@ cr.define('options.internet', function() { }, /** - * Sends the IP Config info to chrome. + * Gets the IPConfig ONC Object. * @param {string} nameServerType The selected name server type: * 'automatic', 'google', or 'user'. + * @return {Object} The IPConfig ONC object. * @private */ - sendIpConfig_: function(nameServerType) { - var userNameServerString = ''; - if (nameServerType == 'user') { + getIpConfig_: function(nameServerType) { + var ipConfig = {}; + // If 'ip-address' is empty, automatic configuration will be used. + if (!$('ip-automatic-configuration-checkbox').checked && + $('ip-address').model.value) { + ipConfig['IPAddress'] = $('ip-address').model.value; + var netmask = $('ip-netmask').model.value; + var routingPrefix = 0; + if (netmask) { + routingPrefix = netmaskToPrefixLength(netmask); + if (routingPrefix == -1) { + console.error('Invalid netmask: ' + netmask); + routingPrefix = 0; + } + } + ipConfig['RoutingPrefix'] = routingPrefix; + ipConfig['Gateway'] = $('ip-gateway').model.value || ''; + } + + // Note: If no nameserver fields are set, automatic configuration will be + // used. TODO(stevenjb): Validate input fields. + if (nameServerType != 'automatic') { var userNameServers = []; - for (var i = 1; i <= 4; ++i) { - var nameServerField = $('ipconfig-dns' + i); - // Skip empty values. - if (nameServerField && nameServerField.model && - nameServerField.model.value) { - userNameServers.push(nameServerField.model.value); + if (nameServerType == 'google') { + userNameServers = GoogleNameServers.slice(); + } else if (nameServerType == 'user') { + for (var i = 1; i <= 4; ++i) { + var nameServerField = $('ipconfig-dns' + i); + // Skip empty values. + if (nameServerField && nameServerField.model && + nameServerField.model.value) { + userNameServers.push(nameServerField.model.value); + } } } - userNameServerString = userNameServers.sort().join(','); + if (userNameServers.length) + ipConfig['NameServers'] = userNameServers.sort(); } - chrome.send('setIPConfig', - [this.servicePath_, - Boolean($('ip-automatic-configuration-checkbox').checked), - $('ip-address').model.value || '', - $('ip-netmask').model.value || '', - $('ip-gateway').model.value || '', - nameServerType, - userNameServerString]); + return ipConfig; }, /** @@ -645,27 +737,33 @@ cr.define('options.internet', function() { return; } + var connectable = onc.getActiveValue('Connectable'); var connectState = onc.getActiveValue('ConnectionState'); if (connectState == 'NotConnected') { + $('details-internet-disconnect').hidden = true; $('details-internet-login').hidden = false; // Connecting to an unconfigured network might trigger certificate // installation UI. Until that gets handled here, always enable the - // Connect button. - $('details-internet-login').disabled = false; - $('details-internet-disconnect').hidden = true; + // Connect button for built-in networks. + var enabled = (this.type_ != 'VPN') || + (onc.getActiveValue('VPN.Type') != 'ThirdPartyVPN') || + connectable; + $('details-internet-login').disabled = !enabled; } else { $('details-internet-login').hidden = true; $('details-internet-disconnect').hidden = false; } - var connectable = onc.getActiveValue('Connectable'); - if (connectState != 'Connected' && - (!connectable || onc.getWiFiSecurity() != 'None' || - (this.type_ == 'WiMAX' || this.type_ == 'VPN'))) { - $('details-internet-configure').hidden = false; - } else { - $('details-internet-configure').hidden = true; + var showConfigure = false; + if (this.type_ == 'VPN') { + showConfigure = true; + } else if (this.type_ == 'WiMAX' && connectState == 'NotConnected') { + showConfigure = true; + } else if (this.type_ == 'WiFi') { + showConfigure = (connectState == 'NotConnected' && + (!connectable || onc.getWiFiSecurity() != 'None')); } + $('details-internet-configure').hidden = !showConfigure; }, /** @@ -695,10 +793,10 @@ cr.define('options.internet', function() { $('sim-card-lock-enabled').checked = lockEnabled; $('change-pin').hidden = !lockEnabled; } - showViewAccount = onc.getActiveValue('showViewAccountButton'); + showViewAccount = shouldShowViewAccountButton(onc); var activationState = onc.getActiveValue('Cellular.ActivationState'); - showActivate = activationState == 'NotActivated' || - activationState == 'PartiallyActivated'; + showActivate = (activationState == 'NotActivated' || + activationState == 'PartiallyActivated'); } $('view-account-details').hidden = !showViewAccount; @@ -716,8 +814,9 @@ cr.define('options.internet', function() { populateHeader_: function() { var onc = this.onc_; - $('network-details-title').textContent = onc.getTranslatedValue('Name'); - var connectionState = onc.getActiveValue('ConnectionState'); + $('network-details-title').textContent = + this.networkTitle_ || onc.getTranslatedValue('Name'); + var connectionStateString = onc.getTranslatedValue('ConnectionState'); $('network-details-subtitle-status').textContent = connectionStateString; @@ -748,18 +847,29 @@ cr.define('options.internet', function() { }, /** - * Helper method called from initializeDetailsPage to initialize the Apn - * list. + * Helper method to insert a 'user' option into the Apn list. + * @param {Object} userOption The 'user' apn dictionary * @private */ - initializeApnList_: function() { - var onc = this.onc_; + insertApnUserOption_: function(userOption) { + // Add the 'user' option before the last option ('other') + var apnSelector = $('select-apn'); + assert(apnSelector.length > 0); + var otherOption = apnSelector[apnSelector.length - 1]; + apnSelector.add(userOption, otherOption); + this.userApnIndex_ = apnSelector.length - 2; + this.selectedApnIndex_ = this.userApnIndex_; + }, + /** + * Helper method called from initializeApnList to populate the Apn list. + * @param {Array} apnList List of available APNs. + * @private + */ + populateApnList_: function(apnList) { + var onc = this.onc_; var apnSelector = $('select-apn'); - // Clear APN lists, keep only last element that "other". - while (apnSelector.length != 1) { - apnSelector.remove(0); - } + assert(apnSelector.length == 1); var otherOption = apnSelector[0]; var activeApn = onc.getActiveValue('Cellular.APN.AccessPointName'); var activeUsername = onc.getActiveValue('Cellular.APN.Username'); @@ -770,7 +880,6 @@ cr.define('options.internet', function() { onc.getActiveValue('Cellular.LastGoodAPN.Username'); var lastGoodPassword = onc.getActiveValue('Cellular.LastGoodAPN.Password'); - var apnList = onc.getActiveValue('Cellular.APNList'); for (var i = 0; i < apnList.length; i++) { var apnDict = apnList[i]; var option = document.createElement('option'); @@ -780,30 +889,58 @@ cr.define('options.internet', function() { option.textContent = name ? (name + ' (' + accessPointName + ')') : accessPointName; option.value = i; - // Insert new option before "other" option. + // Insert new option before 'other' option. apnSelector.add(option, otherOption); if (this.selectedApnIndex_ != -1) continue; - // If this matches the active Apn, or LastGoodApn (or there is no last - // good APN), set it as the selected Apn. - if ((activeApn == accessPointName && - activeUsername == apnDict['Username'] && - activePassword == apnDict['Password']) || - (!activeApn && !lastGoodApn) || - (!activeApn && - lastGoodApn == accessPointName && - lastGoodUsername == apnDict['Username'] && - lastGoodPassword == apnDict['Password'])) { + // If this matches the active Apn name, or LastGoodApn name (or there + // is no last good APN), set it as the selected Apn. + if ((activeApn == accessPointName) || + (!activeApn && (!lastGoodApn || lastGoodApn == accessPointName))) { this.selectedApnIndex_ = i; } } if (this.selectedApnIndex_ == -1 && activeApn) { - var activeOption = document.createElement('option'); - activeOption.textContent = activeApn; - activeOption.value = -1; - apnSelector.add(activeOption, otherOption); - this.selectedApnIndex_ = apnSelector.length - 2; - this.userApnIndex_ = this.selectedApnIndex_; + this.userApn_ = activeApn; + // Create a 'user' entry for any active apn not in the list. + var userOption = document.createElement('option'); + userOption.textContent = activeApn; + userOption.value = -1; + this.insertApnUserOption_(userOption); + } + }, + + /** + * Helper method called from initializeDetailsPage to initialize the Apn + * list. + * @private + */ + initializeApnList_: function() { + this.selectedApnIndex_ = -1; + this.userApnIndex_ = -1; + + var onc = this.onc_; + var apnSelector = $('select-apn'); + + // Clear APN lists, keep only last element, 'other'. + while (apnSelector.length != 1) + apnSelector.remove(0); + + var apnList = onc.getActiveValue('Cellular.APNList'); + if (apnList) { + // Populate the list with the existing APNs. + this.populateApnList_(apnList); + } else { + // Create a single 'default' entry. + var otherOption = apnSelector[0]; + var defaultOption = document.createElement('option'); + defaultOption.textContent = + loadTimeData.getString('cellularApnUseDefault'); + defaultOption.value = -1; + // Add 'default' entry before 'other' option + apnSelector.add(defaultOption, otherOption); + assert(apnSelector.length == 2); // 'default', 'other' + this.selectedApnIndex_ = 0; // Select 'default' } assert(this.selectedApnIndex_ >= 0); apnSelector.selectedIndex = this.selectedApnIndex_; @@ -812,37 +949,49 @@ cr.define('options.internet', function() { }, /** + * Helper function for setting APN properties. + * @param {Object} apnValue Dictionary of APN properties. + * @private + */ + setActiveApn_: function(apnValue) { + var activeApn = {}; + var apnName = apnValue['AccessPointName']; + if (apnName) { + activeApn['AccessPointName'] = apnName; + activeApn['Username'] = stringFromValue(apnValue['Username']); + activeApn['Password'] = stringFromValue(apnValue['Password']); + } + // Set the cached ONC data. + this.onc_.setProperty('Cellular.APN', activeApn); + // Set an ONC object with just the APN values. + var oncData = new OncData({}); + oncData.setProperty('Cellular.APN', activeApn); + chrome.networkingPrivate.setProperties(this.onc_.guid(), + oncData.getData()); + }, + + /** * Event Listener for the cellular-apn-use-default button. * @private */ setDefaultApn_: function() { - var onc = this.onc_; var apnSelector = $('select-apn'); + // Remove the 'user' entry if it exists. if (this.userApnIndex_ != -1) { + assert(this.userApnIndex_ < apnSelector.length - 1); apnSelector.remove(this.userApnIndex_); this.userApnIndex_ = -1; } - var iApn = -1; - var apnList = onc.getActiveValue('Cellular.APNList'); - if (apnList != undefined && apnList.length > 0) { - iApn = 0; - var defaultApn = apnList[iApn]; - var activeApn = {}; - activeApn['AccessPointName'] = - stringFromValue(defaultApn['AccessPointName']); - activeApn['Username'] = stringFromValue(defaultApn['Username']); - activeApn['Password'] = stringFromValue(defaultApn['Password']); - onc.setManagedProperty('Cellular.APN', activeApn); - chrome.send('setApn', [this.servicePath_, - activeApn['AccessPointName'], - activeApn['Username'], - activeApn['Password']]); - } + var apnList = this.onc_.getActiveValue('Cellular.APNList'); + var iApn = (apnList != undefined && apnList.length > 0) ? 0 : -1; apnSelector.selectedIndex = iApn; this.selectedApnIndex_ = iApn; + // Clear any user APN entry to inform Chrome to use the default APN. + this.setActiveApn_({}); + updateHidden('.apn-list-view', false); updateHidden('.apn-details-view', true); }, @@ -855,32 +1004,29 @@ cr.define('options.internet', function() { if (apnValue == '') return; - var onc = this.onc_; var apnSelector = $('select-apn'); var activeApn = {}; activeApn['AccessPointName'] = stringFromValue(apnValue); activeApn['Username'] = stringFromValue($('cellular-apn-username').value); activeApn['Password'] = stringFromValue($('cellular-apn-password').value); - onc.setManagedProperty('Cellular.APN', activeApn); + this.setActiveApn_(activeApn); + // Set the user selected APN. this.userApn_ = activeApn; - chrome.send('setApn', [this.servicePath_, - activeApn['AccessPointName'], - activeApn['Username'], - activeApn['Password']]); + // Remove any existing 'user' entry. if (this.userApnIndex_ != -1) { + assert(this.userApnIndex_ < apnSelector.length - 1); apnSelector.remove(this.userApnIndex_); this.userApnIndex_ = -1; } + // Create a new 'user' entry with the new active apn. var option = document.createElement('option'); option.textContent = activeApn['AccessPointName']; option.value = -1; option.selected = true; - apnSelector.add(option, apnSelector[apnSelector.length - 1]); - this.userApnIndex_ = apnSelector.length - 2; - this.selectedApnIndex_ = this.userApnIndex_; + this.insertApnUserOption_(option); updateHidden('.apn-list-view', false); updateHidden('.apn-details-view', true); @@ -890,11 +1036,7 @@ cr.define('options.internet', function() { * Event Listener for the cellular-apn-cancel button. * @private */ - cancelApn_: function() { - $('select-apn').selectedIndex = this.selectedApnIndex_; - updateHidden('.apn-list-view', false); - updateHidden('.apn-details-view', true); - }, + cancelApn_: function() { this.initializeApnList_(); }, /** * Event Listener for the select-apn button. @@ -903,31 +1045,31 @@ cr.define('options.internet', function() { selectApn_: function() { var onc = this.onc_; var apnSelector = $('select-apn'); - var apnDict; if (apnSelector[apnSelector.selectedIndex].value != -1) { var apnList = onc.getActiveValue('Cellular.APNList'); var apnIndex = apnSelector.selectedIndex; assert(apnIndex < apnList.length); - apnDict = apnList[apnIndex]; - chrome.send('setApn', [this.servicePath_, - stringFromValue(apnDict['AccessPointName']), - stringFromValue(apnDict['Username']), - stringFromValue(apnDict['Password'])]); this.selectedApnIndex_ = apnIndex; + this.setActiveApn_(apnList[apnIndex]); } else if (apnSelector.selectedIndex == this.userApnIndex_) { - apnDict = this.userApn_; - chrome.send('setApn', [this.servicePath_, - stringFromValue(apnDict['AccessPointName']), - stringFromValue(apnDict['Username']), - stringFromValue(apnDict['Password'])]); this.selectedApnIndex_ = apnSelector.selectedIndex; - } else { - $('cellular-apn').value = - stringFromValue(onc.getActiveValue('Cellular.APN.AccessPointName')); - $('cellular-apn-username').value = - stringFromValue(onc.getActiveValue('Cellular.APN.Username')); - $('cellular-apn-password').value = - stringFromValue(onc.getActiveValue('Cellular.APN.Password')); + this.setActiveApn_(this.userApn_); + } else { // 'Other' + var apnDict; + if (this.userApn_['AccessPointName']) { + // Fill in the details fields with the existing 'user' config. + apnDict = this.userApn_; + } else { + // No 'user' config, use the current values. + apnDict = {}; + apnDict['AccessPointName'] = + onc.getActiveValue('Cellular.APN.AccessPointName'); + apnDict['Username'] = onc.getActiveValue('Cellular.APN.Username'); + apnDict['Password'] = onc.getActiveValue('Cellular.APN.Password'); + } + $('cellular-apn').value = stringFromValue(apnDict['AccessPointName']); + $('cellular-apn-username').value = stringFromValue(apnDict['Username']); + $('cellular-apn-password').value = stringFromValue(apnDict['Password']); updateHidden('.apn-list-view', true); updateHidden('.apn-details-view', false); } @@ -956,6 +1098,8 @@ cr.define('options.internet', function() { * Shows a spinner while the carrier is changed. */ DetailsInternetPage.showCarrierChangeSpinner = function(visible) { + if (!DetailsInternetPage.getInstance().visible) + return; $('switch-carrier-spinner').hidden = !visible; // Disable any buttons that allow us to operate on cellular networks. DetailsInternetPage.changeCellularButtonsState(visible); @@ -968,8 +1112,37 @@ cr.define('options.internet', function() { var carrierSelector = $('select-carrier'); var carrier = carrierSelector[carrierSelector.selectedIndex].textContent; DetailsInternetPage.showCarrierChangeSpinner(true); - chrome.send('setCarrier', [ - DetailsInternetPage.getInstance().servicePath_, carrier]); + var guid = DetailsInternetPage.getInstance().onc_.guid(); + var oncData = new OncData({}); + oncData.setProperty('Cellular.Carrier', carrier); + chrome.networkingPrivate.setProperties(guid, oncData.getData(), function() { + // Start activation or show the activation UI after changing carriers. + DetailsInternetPage.activateCellular(guid); + }); + }; + + /** + * If the network is not already activated, starts the activation process or + * shows the activation UI. Otherwise does nothing. + */ + DetailsInternetPage.activateCellular = function(guid) { + chrome.networkingPrivate.getProperties(guid, function(properties) { + var oncData = new OncData(properties); + if (oncData.getActiveValue('Cellular.ActivationState') == 'Activated') { + DetailsInternetPage.showCarrierChangeSpinner(false); + return; + } + var carrier = oncData.getActiveValue('Cellular.Carrier'); + if (carrier == CarrierSprint) { + // Sprint is directly ativated, call startActivate(). + chrome.networkingPrivate.startActivate(guid, '', function() { + DetailsInternetPage.showCarrierChangeSpinner(false); + }); + } else { + DetailsInternetPage.showCarrierChangeSpinner(false); + chrome.send('showMorePlanInfo', [guid]); + } + }); }; /** @@ -1038,19 +1211,65 @@ cr.define('options.internet', function() { } }; - DetailsInternetPage.updateCarrier = function() { - DetailsInternetPage.showCarrierChangeSpinner(false); + DetailsInternetPage.loginFromDetails = function() { + DetailsInternetPage.configureOrConnect(); + PageManager.closeOverlay(); }; - DetailsInternetPage.loginFromDetails = function() { + /** + * This function identifies unconfigured networks and networks that are + * likely to fail (e.g. due to a bad passphrase on a previous connect + * attempt). For such networks a configure dialog will be opened. Otherwise + * a connection will be attempted. + */ + DetailsInternetPage.configureOrConnect = function() { var detailsPage = DetailsInternetPage.getInstance(); if (detailsPage.type_ == 'WiFi') sendChromeMetricsAction('Options_NetworkConnectToWifi'); else if (detailsPage.type_ == 'VPN') sendChromeMetricsAction('Options_NetworkConnectToVPN'); - // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType - chrome.send('startConnect', [detailsPage.servicePath_]); - PageManager.closeOverlay(); + + var onc = detailsPage.onc_; + var guid = onc.guid(); + var type = onc.getActiveValue('Type'); + + // Built-in VPNs do not correctly set 'Connectable', so we always show the + // configuration UI. + if (type == 'VPN') { + if (onc.getActiveValue('VPN.Type') != 'ThirdPartyVPN') { + chrome.send('configureNetwork', [guid]); + return; + } + } + + // If 'Connectable' is false for WiFi or WiMAX, Shill requires + // additional configuration to connect, so show the configuration UI. + if ((type == 'WiFi' || type == 'WiMAX') && + !onc.getActiveValue('Connectable')) { + chrome.send('configureNetwork', [guid]); + return; + } + + // Secure WiFi networks with ErrorState set most likely require + // configuration (e.g. a correct passphrase) before connecting. + if (type == 'WiFi' && onc.getWiFiSecurity() != 'None') { + var errorState = onc.getActiveValue('ErrorState'); + if (errorState && errorState != 'Unknown') { + chrome.send('configureNetwork', [guid]); + return; + } + } + + // Cellular networks need to be activated before they can be connected to. + if (type == 'Cellular') { + var activationState = onc.getActiveValue('Cellular.ActivationState'); + if (activationState != 'Activated' && activationState != 'Unknown') { + DetailsInternetPage.activateCellular(guid); + return; + } + } + + chrome.networkingPrivate.startConnect(guid); }; DetailsInternetPage.disconnectNetwork = function() { @@ -1059,22 +1278,20 @@ cr.define('options.internet', function() { sendChromeMetricsAction('Options_NetworkDisconnectWifi'); else if (detailsPage.type_ == 'VPN') sendChromeMetricsAction('Options_NetworkDisconnectVPN'); - // TODO(stevenjb): chrome.networkingPrivate.startDisconnect - chrome.send('startDisconnect', [detailsPage.servicePath_]); + chrome.networkingPrivate.startDisconnect(detailsPage.onc_.guid()); PageManager.closeOverlay(); }; DetailsInternetPage.configureNetwork = function() { var detailsPage = DetailsInternetPage.getInstance(); - chrome.send('configureNetwork', [detailsPage.servicePath_]); + chrome.send('configureNetwork', [detailsPage.onc_.guid()]); PageManager.closeOverlay(); }; DetailsInternetPage.activateFromDetails = function() { var detailsPage = DetailsInternetPage.getInstance(); - if (detailsPage.type_ == 'Cellular') { - chrome.send('activateNetwork', [detailsPage.servicePath_]); - } + if (detailsPage.type_ == 'Cellular') + DetailsInternetPage.activateCellular(detailsPage.onc_.guid()); PageManager.closeOverlay(); }; @@ -1085,33 +1302,38 @@ cr.define('options.internet', function() { DetailsInternetPage.setDetails = function() { var detailsPage = DetailsInternetPage.getInstance(); var type = detailsPage.type_; - var servicePath = detailsPage.servicePath_; + var oncData = new OncData({}); + var autoConnectCheckboxId = ''; if (type == 'WiFi') { - sendCheckedIfEnabled(servicePath, - 'setPreferNetwork', - 'prefer-network-wifi', - 'Options_NetworkSetPrefer'); - sendCheckedIfEnabled(servicePath, - 'setAutoConnect', - 'auto-connect-network-wifi', - 'Options_NetworkAutoConnect'); + var preferredCheckbox = + assertInstanceof($('prefer-network-wifi'), HTMLInputElement); + if (!preferredCheckbox.hidden && !preferredCheckbox.disabled) { + var kPreferredPriority = 1; + var priority = preferredCheckbox.checked ? kPreferredPriority : 0; + oncData.setProperty('Priority', priority); + sendChromeMetricsAction('Options_NetworkSetPrefer'); + } + autoConnectCheckboxId = 'auto-connect-network-wifi'; } else if (type == 'WiMAX') { - sendCheckedIfEnabled(servicePath, - 'setAutoConnect', - 'auto-connect-network-wimax', - 'Options_NetworkAutoConnect'); + autoConnectCheckboxId = 'auto-connect-network-wimax'; } else if (type == 'Cellular') { - sendCheckedIfEnabled(servicePath, - 'setAutoConnect', - 'auto-connect-network-cellular', - 'Options_NetworkAutoConnect'); + autoConnectCheckboxId = 'auto-connect-network-cellular'; } else if (type == 'VPN') { - chrome.send('setServerHostname', - [servicePath, $('inet-server-hostname').value]); - sendCheckedIfEnabled(servicePath, - 'setAutoConnect', - 'auto-connect-network-vpn', - 'Options_NetworkAutoConnect'); + var providerType = detailsPage.onc_.getActiveValue('VPN.Type'); + if (providerType != 'ThirdPartyVPN') { + oncData.setProperty('VPN.Type', providerType); + oncData.setProperty('VPN.Host', $('inet-server-hostname').value); + autoConnectCheckboxId = 'auto-connect-network-vpn'; + } + } + if (autoConnectCheckboxId != '') { + var autoConnectCheckbox = + assertInstanceof($(autoConnectCheckboxId), HTMLInputElement); + if (!autoConnectCheckbox.hidden && !autoConnectCheckbox.disabled) { + var autoConnectKey = type + '.AutoConnect'; + oncData.setProperty(autoConnectKey, !!autoConnectCheckbox.checked); + sendChromeMetricsAction('Options_NetworkAutoConnect'); + } } var nameServerTypes = ['automatic', 'google', 'user']; @@ -1122,7 +1344,18 @@ cr.define('options.internet', function() { break; } } - detailsPage.sendIpConfig_(nameServerType); + var ipConfig = detailsPage.getIpConfig_(nameServerType); + var ipAddressType = ('IPAddress' in ipConfig) ? 'Static' : 'DHCP'; + var nameServersType = ('NameServers' in ipConfig) ? 'Static' : 'DHCP'; + oncData.setProperty('IPAddressConfigType', ipAddressType); + oncData.setProperty('NameServersConfigType', nameServersType); + oncData.setProperty('StaticIPConfig', ipConfig); + + var data = oncData.getData(); + if (Object.keys(data).length > 0) { + // TODO(stevenjb): Only set changed properties. + chrome.networkingPrivate.setProperties(detailsPage.onc_.guid(), data); + } PageManager.closeOverlay(); }; @@ -1174,7 +1407,7 @@ cr.define('options.internet', function() { if (!detailsPage.visible) return; - if (oncData.servicePath != detailsPage.servicePath_) + if (oncData.GUID != detailsPage.onc_.guid()) return; // Update our cached data object. @@ -1186,17 +1419,6 @@ cr.define('options.internet', function() { }; /** - * Method called from Chrome in response to getManagedProperties. - * We only use this when we want to call initializeDetailsPage. - * TODO(stevenjb): Eliminate when we switch to networkingPrivate - * (initializeDetailsPage will be provided as the callback). - * @param {Object} oncData Dictionary of ONC properties. - */ - DetailsInternetPage.getManagedPropertiesResult = function(oncData) { - DetailsInternetPage.initializeDetailsPage(oncData); - }; - - /** * Initializes the details page with the provided ONC data. * @param {Object} oncData Dictionary of ONC properties. */ @@ -1204,13 +1426,22 @@ cr.define('options.internet', function() { var onc = new OncData(oncData); var detailsPage = DetailsInternetPage.getInstance(); - detailsPage.servicePath_ = oncData.servicePath; detailsPage.onc_ = onc; var type = onc.getActiveValue('Type'); detailsPage.type_ = type; sendShowDetailsMetrics(type, onc.getActiveValue('ConnectionState')); + if (type == 'VPN') { + // Cache the dialog title, which will contain the provider name in the + // case of a third-party VPN provider. This caching is important as the + // provider may go away while the details dialog is being shown, causing + // subsequent updates to be unable to determine the correct title. + detailsPage.networkTitle_ = options.VPNProviders.formatNetworkName(onc); + } else { + delete detailsPage.networkTitle_; + } + detailsPage.populateHeader_(); detailsPage.updateConnectionButtonVisibilty_(); detailsPage.updateDetails_(); @@ -1223,7 +1454,7 @@ cr.define('options.internet', function() { if (remembered) { detailsPage.showProxy_ = true; // Inform Chrome which network to use for proxy configuration. - chrome.send('selectNetwork', [detailsPage.servicePath_]); + chrome.send('selectNetwork', [detailsPage.onc_.guid()]); } else { detailsPage.showProxy_ = false; } @@ -1234,6 +1465,10 @@ cr.define('options.internet', function() { var restrictedString = loadTimeData.getString( restricted ? 'restrictedYes' : 'restrictedNo'); + // These objects contain an 'automatic' property that is displayed when + // ip-automatic-configuration-checkbox is checked, and a 'value' property + // that is displayed when unchecked and used to set the associated ONC + // property for StaticIPConfig on commit. var inetAddress = {}; var inetNetmask = {}; var inetGateway = {}; @@ -1272,13 +1507,14 @@ cr.define('options.internet', function() { } } - // Override the "automatic" values with the real saved DHCP values, - // if they are set. + // Override the 'automatic' properties with the saved DHCP values if the + // saved value is set, and set any unset 'value' properties. var savedNameServersString; var savedIpAddress = onc.getActiveValue('SavedIPConfig.IPAddress'); if (savedIpAddress != undefined) { inetAddress.automatic = savedIpAddress; - inetAddress.value = savedIpAddress; + if (!inetAddress.value) + inetAddress.value = savedIpAddress; } var savedPrefix = onc.getActiveValue('SavedIPConfig.RoutingPrefix'); if (savedPrefix != undefined) { @@ -1286,13 +1522,16 @@ cr.define('options.internet', function() { var savedNetmask = prefixLengthToNetmask( /** @type {number} */(savedPrefix)); inetNetmask.automatic = savedNetmask; - inetNetmask.value = savedNetmask; + if (!inetNetmask.value) + inetNetmask.value = savedNetmask; } var savedGateway = onc.getActiveValue('SavedIPConfig.Gateway'); if (savedGateway != undefined) { inetGateway.automatic = savedGateway; - inetGateway.value = savedGateway; + if (!inetGateway.value) + inetGateway.value = savedGateway; } + var savedNameServers = onc.getActiveValue('SavedIPConfig.NameServers'); if (savedNameServers) { savedNameServers = savedNameServers.sort(); @@ -1300,29 +1539,28 @@ cr.define('options.internet', function() { } var ipAutoConfig = 'automatic'; - - var staticNameServersString; - var staticIpAddress = onc.getActiveValue('StaticIPConfig.IPAddress'); - if (staticIpAddress != undefined) { + if (onc.getActiveValue('IPAddressConfigType') == 'Static') { ipAutoConfig = 'user'; + var staticIpAddress = onc.getActiveValue('StaticIPConfig.IPAddress'); inetAddress.user = staticIpAddress; inetAddress.value = staticIpAddress; - } - var staticPrefix = onc.getActiveValue('StaticIPConfig.RoutingPrefix'); - if (staticPrefix != undefined) { - assert(typeof staticPrefix == 'number'); + + var staticPrefix = onc.getActiveValue('StaticIPConfig.RoutingPrefix'); + if (typeof staticPrefix != 'number') + staticPrefix = 0; var staticNetmask = prefixLengthToNetmask( - /** @type {number} */(staticPrefix)); + /** @type {number} */ (staticPrefix)); inetNetmask.user = staticNetmask; inetNetmask.value = staticNetmask; - } - var staticGateway = onc.getActiveValue('StaticIPConfig.Gateway'); - if (staticGateway != undefined) { + + var staticGateway = onc.getActiveValue('StaticIPConfig.Gateway'); inetGateway.user = staticGateway; inetGateway.value = staticGateway; } - var staticNameServers = onc.getActiveValue('StaticIPConfig.NameServers'); - if (staticNameServers) { + + var staticNameServersString; + if (onc.getActiveValue('NameServersConfigType') == 'Static') { + var staticNameServers = onc.getActiveValue('StaticIPConfig.NameServers'); staticNameServers = staticNameServers.sort(); staticNameServersString = staticNameServers.join(','); } @@ -1343,21 +1581,26 @@ cr.define('options.internet', function() { configureAddressField($('ip-netmask'), inetNetmask); configureAddressField($('ip-gateway'), inetGateway); - // Set Nameserver fields. + // Set Nameserver fields. Nameservers are 'automatic' by default. If a + // static namerserver is set, use that unless it does not match a non + // empty 'NameServers' value (indicating that the custom nameservers are + // invalid or not being applied for some reason). TODO(stevenjb): Only + // set these properites if they change so that invalid custom values do + // not get lost. var nameServerType = 'automatic'; - if (staticNameServersString) { - // If static nameservers are defined and match the google name servers, - // show that in the UI, otherwise show the custom static nameservers. - if (staticNameServersString == GoogleNameServersString) + if (staticNameServersString && + (!inetNameServersString || + staticNameServersString == inetNameServersString)) { + if (staticNameServersString == GoogleNameServers.join(',')) nameServerType = 'google'; - else if (staticNameServersString == inetNameServersString) + else nameServerType = 'user'; } if (nameServerType == 'automatic') $('automatic-dns-display').textContent = inetNameServersString; else $('automatic-dns-display').textContent = savedNameServersString; - $('google-dns-display').textContent = GoogleNameServersString; + $('google-dns-display').textContent = GoogleNameServers.join(','); var nameServersUser = []; if (staticNameServers) { @@ -1476,13 +1719,13 @@ cr.define('options.internet', function() { if (currentCarrierIndex == -1) $('service-name').textContent = networkName; + // TODO(stevenjb): Ideally many of these should be localized. $('network-technology').textContent = onc.getActiveValue('Cellular.NetworkTechnology'); $('roaming-state').textContent = onc.getTranslatedValue('Cellular.RoamingState'); $('cellular-restricted-connectivity').textContent = restrictedString; - // 'errorMessage' is a non ONC property added by Chrome. - $('error-state').textContent = onc.getActiveValue('errorMessage'); + $('error-state').textContent = onc.getActiveValue('ErrorState'); $('manufacturer').textContent = onc.getActiveValue('Cellular.Manufacturer'); $('model-id').textContent = onc.getActiveValue('Cellular.ModelID'); @@ -1527,33 +1770,50 @@ cr.define('options.internet', function() { $('auto-connect-network-cellular').disabled = false; } else if (type == 'VPN') { OptionsPage.showTab($('vpn-nav-tab')); + var providerType = onc.getActiveValue('VPN.Type'); + var isThirdPartyVPN = providerType == 'ThirdPartyVPN'; + $('vpn-tab').classList.toggle('third-party-vpn-provider', + isThirdPartyVPN); + $('inet-service-name').textContent = networkName; $('inet-provider-type').textContent = onc.getTranslatedValue('VPN.Type'); - var providerType = onc.getActiveValue('VPN.Type'); - var usernameKey; - if (providerType == 'OpenVPN') - usernameKey = 'VPN.OpenVPN.Username'; - else if (providerType == 'L2TP-IPsec') - usernameKey = 'VPN.L2TP.Username'; - - if (usernameKey) { - $('inet-username').parentElement.hidden = false; - $('inet-username').textContent = onc.getActiveValue(usernameKey); + + if (isThirdPartyVPN) { + $('inet-provider-name').textContent = ''; + var extensionID = onc.getActiveValue('VPN.ThirdPartyVPN.ExtensionID'); + var providers = options.VPNProviders.getProviders(); + for (var i = 0; i < providers.length; ++i) { + if (extensionID == providers[i].extensionID) { + $('inet-provider-name').textContent = providers[i].name; + break; + } + } } else { - $('inet-username').parentElement.hidden = true; + var usernameKey; + if (providerType == 'OpenVPN') + usernameKey = 'VPN.OpenVPN.Username'; + else if (providerType == 'L2TP-IPsec') + usernameKey = 'VPN.L2TP.Username'; + + if (usernameKey) { + $('inet-username').parentElement.hidden = false; + $('inet-username').textContent = onc.getActiveValue(usernameKey); + } else { + $('inet-username').parentElement.hidden = true; + } + var inetServerHostname = $('inet-server-hostname'); + inetServerHostname.value = onc.getActiveValue('VPN.Host'); + inetServerHostname.resetHandler = function() { + PageManager.hideBubble(); + var recommended = onc.getRecommendedValue('VPN.Host'); + if (recommended != undefined) + inetServerHostname.value = recommended; + }; + $('auto-connect-network-vpn').checked = + onc.getActiveValue('VPN.AutoConnect'); + $('auto-connect-network-vpn').disabled = false; } - var inetServerHostname = $('inet-server-hostname'); - inetServerHostname.value = onc.getActiveValue('VPN.Host'); - inetServerHostname.resetHandler = function() { - PageManager.hideBubble(); - var recommended = onc.getRecommendedValue('VPN.Host'); - if (recommended != undefined) - inetServerHostname.value = recommended; - }; - $('auto-connect-network-vpn').checked = - onc.getActiveValue('VPN.AutoConnect'); - $('auto-connect-network-vpn').disabled = false; } else { OptionsPage.showTab($('internet-nav-tab')); } @@ -1582,7 +1842,7 @@ cr.define('options.internet', function() { /** @type {{value: *, controlledBy: *, recommendedValue: *}} */( propValue)); indicators[i].handlePrefChange(event); - var forElement = $(indicators[i].getAttribute('for')); + var forElement = $(indicators[i].getAttribute('internet-detail-for')); if (forElement) { if (event.value.controlledBy == 'policy') forElement.disabled = true; diff --git a/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js index ae4baa03efe..b18af10166e 100644 --- a/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js +++ b/chromium/chrome/browser/resources/options/chromeos/keyboard_overlay.js @@ -8,7 +8,7 @@ cr.define('options', function() { * Auto-repeat delays (in ms) for the corresponding slider values, from * long to short. The values were chosen to provide a large range while giving * several options near the defaults. - * @type {!Array.<number>} + * @type {!Array<number>} * @const */ var AUTO_REPEAT_DELAYS = @@ -18,7 +18,7 @@ cr.define('options', function() { * Auto-repeat intervals (in ms) for the corresponding slider values, from * long to short. The slider itself is labeled "rate", the inverse of * interval, and goes from slow (long interval) to fast (short interval). - * @type {!Array.<number>} + * @type {!Array<number>} * @const */ var AUTO_REPEAT_INTERVALS = @@ -138,7 +138,7 @@ cr.define('options', function() { * |value|. * @param {string} id The slider's ID. * @param {number} value The value to find. - * @param {!Array.<number>} values The array to search. + * @param {!Array<number>} values The array to search. * @private */ updateSliderFromValue_: function(id, value, values) { diff --git a/chromium/chrome/browser/resources/options/chromeos/network_list.js b/chromium/chrome/browser/resources/options/chromeos/network_list.js index 4c65b342717..bcd947ad165 100644 --- a/chromium/chrome/browser/resources/options/chromeos/network_list.js +++ b/chromium/chrome/browser/resources/options/chromeos/network_list.js @@ -3,15 +3,23 @@ // found in the LICENSE file. /** + * Partial definition of the result of networkingPrivate.getProperties()). * @typedef {{ * ConnectionState: string, - * iconURL: string, - * policyManaged: boolean, - * servicePath: string + * Cellular: { + * Family: ?string, + * SIMPresent: ?boolean, + * SIMLockStatus: { LockType: ?string }, + * SupportNetworkScan: ?boolean + * }, + * GUID: string, + * Name: string, + * Source: string, + * Type: string * }} - * @see chrome/browser/ui/webui/options/chromeos/internet_options_handler.cc + * @see extensions/common/api/networking_private.idl */ -var NetworkInfo; +var NetworkProperties; cr.define('options.network', function() { var ArrayDataModel = cr.ui.ArrayDataModel; @@ -28,12 +36,19 @@ cr.define('options.network', function() { */ function Constants() {} - // Cellular activation states: - Constants.ACTIVATION_STATE_UNKNOWN = 0; - Constants.ACTIVATION_STATE_ACTIVATED = 1; - Constants.ACTIVATION_STATE_ACTIVATING = 2; - Constants.ACTIVATION_STATE_NOT_ACTIVATED = 3; - Constants.ACTIVATION_STATE_PARTIALLY_ACTIVATED = 4; + /** + * Valid network type names. + */ + Constants.NETWORK_TYPES = ['Ethernet', 'WiFi', 'WiMAX', 'Cellular', 'VPN']; + + /** + * Helper function to check whether |type| is a valid network type. + * @param {string} type A string that may contain a valid network type. + * @return {boolean} Whether the string represents a valid network type. + */ + function isNetworkType(type) { + return (Constants.NETWORK_TYPES.indexOf(type) != -1); + } /** * Order in which controls are to appear in the network list sorted by key. @@ -53,53 +68,39 @@ cr.define('options.network', function() { var activeMenu_ = null; /** - * Indicates if cellular networks are available. - * @type {boolean} + * The state of the cellular device or undefined if not available. + * @type {string|undefined} * @private */ - var cellularAvailable_ = false; + var cellularDeviceState_ = undefined; /** - * Indicates if cellular networks are enabled. - * @type {boolean} + * The active cellular network or null if none. + * @type {?NetworkProperties} * @private */ - var cellularEnabled_ = false; + var cellularNetwork_ = null; /** - * Indicates if cellular device supports network scanning. - * @type {boolean} + * The active ethernet network or null if none. + * @type {?NetworkProperties} * @private */ - var cellularSupportsScan_ = false; + var ethernetNetwork_ = null; /** - * Indicates the current SIM lock type of the cellular device. - * @type {string} + * The state of the WiFi device or undefined if not available. + * @type {string|undefined} * @private */ - var cellularSimLockType_ = ''; + var wifiDeviceState_ = undefined; /** - * Indicates whether the SIM card is absent on the cellular device. - * @type {boolean} + * The state of the WiMAX device or undefined if not available. + * @type {string|undefined} * @private */ - var cellularSimAbsent_ = false; - - /** - * Indicates if WiMAX networks are available. - * @type {boolean} - * @private - */ - var wimaxAvailable_ = false; - - /** - * Indicates if WiMAX networks are enabled. - * @type {boolean} - * @private - */ - var wimaxEnabled_ = false; + var wimaxDeviceState_ = undefined; /** * Indicates if mobile data roaming is enabled. @@ -109,19 +110,14 @@ cr.define('options.network', function() { var enableDataRoaming_ = false; /** - * Icon to use when not connected to a particular type of network. - * @type {!Object.<string, string>} Mapping of network type to icon data url. - * @private - */ - var defaultIcons_ = {}; - - /** * Returns the display name for 'network'. - * @param {Object} data The network data dictionary. + * @param {NetworkProperties} data The network data dictionary. */ function getNetworkName(data) { if (data.Type == 'Ethernet') return loadTimeData.getString('ethernetName'); + if (data.Type == 'VPN') + return options.VPNProviders.formatNetworkName(new cr.onc.OncData(data)); return data.Name; } @@ -149,13 +145,11 @@ cr.define('options.network', function() { } /** - * @param {string} servicePath The network service path. + * @param {string} guid The network GUID. */ - function showDetails(servicePath) { - // TODO(stevenjb): chrome.networkingPrivate.getManagedProperties - // (Note: we will need to provide DetailsInternetPage.initializeDetailsPage - // as the callback). - chrome.send('getManagedProperties', [servicePath]); + function showDetails(guid) { + chrome.networkingPrivate.getManagedProperties( + guid, DetailsInternetPage.initializeDetailsPage); } /** @@ -172,7 +166,7 @@ cr.define('options.network', function() { /** * Description of the network group or control. - * @type {Object.<string,Object>} + * @type {Object<string,Object>} * @private */ data_: null, @@ -185,18 +179,11 @@ cr.define('options.network', function() { subtitle_: null, /** - * Icon for the network control. + * Div containing the list item icon. * @type {?Element} * @private */ - icon_: null, - - /** - * Indicates if in the process of connecting to a network. - * @type {boolean} - * @private - */ - connecting_: false, + iconDiv_: null, /** * Description of the network control. @@ -217,42 +204,52 @@ cr.define('options.network', function() { }, /** - * URL for the network icon. - * @type {string} + * Sets the icon based on a network state object. + * @param {!NetworkProperties} data Network state properties. */ - set iconURL(iconURL) { - this.icon_.style.backgroundImage = url(iconURL); + set iconData(data) { + if (!isNetworkType(data.Type)) + return; + var networkIcon = this.getNetworkIcon(); + networkIcon.networkState = CrOncDataElement.create( + /** @type {chrome.networkingPrivate.NetworkStateProperties} */ ( + data)); }, /** - * Type of network icon. Each type corresponds to a CSS rule. + * Sets the icon based on a network type or a special type indecator, e.g. + * 'add-connection' * @type {string} */ set iconType(type) { - if (defaultIcons_[type]) - this.iconURL = defaultIcons_[type]; - else - this.icon_.classList.add('network-' + type.toLowerCase()); - }, - - /** - * Indicates if the network is in the process of being connected. - * @type {boolean} - */ - set connecting(state) { - this.connecting_ = state; - if (state) - this.icon_.classList.add('network-connecting'); - else - this.icon_.classList.remove('network-connecting'); + if (isNetworkType(type)) { + var networkIcon = this.getNetworkIcon(); + networkIcon.networkType = type; + } else { + // Special cases. e.g. 'add-connection'. Background images are + // defined in browser_options.css. + var oldIcon = /** @type {CrNetworkIconElement} */ ( + this.iconDiv_.querySelector('cr-network-icon')); + if (oldIcon) + this.iconDiv_.removeChild(oldIcon); + this.iconDiv_.classList.add('network-' + type.toLowerCase()); + } }, /** - * Indicates if the network is in the process of being connected. - * @type {boolean} + * Returns any existing network icon for the list item or creates a new one. + * @return {!CrNetworkIconElement} The network icon for the list item. */ - get connecting() { - return this.connecting_; + getNetworkIcon: function() { + var networkIcon = /** @type {CrNetworkIconElement} */ ( + this.iconDiv_.querySelector('cr-network-icon')); + if (!networkIcon) { + networkIcon = /** @type {!CrNetworkIconElement} */ ( + document.createElement('cr-network-icon')); + networkIcon.isListItem = false; + this.iconDiv_.appendChild(networkIcon); + } + return networkIcon; }, /** @@ -281,9 +278,9 @@ cr.define('options.network', function() { decorate: function() { ListItem.prototype.decorate.call(this); this.className = 'network-group'; - this.icon_ = this.ownerDocument.createElement('div'); - this.icon_.className = 'network-icon'; - this.appendChild(this.icon_); + this.iconDiv_ = this.ownerDocument.createElement('div'); + this.iconDiv_.className = 'network-icon'; + this.appendChild(this.iconDiv_); var textContent = this.ownerDocument.createElement('div'); textContent.className = 'network-group-labels'; this.appendChild(textContent); @@ -330,9 +327,9 @@ cr.define('options.network', function() { this.subtitle = null; if (this.data.iconType) this.iconType = this.data.iconType; - this.addEventListener('click', function() { + this.addEventListener('click', (function() { this.showMenu(); - }); + }).bind(this)); }, /** @@ -353,6 +350,7 @@ cr.define('options.network', function() { menu.className = 'network-menu'; menu.hidden = true; Menu.decorate(menu); + menu.menuItemSelector = '.network-menu-item'; for (var i = 0; i < this.data.menu.length; i++) { var entry = this.data.menu[i]; createCallback_(menu, null, entry.label, entry.command); @@ -362,11 +360,29 @@ cr.define('options.network', function() { return null; }, + /** + * Determines if a menu can be updated on the fly. Menus that cannot be + * updated are fully regenerated using createMenu. The advantage of + * updating a menu is that it can preserve ordering of networks avoiding + * entries from jumping around after an update. + * @return {boolean} Whether the menu can be updated on the fly. + */ canUpdateMenu: function() { return false; }, /** + * Removes the current menu contents, causing it to be regenerated when the + * menu is shown the next time. If the menu is showing right now, its + * contents are regenerated immediately and the menu remains visible. + */ + refreshMenu: function() { + this.menu_ = null; + if (activeMenu_ == this.getMenuName()) + this.showMenu(); + }, + + /** * Displays a popup menu. */ showMenu: function() { @@ -380,7 +396,7 @@ cr.define('options.network', function() { rebuild = true; var existing = $(this.getMenuName()); if (existing) { - if (this.updateMenu()) + if (this.canUpdateMenu() && this.updateMenu()) return; closeMenu_(); } @@ -404,8 +420,7 @@ cr.define('options.network', function() { this.menu_.hidden = false; } if (rescan) { - // TODO(stevenjb): chrome.networkingPrivate.requestNetworkScan - chrome.send('requestNetworkScan'); + chrome.networkingPrivate.requestNetworkScan(); } } }; @@ -413,8 +428,8 @@ cr.define('options.network', function() { /** * Creates a control for selecting or configuring a network connection based * on the type of connection (e.g. wifi versus vpn). - * @param {{key: string, networkList: Array.<NetworkInfo>}} data Description - * of the network. + * @param {{key: string, networkList: Array<!NetworkProperties>}} data + * An object containing the network type (key) and an array of networks. * @constructor * @extends {NetworkMenuItem} */ @@ -425,42 +440,84 @@ cr.define('options.network', function() { return el; } + /** + * Returns true if |source| is a policy managed source. + * @param {string} source The ONC source of a network. + * @return {boolean} Whether |source| is a managed source. + */ + function isManaged(source) { + return (source == 'DevicePolicy' || source == 'UserPolicy'); + } + + /** + * Returns true if |network| is visible. + * @param {!chrome.networkingPrivate.NetworkStateProperties} network The + * network state properties. + * @return {boolean} Whether |network| is visible. + */ + function networkIsVisible(network) { + if (network.Type == 'WiFi') + return !!(network.WiFi && (network.WiFi.SignalStrength > 0)); + if (network.Type == 'WiMAX') + return !!(network.WiMAX && (network.WiMAX.SignalStrength > 0)); + // Other network types are always considered 'visible'. + return true; + } + + /** + * Returns true if |cellular| is a GSM network with no sim present. + * @param {?NetworkProperties} cellular The network state properties. + * @return {boolean} Whether |network| is missing a SIM card. + */ + function isCellularSimAbsent(cellular) { + if (!cellular || !cellular.Cellular) + return false; + return cellular.Cellular.Family == 'GSM' && !cellular.Cellular.SIMPresent; + } + + /** + * Returns true if |cellular| has a locked SIM card. + * @param {?NetworkProperties} cellular The network state properties. + * @return {boolean} Whether |network| has a locked SIM card. + */ + function isCellularSimLocked(cellular) { + if (!cellular || !cellular.Cellular) + return false; + var simLockStatus = cellular.Cellular.SIMLockStatus; + return !!(simLockStatus && simLockStatus.LockType); + } + NetworkSelectorItem.prototype = { __proto__: NetworkMenuItem.prototype, /** @override */ decorate: function() { // TODO(kevers): Generalize method of setting default label. - var policyManaged = false; this.subtitle = loadTimeData.getString('OncConnectionStateNotConnected'); var list = this.data_.networkList; - var candidateURL = null; + var candidateData = null; for (var i = 0; i < list.length; i++) { var networkDetails = list[i]; if (networkDetails.ConnectionState == 'Connecting' || networkDetails.ConnectionState == 'Connected') { this.subtitle = getNetworkName(networkDetails); this.setSubtitleDirection('ltr'); - policyManaged = networkDetails.policyManaged; - candidateURL = networkDetails.iconURL; + candidateData = networkDetails; // Only break when we see a connecting network as it is possible to // have a connected network and a connecting network at the same // time. - if (networkDetails.ConnectionState == 'Connecting') { - this.connecting = true; - candidateURL = null; + if (networkDetails.ConnectionState == 'Connecting') break; - } } } - if (candidateURL) - this.iconURL = candidateURL; + if (candidateData) + this.iconData = candidateData; else this.iconType = this.data.key; this.showSelector(); - if (policyManaged) + if (candidateData && isManaged(candidateData.Source)) this.showManagedNetworkIndicator(); if (activeMenu_ == this.getMenuName()) { @@ -484,18 +541,21 @@ cr.define('options.network', function() { menu.className = 'network-menu'; menu.hidden = true; Menu.decorate(menu); + menu.menuItemSelector = '.network-menu-item'; var addendum = []; if (this.data_.key == 'WiFi') { addendum.push({ label: loadTimeData.getString('joinOtherNetwork'), - command: createAddConnectionCallback_('WiFi'), + command: createAddNonVPNConnectionCallback_('WiFi'), data: {} }); } else if (this.data_.key == 'Cellular') { - if (cellularEnabled_ && cellularSupportsScan_) { + if (cellularDeviceState_ == 'Enabled' && + cellularNetwork_ && cellularNetwork_.Cellular && + cellularNetwork_.Cellular.SupportNetworkScan) { addendum.push({ label: loadTimeData.getString('otherCellularNetworks'), - command: createAddConnectionCallback_('Cellular'), + command: createAddNonVPNConnectionCallback_('Cellular'), addClass: ['other-cellulars'], data: {} }); @@ -522,11 +582,7 @@ cr.define('options.network', function() { } addendum.push(entry); } else if (this.data_.key == 'VPN') { - addendum.push({ - label: loadTimeData.getString('joinOtherNetwork'), - command: createAddConnectionCallback_('VPN'), - data: {} - }); + addendum = addendum.concat(createAddVPNConnectionEntries_()); } var list = this.data.rememberedNetworks; @@ -548,22 +604,21 @@ cr.define('options.network', function() { list = this.data.networkList; var empty = !list || list.length == 0; if (list) { - var connectedVpnServicePath = ''; + var connectedVpnGuid = ''; for (var i = 0; i < list.length; i++) { var data = list[i]; this.createNetworkOptionsCallback_(networkGroup, data); // For VPN only, append a 'Disconnect' item to the dropdown menu. - if (!connectedVpnServicePath && data.Type == 'VPN' && + if (!connectedVpnGuid && data.Type == 'VPN' && (data.ConnectionState == 'Connected' || data.ConnectionState == 'Connecting')) { - connectedVpnServicePath = data.servicePath; + connectedVpnGuid = data.GUID; } } - if (connectedVpnServicePath) { + if (connectedVpnGuid) { var disconnectCallback = function() { sendChromeMetricsAction('Options_NetworkDisconnectVPN'); - // TODO(stevenjb): chrome.networkingPrivate.startDisconnect - chrome.send('startDisconnect', [connectedVpnServicePath]); + chrome.networkingPrivate.startDisconnect(connectedVpnGuid); }; // Add separator addendum.push({}); @@ -580,24 +635,21 @@ cr.define('options.network', function() { label: loadTimeData.getString('turnOffWifi'), command: function() { sendChromeMetricsAction('Options_NetworkWifiToggle'); - // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType - chrome.send('disableNetworkType', ['WiFi']); + chrome.networkingPrivate.disableNetworkType('WiFi'); }, data: {}}); } else if (this.data_.key == 'WiMAX') { addendum.push({ label: loadTimeData.getString('turnOffWimax'), command: function() { - // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType - chrome.send('disableNetworkType', ['WiMAX']); + chrome.networkingPrivate.disableNetworkType('WiMAX'); }, data: {}}); } else if (this.data_.key == 'Cellular') { addendum.push({ label: loadTimeData.getString('turnOffCellular'), command: function() { - // TODO(stevenjb): chrome.networkingPrivate.disableNetworkType - chrome.send('disableNetworkType', ['Cellular']); + chrome.networkingPrivate.disableNetworkType('Cellular'); }, data: {}}); } @@ -629,12 +681,7 @@ cr.define('options.network', function() { return menu; }, - /** - * Determines if a menu can be updated on the fly. Menus that cannot be - * updated are fully regenerated using createMenu. The advantage of - * updating a menu is that it can preserve ordering of networks avoiding - * entries from jumping around after an update. - */ + /** @override */ canUpdateMenu: function() { return this.data_.key == 'WiFi' && activeMenu_ == this.getMenuName(); }, @@ -645,12 +692,11 @@ cr.define('options.network', function() { * preferred ordering as determined by the network library. If the * ordering becomes potentially out of sync, then the updated menu is * marked for disposal on close. Reopening the menu will force a - * regeneration, which will in turn fix the ordering. + * regeneration, which will in turn fix the ordering. This method must only + * be called if canUpdateMenu() returned |true|. * @return {boolean} True if successfully updated. */ updateMenu: function() { - if (!this.canUpdateMenu()) - return false; var oldMenu = $(this.getMenuName()); var group = oldMenu.getElementsByClassName('network-menu-group')[0]; if (!group) @@ -687,7 +733,7 @@ cr.define('options.network', function() { /** * Extracts a mapping of network names to menu element and position. * @param {!Element} menu The menu to process. - * @return {Object.<string, ?{index: number, button: Element}>} + * @return {Object<string, ?{index: number, button: Element}>} * Network mapping. * @private */ @@ -707,17 +753,15 @@ cr.define('options.network', function() { /** * Adds a menu item for showing network details. * @param {!Element} parent The parent element. - * @param {Object} data Description of the network. + * @param {NetworkProperties} data Description of the network. * @private */ createNetworkOptionsCallback_: function(parent, data) { - var servicePath = data.servicePath; var menuItem = createCallback_(parent, data, getNetworkName(data), - showDetails.bind(null, servicePath), - data.iconURL); - if (data.policyManaged) + showDetails.bind(null, data.GUID)); + if (isManaged(data.Source)) menuItem.appendChild(new ManagedNetworkIndicator()); if (data.ConnectionState == 'Connected' || data.ConnectionState == 'Connecting') { @@ -753,11 +797,11 @@ cr.define('options.network', function() { this.subtitle = null; if (this.data.command) this.addEventListener('click', this.data.command); - if (this.data.iconURL) - this.iconURL = this.data.iconURL; + if (this.data.iconData) + this.iconData = this.data.iconData; else if (this.data.iconType) this.iconType = this.data.iconType; - if (this.data.policyManaged) + if (isManaged(this.data.Source)) this.showManagedNetworkIndicator(); }, }; @@ -765,22 +809,26 @@ cr.define('options.network', function() { /** * Adds a command to a menu for modifying network settings. * @param {!Element} menu Parent menu. - * @param {Object} data Description of the network. + * @param {?NetworkProperties} data Description of the network. * @param {!string} label Display name for the menu item. * @param {!Function} command Callback function. - * @param {string=} opt_iconURL Optional URL to an icon for the menu item. * @return {!Element} The created menu item. * @private */ - function createCallback_(menu, data, label, command, opt_iconURL) { + function createCallback_(menu, data, label, command) { var button = menu.ownerDocument.createElement('div'); button.className = 'network-menu-item'; - var buttonIcon = menu.ownerDocument.createElement('div'); - buttonIcon.className = 'network-menu-item-icon'; - button.appendChild(buttonIcon); - if (opt_iconURL) - buttonIcon.style.backgroundImage = url(opt_iconURL); + var buttonIconDiv = menu.ownerDocument.createElement('div'); + buttonIconDiv.className = 'network-icon'; + button.appendChild(buttonIconDiv); + if (data && isNetworkType(data.Type)) { + var networkIcon = /** @type {!CrNetworkIconElement} */ ( + document.createElement('cr-network-icon')); + buttonIconDiv.appendChild(networkIcon); + networkIcon.isListItem = true; + networkIcon.networkState = CrOncDataElement.create(data); + } var buttonLabel = menu.ownerDocument.createElement('span'); buttonLabel.className = 'network-menu-item-label'; @@ -801,7 +849,7 @@ cr.define('options.network', function() { } } if (callback != null) - button.addEventListener('click', callback); + button.addEventListener('activate', callback); else buttonLabel.classList.add('network-disabled-control'); @@ -835,18 +883,7 @@ cr.define('options.network', function() { // Wi-Fi control is always visible. this.update({key: 'WiFi', networkList: []}); - var entryAddWifi = { - label: loadTimeData.getString('addConnectionWifi'), - command: createAddConnectionCallback_('WiFi') - }; - var entryAddVPN = { - label: loadTimeData.getString('addConnectionVPN'), - command: createAddConnectionCallback_('VPN') - }; - this.update({key: 'addConnection', - iconType: 'add-connection', - menu: [entryAddWifi, entryAddVPN] - }); + this.updateAddConnectionMenuEntries_(); var prefs = options.Preferences.getInstance(); prefs.addEventListener('cros.signed.data_roaming_enabled', @@ -854,6 +891,65 @@ cr.define('options.network', function() { enableDataRoaming_ = event.value.value; }); this.endBatchUpdates(); + + this.onNetworkListChanged_(); // Trigger an initial network update + + chrome.networkingPrivate.onNetworkListChanged.addListener( + this.onNetworkListChanged_.bind(this)); + chrome.networkingPrivate.onDeviceStateListChanged.addListener( + this.onNetworkListChanged_.bind(this)); + + chrome.networkingPrivate.requestNetworkScan(); + + options.VPNProviders.addObserver(this.onVPNProvidersChanged_.bind(this)); + }, + + /** + * networkingPrivate event called when the network list has changed. + */ + onNetworkListChanged_: function() { + var networkList = this; + chrome.networkingPrivate.getDeviceStates(function(deviceStates) { + var filter = { networkType: 'All' }; + chrome.networkingPrivate.getNetworks(filter, function(networkStates) { + networkList.updateNetworkStates(deviceStates, networkStates); + }); + }); + }, + + /** + * Called when the list of VPN providers changes. Refreshes the contents of + * menus that list VPN providers. + * @private + */ + onVPNProvidersChanged_: function() { + // Refresh the contents of the VPN menu. + var index = this.indexOf('VPN'); + if (index != undefined) + this.getListItemByIndex(index).refreshMenu(); + + // Refresh the contents of the "add connection" menu. + this.updateAddConnectionMenuEntries_(); + index = this.indexOf('addConnection'); + if (index != undefined) + this.getListItemByIndex(index).refreshMenu(); + }, + + /** + * Updates the entries in the "add connection" menu, based on the VPN + * providers currently enabled in the primary user's profile. + * @private + */ + updateAddConnectionMenuEntries_: function() { + var entries = [{ + label: loadTimeData.getString('addConnectionWifi'), + command: createAddNonVPNConnectionCallback_('WiFi') + }]; + entries = entries.concat(createAddVPNConnectionEntries_()); + this.update({key: 'addConnection', + iconType: 'add-connection', + menu: entries + }); }, /** @@ -866,6 +962,35 @@ cr.define('options.network', function() { closeMenu_(); }, + /** @override */ + handleKeyDown: function(e) { + if (activeMenu_) { + // keyIdentifier does not report 'Esc' correctly + if (e.keyCode == 27 /* Esc */) { + closeMenu_(); + return; + } + + if ($(activeMenu_).handleKeyDown(e)) { + e.preventDefault(); + e.stopPropagation(); + } + return; + } + + if (e.keyIdentifier == 'Enter' || + e.keyIdentifier == 'U+0020' /* Space */) { + var selectedListItem = this.getListItemByIndex( + this.selectionModel.selectedIndex); + if (selectedListItem) { + selectedListItem.click(); + return; + } + } + + List.prototype.handleKeyDown.call(this, e); + }, + /** * Close bubble and menu when a different list item is selected. * @param {Event} event Event detailing the selection change. @@ -908,7 +1033,7 @@ cr.define('options.network', function() { /** * Updates a network control. - * @param {Object.<string,string>} data Description of the entry. + * @param {Object} data Description of the entry. */ update: function(data) { this.startBatchUpdates(); @@ -956,8 +1081,8 @@ cr.define('options.network', function() { createItem: function(entry) { if (entry.networkList) return new NetworkSelectorItem( - /** @type {{key: string, networkList: Array.<NetworkInfo>}} */( - entry)); + /** @type {{key: string, networkList: Array<!NetworkProperties>}} */ + (entry)); if (entry.command) return new NetworkButtonItem( /** @type {{key: string, subtitle: string, command: Function}} */( @@ -986,100 +1111,125 @@ cr.define('options.network', function() { var index = this.indexOf(key); if (index != undefined) { var entry = this.dataModel.item(index); - entry.iconType = active ? 'control-active' : - 'control-inactive'; + entry.iconType = active ? 'control-active' : 'control-inactive'; this.update(entry); } - } - }; + }, - /** - * Sets the default icon to use for each network type if disconnected. - * @param {!Object.<string, string>} data Mapping of network type to icon - * data url. - */ - NetworkList.setDefaultNetworkIcons = function(data) { - defaultIcons_ = Object.create(data); - }; + /** + * Updates the state of network devices and services. + * @param {!Array<{State: string, Type: string}>} deviceStates The result + * from networkingPrivate.getDeviceStates. + * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>} + * networkStates The result from networkingPrivate.getNetworks. + */ + updateNetworkStates: function(deviceStates, networkStates) { + // Update device states. + cellularDeviceState_ = undefined; + wifiDeviceState_ = undefined; + wimaxDeviceState_ = undefined; + for (var i = 0; i < deviceStates.length; ++i) { + var device = deviceStates[i]; + var type = device.Type; + var state = device.State; + if (type == 'Cellular') + cellularDeviceState_ = cellularDeviceState_ || state; + else if (type == 'WiFi') + wifiDeviceState_ = wifiDeviceState_ || state; + else if (type == 'WiMAX') + wimaxDeviceState_ = wimaxDeviceState_ || state; + } - /** - * Chrome callback for updating network controls. - * @param {{cellularAvailable: boolean, - * cellularEnabled: boolean, - * cellularSimAbsent: boolean, - * cellularSimLockType: string, - * cellularSupportsScan: boolean, - * rememberedList: Array.<NetworkInfo>, - * vpnList: Array.<NetworkInfo>, - * wifiAvailable: boolean, - * wifiEnabled: boolean, - * wimaxAvailable: boolean, - * wimaxEnabled: boolean, - * wiredList: Array.<NetworkInfo>, - * wirelessList: Array.<NetworkInfo>}} data Description of available - * network devices and their corresponding state. - */ - NetworkList.refreshNetworkData = function(data) { - var networkList = $('network-list'); - networkList.startBatchUpdates(); - cellularAvailable_ = data.cellularAvailable; - cellularEnabled_ = data.cellularEnabled; - cellularSupportsScan_ = data.cellularSupportsScan; - cellularSimAbsent_ = data.cellularSimAbsent; - cellularSimLockType_ = data.cellularSimLockType; - wimaxAvailable_ = data.wimaxAvailable; - wimaxEnabled_ = data.wimaxEnabled; - - // Only show Ethernet control if connected. - var ethernetConnection = getConnection_(data.wiredList); - if (ethernetConnection) { - var type = String('Ethernet'); - var path = ethernetConnection.servicePath; - var ethernetOptions = function() { - showDetails(path); - }; - networkList.update( + // Update active network states. + cellularNetwork_ = null; + ethernetNetwork_ = null; + for (var i = 0; i < networkStates.length; i++) { + // Note: This cast is valid since + // networkingPrivate.NetworkStateProperties is a subset of + // NetworkProperties and all missing properties are optional. + var entry = /** @type {NetworkProperties} */ (networkStates[i]); + switch (entry.Type) { + case 'Cellular': + cellularNetwork_ = cellularNetwork_ || entry; + break; + case 'Ethernet': + ethernetNetwork_ = ethernetNetwork_ || entry; + break; + } + if (cellularNetwork_ && ethernetNetwork_) + break; + } + + if (cellularNetwork_ && cellularNetwork_.GUID) { + // Get the complete set of cellular properties which includes SIM and + // Scan properties. + var networkList = this; + chrome.networkingPrivate.getProperties( + cellularNetwork_.GUID, function(cellular) { + cellularNetwork_ = /** @type {NetworkProperties} */ (cellular); + networkList.updateControls(networkStates); + }); + } else { + this.updateControls(networkStates); + } + }, + + /** + * Updates network controls. + * @param {!Array<!chrome.networkingPrivate.NetworkStateProperties>} + * networkStates The result from networkingPrivate.getNetworks. + */ + updateControls: function(networkStates) { + this.startBatchUpdates(); + + // Only show Ethernet control if connected. + if (ethernetNetwork_ && ethernetNetwork_.ConnectionState == 'Connected') { + var ethernetOptions = showDetails.bind(null, ethernetNetwork_.GUID); + this.update( { key: 'Ethernet', subtitle: loadTimeData.getString('OncConnectionStateConnected'), - iconURL: ethernetConnection.iconURL, + iconData: ethernetNetwork_, command: ethernetOptions, - policyManaged: ethernetConnection.policyManaged } - ); - } else { - networkList.deleteItem('Ethernet'); - } - - if (data.wifiEnabled) - loadData_('WiFi', data.wirelessList, data.rememberedList); - else - addEnableNetworkButton_('WiFi'); + Source: ethernetNetwork_.Source } + ); + } else { + this.deleteItem('Ethernet'); + } - // Only show cellular control if available. - if (data.cellularAvailable) { - if (data.cellularEnabled) - loadData_('Cellular', data.wirelessList, data.rememberedList); + if (wifiDeviceState_ == 'Enabled') + loadData_('WiFi', networkStates); else - addEnableNetworkButton_('Cellular'); - } else { - networkList.deleteItem('Cellular'); - } + addEnableNetworkButton_('WiFi'); + + // Only show cellular control if available. + if (cellularDeviceState_) { + if (cellularDeviceState_ == 'Enabled' && + !isCellularSimLocked(cellularNetwork_) && + !isCellularSimAbsent(cellularNetwork_)) { + loadData_('Cellular', networkStates); + } else { + addEnableNetworkButton_('Cellular'); + } + } else { + this.deleteItem('Cellular'); + } - // Only show wimax control if available. Uses cellular icons. - if (data.wimaxAvailable) { - if (data.wimaxEnabled) - loadData_('WiMAX', data.wirelessList, data.rememberedList); - else - addEnableNetworkButton_('WiMAX'); - } else { - networkList.deleteItem('WiMAX'); - } + // Only show wimax control if available. Uses cellular icons. + if (wimaxDeviceState_) { + if (wimaxDeviceState_ == 'Enabled') + loadData_('WiMAX', networkStates); + else + addEnableNetworkButton_('WiMAX'); + } else { + this.deleteItem('WiMAX'); + } - // Only show VPN control if there is at least one VPN configured. - if (data.vpnList.length > 0) - loadData_('VPN', data.vpnList, data.rememberedList); - else - networkList.deleteItem('VPN'); - networkList.endBatchUpdates(); + // Only show VPN control if there is at least one VPN configured. + if (loadData_('VPN', networkStates) == 0) + this.deleteItem('VPN'); + + this.endBatchUpdates(); + } }; /** @@ -1089,25 +1239,23 @@ cr.define('options.network', function() { */ function addEnableNetworkButton_(type) { var subtitle = loadTimeData.getString('networkDisabled'); - var icon = (type == 'WiMAX') ? 'Cellular' : type; var enableNetwork = function() { if (type == 'WiFi') sendChromeMetricsAction('Options_NetworkWifiToggle'); if (type == 'Cellular') { - if (cellularSimLockType_) { + if (isCellularSimLocked(cellularNetwork_)) { chrome.send('simOperation', ['unlock']); return; - } else if (cellularEnabled_ && cellularSimAbsent_) { + } else if (isCellularSimAbsent(cellularNetwork_)) { chrome.send('simOperation', ['configure']); return; } } - // TODO(stevenjb): chrome.networkingPrivate.enableNetworkType - chrome.send('enableNetworkType', [type]); + chrome.networkingPrivate.enableNetworkType(type); }; $('network-list').update({key: type, subtitle: subtitle, - iconType: icon, + iconType: type, command: enableNetwork}); } @@ -1171,26 +1319,34 @@ cr.define('options.network', function() { * Updates the list of available networks and their status, filtered by * network type. * @param {string} type The type of network. - * @param {Array} available The list of available networks and their status. - * @param {Array} remembered The list of remmebered networks. + * @param {Array<!chrome.networkingPrivate.NetworkStateProperties>} networks + * The list of network objects. + * @return {number} The number of visible networks matching |type|. */ - function loadData_(type, available, remembered) { - var data = {key: type}; + function loadData_(type, networks) { + var res = 0; var availableNetworks = []; - for (var i = 0; i < available.length; i++) { - if (available[i].Type == type) - availableNetworks.push(available[i]); - } - data.networkList = availableNetworks; - if (remembered) { - var rememberedNetworks = []; - for (var i = 0; i < remembered.length; i++) { - if (remembered[i].Type == type) - rememberedNetworks.push(remembered[i]); + var rememberedNetworks = []; + for (var i = 0; i < networks.length; i++) { + var network = networks[i]; + if (network.Type != type) + continue; + if (networkIsVisible(network)) { + availableNetworks.push(network); + ++res; + } + if ((type == 'WiFi' || type == 'VPN') && network.Source && + network.Source != 'None') { + rememberedNetworks.push(network); } - data.rememberedNetworks = rememberedNetworks; } + var data = { + key: type, + networkList: availableNetworks, + rememberedNetworks: rememberedNetworks + }; $('network-list').update(data); + return res; } /** @@ -1208,40 +1364,63 @@ cr.define('options.network', function() { } /** - * Fetches the active connection. - * @param {Array.<Object>} networkList List of networks. - * @return {Object} + * Creates a callback function that adds a new connection of the given type. + * This method may be used for all network types except VPN. + * @param {string} type An ONC network type + * @return {function()} The created callback. * @private */ - function getConnection_(networkList) { - if (!networkList) - return null; - for (var i = 0; i < networkList.length; i++) { - var entry = networkList[i]; - if (entry.ConnectionState == 'Connected' || - entry.ConnectionState == 'Connecting') - return entry; - } - return null; + function createAddNonVPNConnectionCallback_(type) { + return function() { + if (type == 'WiFi') + sendChromeMetricsAction('Options_NetworkJoinOtherWifi'); + chrome.send('addNonVPNConnection', [type]); + }; } /** - * Create a callback function that adds a new connection of the given type. - * @param {string} type An ONC network type + * Creates a callback function that shows the "add network" dialog for a VPN + * provider. If |opt_extensionID| is omitted, the dialog for the built-in + * OpenVPN/L2TP provider is shown. Otherwise, |opt_extensionID| identifies the + * third-party provider for which the dialog should be shown. + * @param {string=} opt_extensionID Extension ID identifying the third-party + * VPN provider for which the dialog should be shown. * @return {function()} The created callback. * @private */ - function createAddConnectionCallback_(type) { + function createVPNConnectionCallback_(opt_extensionID) { return function() { - if (type == 'WiFi') - sendChromeMetricsAction('Options_NetworkJoinOtherWifi'); - else if (type == 'VPN') - sendChromeMetricsAction('Options_NetworkJoinOtherVPN'); - chrome.send('addConnection', [type]); + sendChromeMetricsAction(opt_extensionID ? + 'Options_NetworkAddVPNThirdParty' : + 'Options_NetworkAddVPNBuiltIn'); + chrome.send('addVPNConnection', + opt_extensionID ? [opt_extensionID] : undefined); }; } /** + * Generates an "add network" entry for each VPN provider currently enabled in + * the primary user's profile. + * @return {!Array<{label: string, command: function(), data: !Object}>} The + * list of entries. + * @private + */ + function createAddVPNConnectionEntries_() { + var entries = []; + var providers = options.VPNProviders.getProviders(); + for (var i = 0; i < providers.length; ++i) { + entries.push({ + label: loadTimeData.getStringF('addConnectionVPNTemplate', + providers[i].name), + command: createVPNConnectionCallback_( + providers[i].extensionID || undefined), + data: {} + }); + } + return entries; + } + + /** * Whether the Network list is disabled. Only used for display purpose. */ cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR); diff --git a/chromium/chrome/browser/resources/options/chromeos/onc_data.js b/chromium/chrome/browser/resources/options/chromeos/onc_data.js index d6dfcf647fb..8114dbd2a33 100644 --- a/chromium/chrome/browser/resources/options/chromeos/onc_data.js +++ b/chromium/chrome/browser/resources/options/chromeos/onc_data.js @@ -21,6 +21,8 @@ cr.define('cr.onc', function() { } OncData.prototype = { + /** @return {string} The GUID of the network. */ + guid: function() { return this.data_['GUID']; }, /** * Returns either a managed property dictionary or an unmanaged value. @@ -47,9 +49,9 @@ cr.define('cr.onc', function() { * Sets the value of a property. Currently only supports unmanaged * properties. * @param {string} key The property key. - * @param {Object} value The property value to set. + * @param {?} value The property value to set. */ - setManagedProperty: function(key, value) { + setProperty: function(key, value) { var data = this.data_; while (true) { var index = key.indexOf('.'); @@ -177,6 +179,13 @@ cr.define('cr.onc', function() { return property[effective]; } return undefined; + }, + + /** + * Returns the complete ONC dictionary. + */ + getData: function() { + return this.data_; } }; diff --git a/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js b/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js index 8dd24ecf06c..4f5a152b03a 100644 --- a/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js +++ b/chromium/chrome/browser/resources/options/chromeos/preferred_networks.js @@ -5,7 +5,7 @@ cr.exportPath('options'); /** - * @typedef {{Name: string, Type: string, servicePath: string}} + * @typedef {{GUID: string, Name: string, Source: string, Type: string}} */ options.PreferredNetwork; @@ -83,8 +83,10 @@ cr.define('options', function() { DeletableItem.prototype.decorate.call(this); var label = this.ownerDocument.createElement('div'); label.textContent = this.data.Name; - if (this.data.policyManaged) + if (this.data.Source == 'DevicePolicy' || + this.data.Source == 'UserPolicy') { this.deletable = false; + } this.contentElement.appendChild(label); } }; @@ -125,11 +127,8 @@ cr.define('options', function() { /** @override */ deleteItemAtIndex: function(index) { var item = this.dataModel.item(index); - if (item) { - // TODO(stevenjb): Add removeNetwork to chrome.networkingPrivate and - // use that here. - chrome.send('removeNetwork', [item.servicePath]); - } + if (item) + chrome.networkingPrivate.forgetNetwork(item.GUID); this.dataModel.splice(index, 1); // Invalidate the list since it has a stale cache after a splice // involving a deletion. diff --git a/chromium/chrome/browser/resources/options/chromeos/vpn_providers.js b/chromium/chrome/browser/resources/options/chromeos/vpn_providers.js new file mode 100644 index 00000000000..06da41b5339 --- /dev/null +++ b/chromium/chrome/browser/resources/options/chromeos/vpn_providers.js @@ -0,0 +1,123 @@ +// Copyright 2015 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 A singleton that keeps track of the VPN providers enabled in + * the primary user's profile. + */ + +cr.define('options', function() { + /** + * @constructor + */ + function VPNProviders() { + } + + cr.addSingletonGetter(VPNProviders); + + VPNProviders.prototype = { + /** + * The VPN providers enabled in the primary user's profile. Each provider + * has a name. Third-party VPN providers additionally have an extension ID. + * @type {!Array<{name: string, extensionID: ?string}>} + * @private + */ + providers_: [], + + /** + * Observers who will be called when the list of VPN providers changes. + * @type {!Array<!function()>} + */ + observers_: [], + + /** + * The VPN providers enabled in the primary user's profile. + * @type {!Array<{name: string, extensionID: ?string}>} + */ + get providers() { + return this.providers_; + }, + set providers(providers) { + this.providers_ = providers; + for (var i = 0; i < this.observers_.length; ++i) + this.observers_[i](); + }, + + /** + * Adds an observer to be called when the list of VPN providers changes. + * @param {!function()} observer The observer to add. + * @private + */ + addObserver_: function(observer) { + this.observers_.push(observer); + }, + + /** + * Formats a network name for display purposes. If the network belongs to + * a third-party VPN provider, the provider name is added to the network + * name. + * @param {cr.onc.OncData} onc ONC data describing this network. + * @return {string} The resulting display name. + * @private + */ + formatNetworkName_: function(onc) { + var networkName = onc.getTranslatedValue('Name'); + if (onc.getActiveValue('VPN.Type') != 'ThirdPartyVPN') + return networkName; + var extensionID = onc.getActiveValue('VPN.ThirdPartyVPN.ExtensionID'); + for (var i = 0; i < this.providers_.length; ++i) { + if (extensionID == this.providers_[i].extensionID) { + return loadTimeData.getStringF('vpnNameTemplate', + this.providers_[i].name, + networkName); + } + } + return networkName; + }, + }; + + /** + * Adds an observer to be called when the list of VPN providers changes. Note + * that an observer may in turn call setProviders() but should be careful not + * to get stuck in an infinite loop as every change to the list of VPN + * providers will cause the observers to be called again. + * @param {!function()} observer The observer to add. + */ + VPNProviders.addObserver = function(observer) { + VPNProviders.getInstance().addObserver_(observer); + }; + + /** + * Returns the list of VPN providers enabled in the primary user's profile. + * @return {!Array<{name: string, extensionID: ?string}>} The list of VPN + * providers enabled in the primary user's profile. + */ + VPNProviders.getProviders = function() { + return VPNProviders.getInstance().providers; + }; + + /** + * Replaces the list of VPN providers enabled in the primary user's profile. + * @param {!Array<{name: string, extensionID: ?string}>} providers The list + * of VPN providers enabled in the primary user's profile. + */ + VPNProviders.setProviders = function(providers) { + VPNProviders.getInstance().providers = providers; + }; + + /** + * Formats a network name for display purposes. If the network belongs to a + * third-party VPN provider, the provider name is added to the network name. + * @param {cr.onc.OncData} onc ONC data describing this network. + * @return {string} The resulting display name. + */ + VPNProviders.formatNetworkName = function(onc) { + return VPNProviders.getInstance().formatNetworkName_(onc); + }; + + // Export + return { + VPNProviders: VPNProviders + }; +}); |