summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorivankr@chromium.org <ivankr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-25 12:16:02 +0000
committerivankr@chromium.org <ivankr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-25 12:16:02 +0000
commitb4c71c1b9c5daa9afd39a121ca8e68eb576acbf9 (patch)
treed6fb45a4d747f81b8ee87719965b90486ec20de2 /chrome
parent6ca5ff2de3f35d5c657bfe6c4e7e0dff33204f7b (diff)
downloadchromium_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')
-rw-r--r--chrome/app/generated_resources.grd3
-rw-r--r--chrome/browser/resources/chromeos/login/oobe.css14
-rw-r--r--chrome/browser/resources/chromeos/login/oobe_screen_user_image.js325
-rw-r--r--chrome/browser/resources/chromeos/user_images_grid.js317
-rw-r--r--chrome/browser/resources/options2/chromeos/change_picture_options.css136
-rw-r--r--chrome/browser/resources/options2/chromeos/change_picture_options.html15
-rw-r--r--chrome/browser/resources/options2/chromeos/change_picture_options.js281
-rw-r--r--chrome/browser/resources/options2/chromeos/image_picker.css37
-rw-r--r--chrome/browser/resources/options2/options.html2
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.cc96
-rw-r--r--chrome/browser/ui/webui/options2/chromeos/change_picture_options_handler2.h22
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);
};