diff options
author | ivankr@chromium.org <ivankr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-25 12:16:02 +0000 |
---|---|---|
committer | ivankr@chromium.org <ivankr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-25 12:16:02 +0000 |
commit | b4c71c1b9c5daa9afd39a121ca8e68eb576acbf9 (patch) | |
tree | d6fb45a4d747f81b8ee87719965b90486ec20de2 /chrome | |
parent | 6ca5ff2de3f35d5c657bfe6c4e7e0dff33204f7b (diff) | |
download | chromium_src-b4c71c1b9c5daa9afd39a121ca8e68eb576acbf9.zip chromium_src-b4c71c1b9c5daa9afd39a121ca8e68eb576acbf9.tar.gz chromium_src-b4c71c1b9c5daa9afd39a121ca8e68eb576acbf9.tar.bz2 |
[cros] WebRTC-enabled "Change Picture" options page.
*) Moved all camera-related code to user_image_grid.js.
*) Some HTML/JS cleanup.
*) Ephemeral users can set their picture for the current session.
BUG=122764
TEST=Manual with --enable-media-source --enable-new-oobe --enable-html5-camera
Review URL: https://chromiumcodereview.appspot.com/10536216
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@143897 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
11 files changed, 846 insertions, 402 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 8068463..2f299ac 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -13646,6 +13646,9 @@ Press any key to continue exploring. <message name="IDS_OPTIONS_CHANGE_PICTURE_TAKE_PHOTO" desc="The text on the button to take photo of the current user."> Take photo </message> + <message name="IDS_OPTIONS_CHANGE_PICTURE_PREVIEW_ALT" desc="Alt text for the big preview of user image."> + User image preview + </message> <message name="IDS_OPTIONS_CHANGE_PICTURE_PROFILE_PHOTO" desc="The text on the Google profile photo of the user."> Google Profile photo </message> diff --git a/chrome/browser/resources/chromeos/login/oobe.css b/chrome/browser/resources/chromeos/login/oobe.css index ad0fefa..a053ba0 100644 --- a/chrome/browser/resources/chromeos/login/oobe.css +++ b/chrome/browser/resources/chromeos/login/oobe.css @@ -511,13 +511,6 @@ html[oobe=old] #user-image { top: 14px; } -#user-image-preview { - border-radius: 4px; - float: right; - margin: 4px; - max-width: 220px; -} - /** * #user-image-preview can have the following classes: * .default-image: one of the default images is selected (including the grey @@ -529,6 +522,13 @@ html[oobe=old] #user-image { * .live: camera is in live mode (no photo taken yet/last photo removed). */ +#user-image-preview { + border-radius: 4px; + float: right; + margin: 4px; + max-width: 220px; +} + html[dir=rtl] #user-image-preview { float: left; } diff --git a/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js b/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js index d327bec..bd0b63a 100644 --- a/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js +++ b/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js @@ -27,28 +27,6 @@ cr.define('oobe', function() { var UserImageScreen = cr.ui.define('div'); /** - * Dimensions for camera capture. - * @const - */ - var CAPTURE_SIZE = { - height: 480, - width: 480 - }; - - /** - * Interval between consecutive camera presence checks in msec while camera is - * not present. - * @const - */ - var CAMERA_CHECK_INTERVAL_MS = 3000; - - /** - * Interval between consecutive camera liveness checks in msec. - * @const - */ - var CAMERA_LIVENESS_CHECK_MS = 1000; - - /** * Registers with Oobe. */ UserImageScreen.register = function() { @@ -75,12 +53,10 @@ cr.define('oobe', function() { var imageGrid = $('user-image-grid'); UserImagesGrid.decorate(imageGrid); - imageGrid.addEventListener('change', - this.handleSelection_.bind(this)); + imageGrid.addEventListener('select', + this.handleSelect_.bind(this)); imageGrid.addEventListener('activate', this.handleImageActivated_.bind(this)); - imageGrid.addEventListener('dblclick', - this.handleImageDblClick_.bind(this)); // Whether a button image is selected. this.buttonImageSelected_ = false; @@ -204,7 +180,7 @@ cr.define('oobe', function() { * Handles selection change. * @private */ - handleSelection_: function() { + handleSelect_: function() { var selectedItem = $('user-image-grid').selectedItem; if (selectedItem === null) return; @@ -230,17 +206,6 @@ cr.define('oobe', function() { }, /** - * Handles double click on the image grid. - * @param {Event} e Double click Event. - */ - handleImageDblClick_: function(e) { - // If an image is double-clicked and not the grid itself, handle this - // as 'OK' button button press. - if (e.target.id != 'user-image-grid') - this.acceptImage_(); - }, - - /** * Event handler that is invoked just before the screen is shown. * @param {object} data Screen init payload. */ @@ -377,12 +342,14 @@ cr.define('oobe', function() { var imageGrid = $('user-image-grid'); UserImagesGrid.decorate(imageGrid); - imageGrid.addEventListener('change', - this.handleSelection_.bind(this)); + // Preview image will track the selected item's URL. + imageGrid.previewElement = $('user-image-preview'); + imageGrid.selectionType = 'default'; + + imageGrid.addEventListener('select', + this.handleSelect_.bind(this)); imageGrid.addEventListener('activate', this.handleImageActivated_.bind(this)); - imageGrid.addEventListener('dblclick', - this.handleImageDblClick_.bind(this)); // Profile image data (if present). this.profileImage_ = imageGrid.addItem( @@ -398,18 +365,17 @@ cr.define('oobe', function() { el.id = 'profile-image'; }); this.profileImage_.type = 'profile'; - this.selectionType = 'default'; - - var video = $('user-image-stream'); - video.addEventListener('canplay', this.handleVideoStarted_.bind(this)); - video.addEventListener('timeupdate', this.handleVideoUpdate_.bind(this)); - $('take-photo').addEventListener('click', - this.handleTakePhoto_.bind(this)); - $('discard-photo').addEventListener('click', - this.handleDiscardPhoto_.bind(this)); - this.cameraImage = null; + + // Add camera stream element. + imageGrid.cameraImage = null; + // Perform an early check if camera is present, without starting capture. - this.checkCameraPresence_(false, false); + imageGrid.checkCameraPresence(false, false); + + $('take-photo').addEventListener( + 'click', this.handleTakePhoto_.bind(this)); + $('discard-photo').addEventListener( + 'click', imageGrid.discardPhoto.bind(imageGrid)); }, /** @@ -459,34 +425,6 @@ cr.define('oobe', function() { }, /** - * True when camera is in live mode (i.e. no still photo selected). - * @type {boolean} - */ - get cameraLive() { - return this.cameraLive_; - }, - set cameraLive(value) { - this.cameraLive_ = value; - $('user-image-preview').classList[value ? 'add' : 'remove']('live'); - }, - - /** - * Type of the selected image (one of 'default', 'profile', 'camera'). - * @type {string} - */ - get selectionType() { - return this.selectionType_; - }, - set selectionType(value) { - this.selectionType_ = value; - var previewClassList = $('user-image-preview').classList; - previewClassList[value == 'default' ? 'add' : 'remove']('default-image'); - previewClassList[value == 'profile' ? 'add' : 'remove']('profile-image'); - previewClassList[value == 'camera' ? 'add' : 'remove']('camera'); - this.updateCaption_(); - }, - - /** * Handles image activation (by pressing Enter). * @private */ @@ -502,102 +440,27 @@ cr.define('oobe', function() { }, /** - * Handles photo capture from the live camera stream. - * @private - */ - handleTakePhoto_: function() { - var self = this; - var photoURL = this.captureFrame_($('user-image-stream'), CAPTURE_SIZE); - chrome.send('photoTaken', [photoURL]); - // Wait until image is loaded before displaying it. - var previewImg = new Image(); - previewImg.addEventListener('load', function(e) { - self.cameraImage = this.src; - }); - previewImg.src = photoURL; - }, - - /** - * Discard current photo and return to the live camera stream. - * @private - */ - handleDiscardPhoto_: function() { - this.cameraImage = null; - }, - - /** - * Capture a single still frame from a <video> element. - * @param {HTMLVideoElement} video Video element to capture from. - * @param {{width: number, height: number}} destSize Capture size. - * @return {string} Captured frame as a data URL. - * @private - */ - captureFrame_: function(video, destSize) { - var canvas = document.createElement('canvas'); - canvas.width = destSize.width; - canvas.height = destSize.height; - var ctx = canvas.getContext('2d'); - var width = video.videoWidth; - var height = video.videoHeight; - if (width < destSize.width || height < destSize.height) { - console.error('Video capture size too small: ' + - width + 'x' + height + '!'); - } - var src = {}; - if (width / destSize.width > height / destSize.height) { - // Full height, crop left/right. - src.height = height; - src.width = height * destSize.width / destSize.height; - } else { - // Full width, crop top/bottom. - src.width = width; - src.height = width * destSize.height / destSize.width; - } - src.x = (width - src.width) / 2; - src.y = (height - src.height) / 2; - ctx.drawImage(video, src.x, src.y, src.width, src.height, - 0, 0, destSize.width, destSize.height); - return canvas.toDataURL('image/png'); - }, - - /** * Handles selection change. * @private */ - handleSelection_: function() { - var selectedItem = $('user-image-grid').selectedItem; - if (selectedItem === null) - return; - - // Update preview image URL. - var url = selectedItem.url; - $('user-image-preview-img').src = url; - - // Update current selection type. - this.selectionType = selectedItem.type; - - // Show grey silhouette with the same border as stock images. - if (/^chrome:\/\/theme\//.test(url)) - $('user-image-preview').classList.add('default-image'); - - if (ButtonImageUrls.indexOf(url) == -1) { - // Non-button image is selected. - $('ok-button').disabled = false; - chrome.send('selectImage', [url]); - } else { + handleSelect_: function() { + if (this.selectionType == 'camera' && this.cameraLive) { + // No current image selected. $('ok-button').disabled = true; + } else { + $('ok-button').disabled = false; + chrome.send('selectImage', [$('user-image-grid').selectedItemUrl]); } + this.updateCaption_(); }, /** - * Handles double click on the image grid. - * @param {Event} e Double click Event. + * Handle photo capture from the live camera stream. */ - handleImageDblClick_: function(e) { - // If an image is double-clicked and not the grid itself, handle this - // as 'OK' button button press. - if (e.target.id != 'user-image-grid') - this.acceptImage_(); + handleTakePhoto_: function(e) { + $('user-image-grid').takePhoto(function(photoURL) { + chrome.send('photoTaken', [photoURL]); + }); }, /** @@ -606,17 +469,18 @@ cr.define('oobe', function() { */ onBeforeShow: function(data) { Oobe.getInstance().headerHidden = true; - $('user-image-grid').updateAndFocus(); - chrome.send('onUserImageScreenShown'); + var imageGrid = $('user-image-grid'); + imageGrid.updateAndFocus(); // Now check again for camera presence and start capture. - this.checkCameraPresence_(true, true); + imageGrid.checkCameraPresence(true, true); + chrome.send('onUserImageScreenShown'); }, /** * Event handler that is invoked just before the screen is hidden. */ onBeforeHide: function() { - $('user-image-stream').src = ''; + $('user-image-grid').stopCamera(); }, /** @@ -634,123 +498,6 @@ cr.define('oobe', function() { }, /** - * @param {boolean} present Whether a camera is present or not. - */ - get cameraPresent() { - return this.cameraPresent_; - }, - set cameraPresent(value) { - this.cameraPresent_ = value; - if (this.cameraLive) - this.cameraImage = null; - }, - - /** - * Start camera presence check. - * @param {boolean} autoplay Whether to start capture immediately. - * @param {boolean} preselect Whether to select camera automatically. - * @private - */ - checkCameraPresence_: function(autoplay, preselect) { - $('user-image-preview').classList.remove('online'); - navigator.webkitGetUserMedia( - {video: true}, - this.handleCameraAvailable_.bind(this, autoplay, preselect), - // When ready to capture camera, poll regularly for camera presence. - this.handleCameraAbsent_.bind(this, /* recheck= */ autoplay)); - }, - - /** - * Handles successful camera check. - * @param {boolean} autoplay Whether to start capture immediately. - * @param {boolean} preselect Whether to select camera automatically. - * @param {MediaStream} stream Stream object as returned by getUserMedia. - * @private - */ - handleCameraAvailable_: function(autoplay, preselect, stream) { - if (autoplay) - $('user-image-stream').src = window.webkitURL.createObjectURL(stream); - this.cameraPresent = true; - if (preselect) - $('user-image-grid').selectedItem = this.cameraImage; - }, - - /** - * Handles camera check failure. - * @param {boolean} recheck Whether to check for camera again. - * @param {NavigatorUserMediaError=} err Error object. - * @private - */ - handleCameraAbsent_: function(recheck, err) { - this.cameraPresent = false; - $('user-image-preview').classList.remove('online'); - // |preselect| is |false| in this case to not override user's selection. - if (recheck) { - setTimeout(this.checkCameraPresence_.bind(this, true, false), - CAMERA_CHECK_INTERVAL_MS); - } - if (this.cameraLiveCheckTimer_) { - clearInterval(this.cameraLiveCheckTimer_); - this.cameraLiveCheckTimer_ = null; - } - }, - - /** - * Handles successful camera capture start. - * @private - */ - handleVideoStarted_: function() { - $('user-image-preview').classList.add('online'); - this.cameraLiveCheckTimer_ = setInterval(this.checkCameraLive_.bind(this), - CAMERA_LIVENESS_CHECK_MS); - }, - - /** - * Handles camera stream update. Called regularly (at rate no greater then - * 4/sec) while camera stream is live. - * @private - */ - handleVideoUpdate_: function() { - this.lastFrameTime_ = new Date().getTime(); - }, - - /** - * Checks if camera is still live by comparing the timestamp of the last - * 'timeupdate' event with the current time. - * @private - */ - checkCameraLive_: function() { - if (new Date().getTime() - this.lastFrameTime_ > CAMERA_LIVENESS_CHECK_MS) - this.handleCameraAbsent_(true, null); - }, - - /** - * Current image captured from camera as data URL. Setting to null will - * return to the live camera stream. - * @type {string=} - */ - get cameraImage() { - return this.cameraImage_; - }, - set cameraImage(imageUrl) { - this.cameraLive = !imageUrl; - var imageGrid = $('user-image-grid'); - if (this.cameraPresent && !imageUrl) { - imageUrl = ButtonImages.TAKE_PHOTO; - } - if (imageUrl) { - this.cameraImage_ = this.cameraImage_ ? - imageGrid.updateItem(this.cameraImage_, imageUrl) : - imageGrid.addItem(imageUrl, undefined, undefined, 0); - this.cameraImage_.type = 'camera'; - } else { - imageGrid.removeItem(this.cameraImage_); - this.cameraImage_ = null; - } - imageGrid.focus(); - }, - - /** * Updates user profile image. * @param {?string} imageUrl Image encoded as data URL. If null, user has * the default profile image, which we don't want to show. diff --git a/chrome/browser/resources/chromeos/user_images_grid.js b/chrome/browser/resources/chromeos/user_images_grid.js index f252379..6c0b321a 100644 --- a/chrome/browser/resources/chromeos/user_images_grid.js +++ b/chrome/browser/resources/chromeos/user_images_grid.js @@ -10,6 +10,28 @@ cr.define('options', function() { /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; /** + * Dimensions for camera capture. + * @const + */ + var CAPTURE_SIZE = { + height: 480, + width: 480 + }; + + /** + * Interval between consecutive camera presence checks in msec while camera is + * not present. + * @const + */ + var CAMERA_CHECK_INTERVAL_MS = 3000; + + /** + * Interval between consecutive camera liveness checks in msec. + * @const + */ + var CAMERA_LIVENESS_CHECK_MS = 1000; + + /** * Creates a new user images grid item. * @param {{url: string, title: string=, decorateFn: function=, * clickHandler: function=}} imageInfo User image URL, optional title, @@ -35,7 +57,7 @@ cr.define('options', function() { var label = imageEl.src.replace(/(.*\/|\.png)/g, ''); imageEl.setAttribute('aria-label', label.replace(/_/g, ' ')); if (typeof this.dataItem.clickHandler == 'function') - imageEl.addEventListener('click', this.dataItem.clickHandler); + imageEl.addEventListener('mousedown', this.dataItem.clickHandler); // Remove any garbage added by GridItem and ListItem decorators. this.textContent = ''; this.appendChild(imageEl); @@ -80,7 +102,7 @@ cr.define('options', function() { cr.dispatchSimpleEvent(this.grid_, 'activate'); else GridSelectionController.prototype.handleKeyDown.call(this, e); - }, + } }; /** @@ -106,6 +128,217 @@ cr.define('options', function() { this.itemConstructor = UserImagesGridItem; this.selectionModel = new ListSingleSelectionModel(); this.inProgramSelection_ = false; + this.addEventListener('dblclick', this.handleDblClick_.bind(this)); + this.addEventListener('change', this.handleChange_.bind(this)); + }, + + /** + * Handles double click on the image grid. + * @param {Event} e Double click Event. + * @private + */ + handleDblClick_: function(e) { + // If a child element is double-clicked and not the grid itself, handle + // this as 'Enter' keypress. + if (e.target != this) + cr.dispatchSimpleEvent(this, 'activate'); + }, + + /** + * Handles selection change. + * @param {Event} e Double click Event. + * @private + */ + handleChange_: function(e) { + if (this.selectedItem === null) + return; + + // Update current selection type. + this.selectionType = this.selectedItem.type; + + // Show grey silhouette with the same border as stock images. + if (/^chrome:\/\/theme\//.test(this.selectedItemUrl)) + this.previewElement.classList.add('default-image'); + + this.updatePreview_(); + + cr.dispatchSimpleEvent(this, 'select'); + }, + + /** + * Updates the preview image, if present. + * @private + */ + updatePreview_: function() { + var url = this.selectedItemUrl; + if (url && this.previewImage_) + this.previewImage_.src = url; + }, + + /** + * @param {boolean} present Whether a camera is present or not. + */ + get cameraPresent() { + return this.cameraPresent_; + }, + set cameraPresent(value) { + this.cameraPresent_ = value; + if (this.cameraLive) + this.cameraImage = null; + }, + + /** + * Start camera presence check. + * @param {boolean} autoplay Whether to start capture immediately. + * @param {boolean} preselect Whether to select camera automatically. + */ + checkCameraPresence: function(autoplay, preselect) { + this.previewElement.classList.remove('online'); + if (!this.cameraVideo_) + return; + navigator.webkitGetUserMedia( + {video: true}, + this.handleCameraAvailable_.bind(this, autoplay, preselect), + // When ready to capture camera, poll regularly for camera presence. + this.handleCameraAbsent_.bind(this, /* recheck= */ autoplay)); + }, + + /** + * Stops camera capture, if it's currently active. + */ + stopCamera: function() { + if (this.cameraLiveCheckTimer_) { + clearInterval(this.cameraLiveCheckTimer_); + this.cameraLiveCheckTimer_ = null; + } + if (this.cameraVideo_) + this.cameraVideo_.src = ''; + }, + + /** + * Handles successful camera check. + * @param {boolean} autoplay Whether to start capture immediately. + * @param {boolean} preselect Whether to select camera automatically. + * @param {MediaStream} stream Stream object as returned by getUserMedia. + * @private + */ + handleCameraAvailable_: function(autoplay, preselect, stream) { + if (autoplay) + this.cameraVideo_.src = window.webkitURL.createObjectURL(stream); + this.cameraPresent = true; + if (preselect) + this.selectedItem = this.cameraImage; + }, + + /** + * Handles camera check failure. + * @param {boolean} recheck Whether to check for camera again. + * @param {NavigatorUserMediaError=} err Error object. + * @private + */ + handleCameraAbsent_: function(recheck, err) { + this.cameraPresent = false; + this.previewElement.classList.remove('online'); + // |preselect| is |false| in this case to not override user's selection. + if (recheck) { + setTimeout(this.checkCameraPresence.bind(this, true, false), + CAMERA_CHECK_INTERVAL_MS); + } + if (this.cameraLiveCheckTimer_) { + clearInterval(this.cameraLiveCheckTimer_); + this.cameraLiveCheckTimer_ = null; + } + }, + + /** + * Handles successful camera capture start. + * @private + */ + handleVideoStarted_: function() { + this.previewElement.classList.add('online'); + this.cameraLiveCheckTimer_ = setInterval(this.checkCameraLive_.bind(this), + CAMERA_LIVENESS_CHECK_MS); + this.handleVideoUpdate_(); + }, + + /** + * Handles camera stream update. Called regularly (at rate no greater then + * 4/sec) while camera stream is live. + * @private + */ + handleVideoUpdate_: function() { + this.lastFrameTime_ = new Date().getTime(); + }, + + /** + * Checks if camera is still live by comparing the timestamp of the last + * 'timeupdate' event with the current time. + * @private + */ + checkCameraLive_: function() { + if (new Date().getTime() - this.lastFrameTime_ > CAMERA_LIVENESS_CHECK_MS) + this.handleCameraAbsent_(true, null); + }, + + /** + * Type of the selected image (one of 'default', 'profile', 'camera'). + * Setting it will update class list of |previewElement|. + * @type {string} + */ + get selectionType() { + return this.selectionType_; + }, + set selectionType(value) { + this.selectionType_ = value; + var previewClassList = this.previewElement.classList; + previewClassList[value == 'default' ? 'add' : 'remove']('default-image'); + previewClassList[value == 'profile' ? 'add' : 'remove']('profile-image'); + previewClassList[value == 'camera' ? 'add' : 'remove']('camera'); + }, + + /** + * Current image captured from camera as data URL. Setting to null will + * return to the live camera stream. + * @type {string=} + */ + get cameraImage() { + return this.cameraImage_; + }, + set cameraImage(imageUrl) { + this.cameraLive = !imageUrl; + if (this.cameraPresent && !imageUrl) + imageUrl = UserImagesGrid.ButtonImages.TAKE_PHOTO; + if (imageUrl) { + this.cameraImage_ = this.cameraImage_ ? + this.updateItem(this.cameraImage_, imageUrl) : + this.addItem(imageUrl, this.cameraTitle_, undefined, 0); + this.cameraImage_.type = 'camera'; + } else { + this.removeItem(this.cameraImage_); + this.cameraImage_ = null; + } + this.focus(); + }, + + /** + * Title for the camera element. Must be set before setting |cameraImage| + * for the first time. + * @type {string} + */ + set cameraTitle(value) { + return this.cameraTitle_ = value; + }, + + /** + * True when camera is in live mode (i.e. no still photo selected). + * @type {boolean} + */ + get cameraLive() { + return this.cameraLive_; + }, + set cameraLive(value) { + this.cameraLive_ = value; + this.previewElement.classList[value ? 'add' : 'remove']('live'); }, /** @@ -156,6 +389,86 @@ cr.define('options', function() { }, /** + * Element containing the preview image (the first IMG element) and the + * camera live stream (the first VIDEO element). + * @type {HTMLElement} + */ + get previewElement() { + // TODO(ivankr): temporary hack for non-HTML5 version. + return this.previewElement_ || this; + }, + set previewElement(value) { + this.previewElement_ = value; + this.previewImage_ = value.querySelector('img'); + this.cameraVideo_ = value.querySelector('video'); + this.cameraVideo_.addEventListener('canplay', + this.handleVideoStarted_.bind(this)); + this.cameraVideo_.addEventListener('timeupdate', + this.handleVideoUpdate_.bind(this)); + this.updatePreview_(); + }, + + /** + * Performs photo capture from the live camera stream. + * @param {function=} opt_callback Callback that receives taken photo as + * data URL. + */ + takePhoto: function(opt_callback) { + var self = this; + var photoURL = this.captureFrame_(this.cameraVideo_, CAPTURE_SIZE); + if (opt_callback && typeof opt_callback == 'function') + opt_callback(photoURL); + // Wait until image is loaded before displaying it. + var previewImg = new Image(); + previewImg.addEventListener('load', function(e) { + self.cameraImage = this.src; + }); + previewImg.src = photoURL; + }, + + /** + * Discard current photo and return to the live camera stream. + */ + discardPhoto: function() { + this.cameraImage = null; + }, + + /** + * Capture a single still frame from a <video> element. + * @param {HTMLVideoElement} video Video element to capture from. + * @param {{width: number, height: number}} destSize Capture size. + * @return {string} Captured frame as a data URL. + * @private + */ + captureFrame_: function(video, destSize) { + var canvas = document.createElement('canvas'); + canvas.width = destSize.width; + canvas.height = destSize.height; + var ctx = canvas.getContext('2d'); + var width = video.videoWidth; + var height = video.videoHeight; + if (width < destSize.width || height < destSize.height) { + console.error('Video capture size too small: ' + + width + 'x' + height + '!'); + } + var src = {}; + if (width / destSize.width > height / destSize.height) { + // Full height, crop left/right. + src.height = height; + src.width = height * destSize.width / destSize.height; + } else { + // Full width, crop top/bottom. + src.width = width; + src.height = width * destSize.height / destSize.width; + } + src.x = (width - src.width) / 2; + src.y = (height - src.height) / 2; + ctx.drawImage(video, src.x, src.y, src.width, src.height, + 0, 0, destSize.width, destSize.height); + return canvas.toDataURL('image/png'); + }, + + /** * Adds new image to the user image grid. * @param {string} src Image URL. * @param {string=} opt_title Image tooltip. diff --git a/chrome/browser/resources/options2/chromeos/change_picture_options.css b/chrome/browser/resources/options2/chromeos/change_picture_options.css new file mode 100644 index 0000000..f781790 --- /dev/null +++ b/chrome/browser/resources/options2/chromeos/change_picture_options.css @@ -0,0 +1,136 @@ +/* Copyright (c) 2012 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. */ + +#user-image-grid { + -webkit-user-drag: none; + -webkit-user-select: none; + display: inline-block; + margin: 10px; + outline: none; + /* Necessary for correct metrics calculation by grid.js. */ + overflow: hidden; + padding: 0; + width: 562px; +} + +#user-image-grid * { + margin: 0; + padding: 0; +} + +#user-image-grid img { + background-color: white; + height: 64px; + vertical-align: middle; + width: 64px; +} + +#user-image-grid [role=listitem] { + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + display: inline-block; + margin: 10px; + padding: 3px; +} + +#user-image-grid [selected] { + border: 2px solid rgb(0, 102, 204); + padding: 2px; +} + +/** + * #user-image-preview can have the following classes: + * .default-image: one of the default images is selected (including the grey + * silhouette); + * .profile-image: profile image is selected; + * .online: camera is streaming video; + * .camera: camera (live or photo) is selected; + * .live: camera is in live mode (no photo taken yet/last photo removed). + */ + +#user-image-preview { + border-radius: 4px; + display: inline-block; + margin-right: 10px; + margin-top: 20px; + max-width: 220px; + vertical-align: top; +} + +html[dir=rtl] #user-image-preview { + float: left; +} + +.user-image-preview-img { + display: block; + max-height: 220px; + max-width: 220px; +} + +[camera=webrtc] .camera.live .user-image-preview-img { + display: none; +} + +.default-image .user-image-preview-img { + background: white; + border: solid 1px #cacaca; +} + +.user-image-stream-area { + display: none; + height: 220px; + overflow: hidden; + position: relative; + width: 220px; +} + +[camera=webrtc] .camera.live .user-image-stream-area { + display: block; +} + +/* TODO(ivankr): specify dimensions from real capture size. */ +.user-image-stream { + border: solid 1px #cacaca; + height: 220px; + /* Center image for 4:3 aspect ratio. */ + left: -16.6%; + position: absolute; + visibility: hidden; +} + +.online .user-image-stream { + visibility: visible; +} + +.user-image-stream-area .spinner { + -webkit-margin-before: 14px; + -webkit-margin-start: 14px; + display: none; + position: absolute; +} + +.camera.live:not(.online) .user-image-stream-area .spinner { + display: block; +} + +#discard-photo, +#take-photo { + display: none; + height: 25px; + margin: 4px 1px; + padding: 0; + width: 220px; +} + +[camera=webrtc] .camera:not(.live) #discard-photo { + background: url('chrome://theme/IDR_USER_IMAGE_RECYCLE') + no-repeat center 0; + display: block; +} + +[camera=webrtc] .camera.live.online #take-photo { + background: url('chrome://theme/IDR_USER_IMAGE_CAPTURE') + no-repeat center -1px; + display: block; +} diff --git a/chrome/browser/resources/options2/chromeos/change_picture_options.html b/chrome/browser/resources/options2/chromeos/change_picture_options.html index 784f0ba..7bf17a7 100644 --- a/chrome/browser/resources/options2/chromeos/change_picture_options.html +++ b/chrome/browser/resources/options2/chromeos/change_picture_options.html @@ -1,9 +1,18 @@ -<div id="change-picture-page" class="page" hidden> +<div id="change-picture-page" class="page" i18n-values="camera:cameraType" hidden> <div class="close-button"></div> <h1 i18n-content="changePicturePage"></h1> <div class="content-area"> - <span i18n-content="changePicturePageDescription"></span> - <grid id="images-grid" class="image-picker"></grid> + <div i18n-content="changePicturePageDescription"></div> + <grid id="user-image-grid" class="user-image-picker"></grid> + <div id="user-image-preview"> + <img class="user-image-preview-img" i18n-values="alt:previewAltText"> + <div class="user-image-stream-area"> + <video class="user-image-stream" autoplay></video> + <div class="spinner"></div> + </div> + <button id="discard-photo"></button> + <button id="take-photo"></button> + </div> </div> <div class="action-area"> <div class="button-strip"> diff --git a/chrome/browser/resources/options2/chromeos/change_picture_options.js b/chrome/browser/resources/options2/chromeos/change_picture_options.js index 87bb16a..ddd16e6 100644 --- a/chrome/browser/resources/options2/chromeos/change_picture_options.js +++ b/chrome/browser/resources/options2/chromeos/change_picture_options.js @@ -26,6 +26,11 @@ cr.define('options', function() { * @constructor */ function ChangePictureOptions() { + var isWebRTC = $('change-picture-page').getAttribute('camera') == 'webrtc'; + ChangePictureOptions.prototype = isWebRTC ? + ChangePictureOptionsWebRTCProto : ChangePictureOptionsOldProto; + // |this| has been already created so it's |__proto__| has to be reset. + this.__proto__ = ChangePictureOptions.prototype; OptionsPage.call( this, 'changePicture', @@ -35,7 +40,7 @@ cr.define('options', function() { cr.addSingletonGetter(ChangePictureOptions); - ChangePictureOptions.prototype = { + var ChangePictureOptionsOldProto = { // Inherit ChangePictureOptions from OptionsPage. __proto__: options.OptionsPage.prototype, @@ -46,30 +51,23 @@ cr.define('options', function() { // Call base class implementation to start preferences initialization. OptionsPage.prototype.initializePage.call(this); - var imageGrid = $('images-grid'); + var imageGrid = $('user-image-grid'); UserImagesGrid.decorate(imageGrid); - imageGrid.addEventListener('change', + imageGrid.addEventListener('select', this.handleImageSelected_.bind(this)); imageGrid.addEventListener('activate', this.handleImageActivated_.bind(this)); - imageGrid.addEventListener('dblclick', - this.handleImageDblClick_.bind(this)); - - // Ephemeral users can choose from the standard pictures only. This is - // because a custom image would have to be written to a file outside the - // user's cryptohome where its removal on logout could not be guaranteed. - if (!this.userIsEphemeral_()) { - // Add the "Choose file" button. - imageGrid.addItem(ButtonImages.CHOOSE_FILE, - loadTimeData.getString('chooseFile'), - this.handleChooseFile_.bind(this)); - - // Profile image data. - this.profileImage_ = imageGrid.addItem( - ButtonImages.PROFILE_PICTURE, - loadTimeData.getString('profilePhotoLoading')); - } + + // Add the "Choose file" button. + imageGrid.addItem(ButtonImages.CHOOSE_FILE, + loadTimeData.getString('chooseFile'), + this.handleChooseFile_.bind(this)); + + // Profile image data. + this.profileImage_ = imageGrid.addItem( + ButtonImages.PROFILE_PICTURE, + loadTimeData.getString('profilePhotoLoading')); // Old user image data (if present). this.oldImage_ = null; @@ -83,7 +81,7 @@ cr.define('options', function() { * Called right after the page has been shown to user. */ didShowPage: function() { - $('images-grid').updateAndFocus(); + $('user-image-grid').updateAndFocus(); chrome.send('onChangePicturePageShown'); }, @@ -91,7 +89,7 @@ cr.define('options', function() { * Called right before the page is hidden. */ willHidePage: function() { - var imageGrid = $('images-grid'); + var imageGrid = $('user-image-grid'); imageGrid.blur(); // Make sure the image grid is not active. if (this.oldImage_) { imageGrid.removeItem(this.oldImage_); @@ -139,7 +137,7 @@ cr.define('options', function() { * @private */ handleImageSelected_: function() { - var imageGrid = $('images-grid'); + var imageGrid = $('user-image-grid'); var url = imageGrid.selectedItemUrl; // Ignore deselection, selection change caused by program itself and // selection of one of the action buttons. @@ -155,7 +153,7 @@ cr.define('options', function() { * @private */ handleImageActivated_: function() { - switch ($('images-grid').selectedItemUrl) { + switch ($('user-image-grid').selectedItemUrl) { case ButtonImages.TAKE_PHOTO: this.handleTakePhoto_(); break; @@ -169,18 +167,6 @@ cr.define('options', function() { }, /** - * Handles double click on the image grid. - * @param {Event} e Double click Event. - */ - handleImageDblClick_: function(e) { - // Close page unless the click target is the grid itself or - // any of the buttons. - var url = e.target.src; - if (url && ButtonImageUrls.indexOf(url) == -1) - this.closePage_(); - }, - - /** * URL of the current user image. * @type {string} */ @@ -195,11 +181,8 @@ cr.define('options', function() { * @private */ setCameraPresent_: function(present) { - var imageGrid = $('images-grid'); - // Ephemeral users can choose from the standard pictures only. This is - // because a custom image would have to be written to a file outside the - // user's cryptohome where its removal on logout could not be guaranteed. - var showTakePhotoButton = present && !this.userIsEphemeral_(); + var imageGrid = $('user-image-grid'); + var showTakePhotoButton = present; if (showTakePhotoButton && !this.takePhotoButton_) { this.takePhotoButton_ = imageGrid.addItem( ButtonImages.TAKE_PHOTO, @@ -218,7 +201,7 @@ cr.define('options', function() { * @private */ setOldImage_: function() { - var imageGrid = $('images-grid'); + var imageGrid = $('user-image-grid'); var url = this.currentUserImageUrl; if (this.oldImage_) { this.oldImage_ = imageGrid.updateItem(this.oldImage_, url); @@ -237,7 +220,7 @@ cr.define('options', function() { * @private */ setProfileImage_: function(imageUrl, select) { - var imageGrid = $('images-grid'); + var imageGrid = $('user-image-grid'); this.profileImage_ = imageGrid.updateItem( this.profileImage_, imageUrl, loadTimeData.getString('profilePhoto')); if (select) @@ -250,7 +233,7 @@ cr.define('options', function() { * @private */ setSelectedImage_: function(url) { - $('images-grid').selectedItemUrl = url; + $('user-image-grid').selectedItemUrl = url; }, /** @@ -259,19 +242,219 @@ cr.define('options', function() { * @private */ setDefaultImages_: function(images) { - var imageGrid = $('images-grid'); + var imageGrid = $('user-image-grid'); for (var i = 0, url; url = images[i]; i++) { imageGrid.addItem(url); } }, + }; + + var ChangePictureOptionsWebRTCProto = { + // Inherit ChangePictureOptions from OptionsPage. + __proto__: options.OptionsPage.prototype, + + /** + * Initializes ChangePictureOptions page. + */ + initializePage: function() { + // Call base class implementation to start preferences initialization. + OptionsPage.prototype.initializePage.call(this); + + var imageGrid = $('user-image-grid'); + UserImagesGrid.decorate(imageGrid); + + // Preview image will track the selected item's URL. + imageGrid.previewElement = $('user-image-preview'); + imageGrid.selectionType = 'default'; + + imageGrid.addEventListener('select', + this.handleImageSelected_.bind(this)); + imageGrid.addEventListener('activate', + this.handleImageActivated_.bind(this)); + + // Set the title for "Take Photo" button. + imageGrid.cameraTitle = loadTimeData.getString('takePhoto'); + + // Add the "Choose file" button. + imageGrid.addItem(ButtonImages.CHOOSE_FILE, + loadTimeData.getString('chooseFile'), + this.handleChooseFile_.bind(this)).type = 'file'; + + // Profile image data. + this.profileImage_ = imageGrid.addItem( + ButtonImages.PROFILE_PICTURE, + loadTimeData.getString('profilePhotoLoading')); + this.profileImage_.type = 'profile'; + + $('take-photo').addEventListener( + 'click', this.handleTakePhoto_.bind(this)); + $('discard-photo').addEventListener( + 'click', imageGrid.discardPhoto.bind(imageGrid)); + + // Old user image data (if present). + this.oldImage_ = null; + + $('change-picture-overlay-confirm').addEventListener( + 'click', this.closePage_.bind(this)); + + chrome.send('onChangePicturePageInitialized'); + }, + + /** + * Called right after the page has been shown to user. + */ + didShowPage: function() { + var imageGrid = $('user-image-grid'); + imageGrid.updateAndFocus(); + // Reset camera element. + imageGrid.cameraImage = null; + // Autoplay but do not preselect. + imageGrid.checkCameraPresence(true, false); + chrome.send('onChangePicturePageShown'); + }, + + /** + * Called right before the page is hidden. + */ + willHidePage: function() { + var imageGrid = $('user-image-grid'); + imageGrid.blur(); // Make sure the image grid is not active. + imageGrid.stopCamera(); + if (this.oldImage_) { + imageGrid.removeItem(this.oldImage_); + this.oldImage_ = null; + } + }, + + /** + * Called right after the page has been hidden. + */ + // TODO(ivankr): both callbacks are required as only one of them is called + // depending on the way the page was closed, see http://crbug.com/118923. + didClosePage: function() { + this.willHidePage(); + }, + + /** + * Closes current page, returning back to Personal Stuff page. + * @private + */ + closePage_: function() { + OptionsPage.closeOverlay(); + }, + + /** + * Handles "Take photo" button activation. + * @private + */ + handleTakePhoto_: function() { + $('user-image-grid').takePhoto(function(photoURL) { + chrome.send('photoTaken', [photoURL]); + }); + }, + + /** + * Handles "Choose a file" button activation. + * @private + */ + handleChooseFile_: function() { + chrome.send('chooseFile'); + this.closePage_(); + }, + + /** + * Handles image selection change. + * @private + */ + handleImageSelected_: function() { + var imageGrid = $('user-image-grid'); + var url = imageGrid.selectedItemUrl; + // Ignore selection change caused by program itself and selection of one + // of the action buttons. + if (!imageGrid.inProgramSelection && + url != ButtonImages.TAKE_PHOTO && url != ButtonImages.CHOOSE_FILE) { + chrome.send('selectImage', [url]); + } + }, + + /** + * Handles image activation (by pressing Enter). + * @private + */ + handleImageActivated_: function() { + switch ($('user-image-grid').selectedItemUrl) { + case ButtonImages.TAKE_PHOTO: + this.handleTakePhoto_(); + break; + case ButtonImages.CHOOSE_FILE: + this.handleChooseFile_(); + break; + default: + this.closePage_(); + break; + } + }, /** - * Returns whether the user is logged in as ephemeral. - * @return {boolean} True if the user is logged in as ephemeral. + * URL of the current user image. + * @type {string} + */ + get currentUserImageUrl() { + return 'chrome://userimage/' + BrowserOptions.getLoggedInUsername() + + '?id=' + (new Date()).getTime() + '&animated'; + }, + + /** + * Adds or updates old user image taken from file/camera (neither a profile + * image nor a default one). * @private */ - userIsEphemeral_: function() { - return loadTimeData.getBoolean('userIsEphemeral'); + setOldImage_: function() { + var imageGrid = $('user-image-grid'); + var url = this.currentUserImageUrl; + if (this.oldImage_) { + this.oldImage_ = imageGrid.updateItem(this.oldImage_, url); + } else { + // Insert next to the profile image. + var pos = imageGrid.indexOf(this.profileImage_) + 1; + this.oldImage_ = imageGrid.addItem(url, undefined, undefined, pos); + imageGrid.selectedItem = this.oldImage_; + } + }, + + /** + * Updates user's profile image. + * @param {string} imageUrl Profile image, encoded as data URL. + * @param {boolean} select If true, profile image should be selected. + * @private + */ + setProfileImage_: function(imageUrl, select) { + var imageGrid = $('user-image-grid'); + this.profileImage_ = imageGrid.updateItem( + this.profileImage_, imageUrl, loadTimeData.getString('profilePhoto')); + if (select) + imageGrid.selectedItem = this.profileImage_; + }, + + /** + * Selects user image with the given URL. + * @param {string} url URL of the image to select. + * @private + */ + setSelectedImage_: function(url) { + $('user-image-grid').selectedItemUrl = url; + }, + + /** + * Appends default images to the image grid. Should only be called once. + * @param {Array.<string>} images An array of URLs to default images. + * @private + */ + setDefaultImages_: function(images) { + var imageGrid = $('user-image-grid'); + for (var i = 0, url; url = images[i]; i++) { + imageGrid.addItem(url).type = 'default'; + } }, }; diff --git a/chrome/browser/resources/options2/chromeos/image_picker.css b/chrome/browser/resources/options2/chromeos/image_picker.css deleted file mode 100644 index 4cb5af1..0000000 --- a/chrome/browser/resources/options2/chromeos/image_picker.css +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright (c) 2012 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. */ - -.image-picker { - -webkit-user-drag: none; - -webkit-user-select: none; - margin: 10px; - outline: none; - padding: 10px; - width: 600px; -} - -.image-picker * { - margin: 0; - padding: 0; -} - -.image-picker img { - background-color: white; - height: 64px; - vertical-align: middle; - width: 64px; -} - -.image-picker [role=listitem] { - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px; - display: inline-block; - margin: 10px; - padding: 3px; -} - -.image-picker [selected] { - border: 2px solid rgb(0, 102, 204); - padding: 2px; -} diff --git a/chrome/browser/resources/options2/options.html b/chrome/browser/resources/options2/options.html index 6cfc3ab..e28a48d 100644 --- a/chrome/browser/resources/options2/options.html +++ b/chrome/browser/resources/options2/options.html @@ -37,8 +37,8 @@ <if expr="pp_ifdef('chromeos')"> <link rel="stylesheet" href="chromeos/accounts_options_page.css"> <link rel="stylesheet" href="chromeos/bluetooth.css"> + <link rel="stylesheet" href="chromeos/change_picture_options.css"> <link rel="stylesheet" href="chromeos/display_options.css"> - <link rel="stylesheet" href="chromeos/image_picker.css"> <link rel="stylesheet" href="chromeos/internet_detail.css"> <link rel="stylesheet" href="chromeos/pointer_overlay.css"> </if> diff --git a/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.cc b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.cc index cbf92cd..f9e043d 100644 --- a/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.cc +++ b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.cc @@ -6,6 +6,7 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/command_line.h" #include "base/metrics/histogram.h" #include "base/path_service.h" #include "base/string_util.h" @@ -22,12 +23,15 @@ #include "chrome/browser/ui/webui/web_ui_util.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" #include "chrome/common/url_constants.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/web_ui.h" #include "content/public/common/url_constants.h" +#include "googleurl/src/gurl.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" +#include "net/base/data_url.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/views/widget/widget.h" @@ -75,6 +79,8 @@ ChangePictureOptionsHandler::ChangePictureOptionsHandler() ChangePictureOptionsHandler::~ChangePictureOptionsHandler() { if (select_file_dialog_.get()) select_file_dialog_->ListenerDestroyed(); + if (image_decoder_.get()) + image_decoder_->set_delegate(NULL); } void ChangePictureOptionsHandler::GetLocalizedValues( @@ -93,9 +99,12 @@ void ChangePictureOptionsHandler::GetLocalizedValues( localized_strings->SetString("profilePhotoLoading", l10n_util::GetStringUTF16( IDS_OPTIONS_CHANGE_PICTURE_PROFILE_LOADING_PHOTO)); - - localized_strings->SetBoolean("userIsEphemeral", - UserManager::Get()->IsCurrentUserEphemeral()); + localized_strings->SetString("previewAltText", + l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_PREVIEW_ALT)); + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableHtml5Camera)) + localized_strings->SetString("cameraType", "webrtc"); + else + localized_strings->SetString("cameraType", "old"); } void ChangePictureOptionsHandler::RegisterMessages() { @@ -105,6 +114,9 @@ void ChangePictureOptionsHandler::RegisterMessages() { web_ui()->RegisterMessageCallback("takePhoto", base::Bind(&ChangePictureOptionsHandler::HandleTakePhoto, base::Unretained(this))); + web_ui()->RegisterMessageCallback("photoTaken", + base::Bind(&ChangePictureOptionsHandler::HandlePhotoTaken, + base::Unretained(this))); web_ui()->RegisterMessageCallback("onChangePicturePageShown", base::Bind(&ChangePictureOptionsHandler::HandlePageShown, base::Unretained(this))); @@ -160,20 +172,45 @@ void ChangePictureOptionsHandler::HandleTakePhoto(const ListValue* args) { window->Show(); } +void ChangePictureOptionsHandler::HandlePhotoTaken( + const base::ListValue* args) { + std::string image_url; + if (!args || args->GetSize() != 1 || !args->GetString(0, &image_url)) + NOTREACHED(); + DCHECK(!image_url.empty()); + + std::string mime_type, charset, raw_data; + if (!net::DataURL::Parse(GURL(image_url), &mime_type, &charset, &raw_data)) + NOTREACHED(); + DCHECK_EQ("image/png", mime_type); + + user_photo_ = gfx::ImageSkia(); + user_photo_data_url_ = image_url; + + if (image_decoder_.get()) + image_decoder_->set_delegate(NULL); + image_decoder_ = new ImageDecoder(this, raw_data); + image_decoder_->Start(); +} + void ChangePictureOptionsHandler::HandlePageInitialized( const base::ListValue* args) { DCHECK(args && args->empty()); - // If no camera presence check has been performed in this session, - // start one now. - if (CameraDetector::camera_presence() == - CameraDetector::kCameraPresenceUnknown) { - CheckCameraPresence(); - } - // While the check is in progress, use previous camera presence state and - // presume it is present if no check has been performed yet. - SetCameraPresent(CameraDetector::camera_presence() != - CameraDetector::kCameraAbsent); + if (!CommandLine::ForCurrentProcess()-> + HasSwitch(switches::kEnableHtml5Camera)) { + // If no camera presence check has been performed in this session, + // start one now. + if (CameraDetector::camera_presence() == + CameraDetector::kCameraPresenceUnknown) { + CheckCameraPresence(); + } + + // While the check is in progress, use previous camera presence state and + // presume it is present if no check has been performed yet. + SetCameraPresent(CameraDetector::camera_presence() != + CameraDetector::kCameraAbsent); + } SendDefaultImages(); } @@ -250,6 +287,7 @@ void ChangePictureOptionsHandler::HandleSelectImage(const ListValue* args) { UserManager* user_manager = UserManager::Get(); const User& user = user_manager->GetLoggedInUser(); int image_index = User::kInvalidImageIndex; + bool waiting_for_camera_photo = false; if (StartsWithASCII(image_url, chrome::kChromeUIUserImageURL, false)) { // Image from file/camera uses kChromeUIUserImageURL as URL while @@ -273,6 +311,15 @@ void ChangePictureOptionsHandler::HandleSelectImage(const ListValue* args) { image_index, kHistogramImagesCount); VLOG(1) << "Selected default user image: " << image_index; + } else if (image_url == user_photo_data_url_) { + // Camera image is selected. + if (user_photo_.empty()) { + DCHECK(image_decoder_.get()); + waiting_for_camera_photo = true; + VLOG(1) << "Still waiting for camera image to decode"; + } else { + OnPhotoAccepted(user_photo_); + } } else { // Profile image selected. Could be previous (old) user image. user_manager->SaveUserImageFromProfileImage(user.email()); @@ -289,6 +336,10 @@ void ChangePictureOptionsHandler::HandleSelectImage(const ListValue* args) { VLOG(1) << "Selected profile image"; } } + + // Ignore the result of the previous decoding if it's no longer needed. + if (!waiting_for_camera_photo && image_decoder_.get()) + image_decoder_->set_delegate(NULL); } void ChangePictureOptionsHandler::FileSelected(const FilePath& path, @@ -300,6 +351,7 @@ void ChangePictureOptionsHandler::FileSelected(const FilePath& path, UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", kHistogramImageFromFile, kHistogramImagesCount); + VLOG(1) << "Selected image from file"; } void ChangePictureOptionsHandler::OnPhotoAccepted(const gfx::ImageSkia& photo) { @@ -309,9 +361,13 @@ void ChangePictureOptionsHandler::OnPhotoAccepted(const gfx::ImageSkia& photo) { UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", kHistogramImageFromCamera, kHistogramImagesCount); + VLOG(1) << "Selected camera photo"; } void ChangePictureOptionsHandler::CheckCameraPresence() { + // For WebRTC, camera presence checked is done on JS side. + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableHtml5Camera)) + return; CameraDetector::StartPresenceCheck( base::Bind(&ChangePictureOptionsHandler::OnCameraPresenceCheckDone, weak_factory_.GetWeakPtr())); @@ -348,5 +404,19 @@ gfx::NativeWindow ChangePictureOptionsHandler::GetBrowserWindow() const { return browser->window()->GetNativeWindow(); } +void ChangePictureOptionsHandler::OnImageDecoded( + const ImageDecoder* decoder, + const SkBitmap& decoded_image) { + DCHECK_EQ(image_decoder_.get(), decoder); + image_decoder_ = NULL; + user_photo_ = gfx::ImageSkia(decoded_image); + OnPhotoAccepted(user_photo_); +} + +void ChangePictureOptionsHandler::OnDecodeImageFailed( + const ImageDecoder* decoder) { + NOTREACHED() << "Failed to decode PNG image from WebUI"; +} + } // namespace options2 } // namespace chromeos diff --git a/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.h b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.h index 14561a0..17a3463 100644 --- a/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.h +++ b/chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.h @@ -7,6 +7,7 @@ #include "base/memory/weak_ptr.h" #include "chrome/browser/chromeos/options/take_photo_dialog.h" +#include "chrome/browser/image_decoder.h" #include "chrome/browser/ui/select_file_dialog.h" #include "chrome/browser/ui/webui/options2/options_ui2.h" #include "content/public/browser/notification_registrar.h" @@ -24,7 +25,8 @@ namespace options2 { // ChromeOS user image options page UI handler. class ChangePictureOptionsHandler : public ::options2::OptionsPageUIHandler, public SelectFileDialog::Listener, - public TakePhotoDialog::Delegate { + public TakePhotoDialog::Delegate, + public ImageDecoder::Delegate { public: ChangePictureOptionsHandler(); virtual ~ChangePictureOptionsHandler(); @@ -63,6 +65,9 @@ class ChangePictureOptionsHandler : public ::options2::OptionsPageUIHandler, // Opens the camera capture dialog. void HandleTakePhoto(const base::ListValue* args); + // Handles photo taken with WebRTC UI. + void HandlePhotoTaken(const base::ListValue* args); + // Gets the list of available user images and sends it to the page. void HandleGetAvailableImages(const base::ListValue* args); @@ -94,6 +99,11 @@ class ChangePictureOptionsHandler : public ::options2::OptionsPageUIHandler, // Returns handle to browser window or NULL if it can't be found. gfx::NativeWindow GetBrowserWindow() const; + // Overriden from ImageDecoder::Delegate: + virtual void OnImageDecoded(const ImageDecoder* decoder, + const SkBitmap& decoded_image) OVERRIDE; + virtual void OnDecodeImageFailed(const ImageDecoder* decoder) OVERRIDE; + scoped_refptr<SelectFileDialog> select_file_dialog_; // Previous user image from camera/file and its data URL. @@ -103,10 +113,20 @@ class ChangePictureOptionsHandler : public ::options2::OptionsPageUIHandler, // Index of the previous user image. int previous_image_index_; + // Last user photo, if taken. + gfx::ImageSkia user_photo_; + + // Data URL for |user_photo_|. + std::string user_photo_data_url_; + content::NotificationRegistrar registrar_; base::WeakPtrFactory<ChangePictureOptionsHandler> weak_factory_; + // Last ImageDecoder instance used to decode an image blob received by + // HandlePhotoTaken. + scoped_refptr<ImageDecoder> image_decoder_; + DISALLOW_COPY_AND_ASSIGN(ChangePictureOptionsHandler); }; |