| Index: chrome/browser/resources/chromeos/login/oobe_screen_user_image.js
|
| 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 59eaa02509f69d7cdb7571420c42bc519afec37d..fbc3561dd67ef3918a26d7575303bf5a52eabe80 100644
|
| --- a/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js
|
| +++ b/chrome/browser/resources/chromeos/login/oobe_screen_user_image.js
|
| @@ -7,35 +7,60 @@
|
| */
|
|
|
| cr.define('oobe', function() {
|
| -
|
| var UserImagesGrid = options.UserImagesGrid;
|
| var ButtonImages = UserImagesGrid.ButtonImages;
|
|
|
| /**
|
| * Array of button URLs used on this page.
|
| * @type {Array.<string>}
|
| + * @const
|
| */
|
| - const ButtonImageUrls = [
|
| + var ButtonImageUrls = [
|
| ButtonImages.TAKE_PHOTO
|
| ];
|
|
|
| /**
|
| - * Creates a new oobe screen div.
|
| + * Creates a new OOBE screen div.
|
| * @constructor
|
| * @extends {HTMLDivElement}
|
| */
|
| 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() {
|
| var screen = $('user-image');
|
| + var isWebRTC = document.documentElement.getAttribute('camera') == 'webrtc';
|
| + UserImageScreen.prototype = isWebRTC ? UserImageScreenWebRTCProto :
|
| + UserImageScreenOldProto;
|
| UserImageScreen.decorate(screen);
|
| Oobe.getInstance().registerScreen(screen);
|
| };
|
|
|
| - UserImageScreen.prototype = {
|
| + var UserImageScreenOldProto = {
|
| __proto__: HTMLDivElement.prototype,
|
|
|
| /**
|
| @@ -102,7 +127,7 @@ cr.define('oobe', function() {
|
| okButton.id = 'ok-button';
|
| okButton.textContent = localStrings.getString('okButtonText');
|
| okButton.addEventListener('click', this.acceptImage_.bind(this));
|
| - return [ okButton ];
|
| + return [okButton];
|
| },
|
|
|
| /**
|
| @@ -322,7 +347,456 @@ cr.define('oobe', function() {
|
|
|
| /**
|
| * Updates localized content of the screen that is not updated via template.
|
| - * @public
|
| + */
|
| + updateLocalizedContent: function() {
|
| + this.updateProfileImageCaption_();
|
| + },
|
| +
|
| + /**
|
| + * Updates profile image caption.
|
| + * @private
|
| + */
|
| + updateProfileImageCaption_: function() {
|
| + this.profileImageCaption = localStrings.getString(
|
| + this.profileImageLoading_ ? 'profilePhotoLoading' : 'profilePhoto');
|
| + }
|
| + };
|
| +
|
| + var UserImageScreenWebRTCProto = {
|
| + __proto__: HTMLDivElement.prototype,
|
| +
|
| + /**
|
| + * Currently selected user image index (take photo button is with zero
|
| + * index).
|
| + * @type {number}
|
| + */
|
| + selectedUserImage_: -1,
|
| +
|
| + /** @inheritDoc */
|
| + decorate: function(element) {
|
| + var imageGrid = $('user-image-grid');
|
| + UserImagesGrid.decorate(imageGrid);
|
| +
|
| + imageGrid.addEventListener('change',
|
| + this.handleSelection_.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(
|
| + ButtonImages.PROFILE_PICTURE,
|
| + undefined, undefined, undefined,
|
| + function(el) { // Custom decorator for Profile image element.
|
| + var spinner = el.ownerDocument.createElement('div');
|
| + spinner.className = 'spinner';
|
| + var spinnerBg = el.ownerDocument.createElement('div');
|
| + spinnerBg.className = 'spinner-bg';
|
| + spinnerBg.appendChild(spinner);
|
| + el.appendChild(spinnerBg);
|
| + 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;
|
| + // Perform an early check if camera is present, without starting capture.
|
| + this.checkCameraPresence_(false, false);
|
| + },
|
| +
|
| + /**
|
| + * Header text of the screen.
|
| + * @type {string}
|
| + */
|
| + get header() {
|
| + return localStrings.getString('userImageScreenTitle');
|
| + },
|
| +
|
| + /**
|
| + * Buttons in oobe wizard's button strip.
|
| + * @type {array} Array of Buttons.
|
| + */
|
| + get buttons() {
|
| + var okButton = this.ownerDocument.createElement('button');
|
| + okButton.id = 'ok-button';
|
| + okButton.textContent = localStrings.getString('okButtonText');
|
| + okButton.addEventListener('click', this.acceptImage_.bind(this));
|
| + return [okButton];
|
| + },
|
| +
|
| + /**
|
| + * The caption to use for the Profile image preview.
|
| + * @type {string}
|
| + */
|
| + get profileImageCaption() {
|
| + return this.profileImageCaption_;
|
| + },
|
| + set profileImageCaption(value) {
|
| + this.profileImageCaption_ = value;
|
| + this.updateCaption_();
|
| + },
|
| +
|
| + /**
|
| + * True if the Profile image is being loaded.
|
| + * @type {boolean}
|
| + */
|
| + get profileImageLoading() {
|
| + return this.profileImageLoading_;
|
| + },
|
| + set profileImageLoading(value) {
|
| + this.profileImageLoading_ = value;
|
| + $('user-image-screen-main').classList[
|
| + value ? 'add' : 'remove']('profile-image-loading');
|
| + this.updateProfileImageCaption_();
|
| + },
|
| +
|
| + /**
|
| + * 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
|
| + */
|
| + handleImageActivated_: function() {
|
| + switch ($('user-image-grid').selectedItemUrl) {
|
| + case ButtonImages.TAKE_PHOTO:
|
| + this.handleTakePhoto_();
|
| + break;
|
| + default:
|
| + this.acceptImage_();
|
| + break;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * 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 {
|
| + $('ok-button').disabled = true;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * 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.
|
| + */
|
| + onBeforeShow: function(data) {
|
| + Oobe.getInstance().headerHidden = true;
|
| + $('user-image-grid').updateAndFocus();
|
| + chrome.send('onUserImageScreenShown');
|
| + // Now check again for camera presence and start capture.
|
| + this.checkCameraPresence_(true, true);
|
| + },
|
| +
|
| + /**
|
| + * Event handler that is invoked just before the screen is hidden.
|
| + */
|
| + onBeforeHide: function() {
|
| + $('user-image-stream').src = '';
|
| + },
|
| +
|
| + /**
|
| + * Accepts currently selected image, if possible.
|
| + * @private
|
| + */
|
| + acceptImage_: function() {
|
| + var okButton = $('ok-button');
|
| + if (!okButton.disabled) {
|
| + // This ensures that #ok-button won't be re-enabled again.
|
| + $('user-image-grid').disabled = true;
|
| + okButton.disabled = true;
|
| + chrome.send('onUserImageAccepted');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * @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.
|
| + * @private
|
| + */
|
| + setProfileImage_: function(imageUrl) {
|
| + this.profileImageLoading = false;
|
| + if (imageUrl !== null) {
|
| + this.profileImage_ =
|
| + $('user-image-grid').updateItem(this.profileImage_, imageUrl);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Appends received images to the list.
|
| + * @param {Array.<string>} images An array of URLs to user images.
|
| + * @private
|
| + */
|
| + setUserImages_: function(images) {
|
| + var imageGrid = $('user-image-grid');
|
| + for (var i = 0, url; url = images[i]; i++)
|
| + imageGrid.addItem(url).type = 'default';
|
| + },
|
| +
|
| + /**
|
| + * Selects user image with the given URL.
|
| + * @param {string} url URL of the image to select.
|
| + * @private
|
| + */
|
| + setSelectedImage_: function(url) {
|
| + var imageGrid = $('user-image-grid');
|
| + imageGrid.selectedItemUrl = url;
|
| + imageGrid.focus();
|
| + },
|
| +
|
| + /**
|
| + * Updates the image preview caption.
|
| + * @private
|
| + */
|
| + updateCaption_: function() {
|
| + $('user-image-preview-caption').textContent =
|
| + (this.selectionType == 'profile') ? this.profileImageCaption : '';
|
| + },
|
| +
|
| + /**
|
| + * Updates localized content of the screen that is not updated via template.
|
| */
|
| updateLocalizedContent: function() {
|
| this.updateProfileImageCaption_();
|
|
|