OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview Out of the box experience flow (OOBE). | 6 * @fileoverview Out of the box experience flow (OOBE). |
7 * This is the main code for the OOBE WebUI implementation. | 7 * This is the main code for the OOBE WebUI implementation. |
8 */ | 8 */ |
9 | 9 |
10 var localStrings = new LocalStrings(); | 10 var localStrings = new LocalStrings(); |
11 | 11 |
12 | |
13 cr.define('cr.ui', function() { | 12 cr.define('cr.ui', function() { |
14 const SCREEN_SIGNIN = 'signin'; | 13 var DisplayManager = cr.ui.login.DisplayManager; |
15 const SCREEN_GAIA_SIGNIN = 'gaia-signin'; | |
16 const SCREEN_ACCOUNT_PICKER = 'account-picker'; | |
17 | |
18 /* Accelerator identifiers. Must be kept in sync with webui_login_view.cc. */ | |
19 const ACCELERATOR_ACCESSIBILITY = 'accessibility'; | |
20 const ACCELERATOR_CANCEL = 'cancel'; | |
21 const ACCELERATOR_ENROLLMENT = 'enrollment'; | |
22 | 14 |
23 /** | 15 /** |
24 * Constructs an Out of box controller. It manages initialization of screens, | 16 * Constructs an Out of box controller. It manages initialization of screens, |
25 * transitions, error messages display. | 17 * transitions, error messages display. |
26 * | 18 * @extends {DisplayManager} |
27 * @constructor | 19 * @constructor |
28 */ | 20 */ |
29 function Oobe() { | 21 function Oobe() { |
30 } | 22 } |
31 | 23 |
32 cr.addSingletonGetter(Oobe); | 24 cr.addSingletonGetter(Oobe); |
33 | 25 |
34 Oobe.prototype = { | 26 Oobe.prototype = { |
35 /** | 27 __proto__: DisplayManager.prototype, |
36 * Registered screens. | |
37 */ | |
38 screens_: [], | |
39 | |
40 /** | |
41 * Current OOBE step, index in the screens array. | |
42 * @type {number} | |
43 */ | |
44 currentStep_: 0, | |
45 | |
46 /** | |
47 * Gets current screen element. | |
48 * @type {HTMLElement} | |
49 */ | |
50 get currentScreen() { | |
51 return $(this.screens_[this.currentStep_]); | |
52 }, | |
53 | |
54 /** | |
55 * Hides/shows header (Shutdown/Add User/Cancel buttons). | |
56 * @param {bool} hidden Whether header is hidden. | |
57 */ | |
58 set headerHidden(hidden) { | |
59 $('login-header-bar').hidden = hidden; | |
60 }, | |
61 | |
62 /** | |
63 * Handle accelerators. | |
64 * @param {string} name Accelerator name. | |
65 */ | |
66 handleAccelerator: function(name) { | |
67 if (name == ACCELERATOR_ACCESSIBILITY) { | |
68 chrome.send('toggleAccessibility', []); | |
69 } else if (name == ACCELERATOR_CANCEL) { | |
70 if (this.currentScreen.cancel) { | |
71 this.currentScreen.cancel(); | |
72 } | |
73 } else if (ACCELERATOR_ENROLLMENT) { | |
74 var currentStepId = this.screens_[this.currentStep_]; | |
75 if (currentStepId == SCREEN_SIGNIN || | |
76 currentStepId == SCREEN_GAIA_SIGNIN) { | |
77 chrome.send('toggleEnrollmentScreen', []); | |
78 } | |
79 } | |
80 }, | |
81 | |
82 /** | |
83 * Appends buttons to the button strip. | |
84 * @param {Array} buttons Array with the buttons to append. | |
85 */ | |
86 appendButtons_ : function(buttons) { | |
87 if (buttons) { | |
88 var buttonStrip = $('button-strip'); | |
89 for (var i = 0; i < buttons.length; ++i) { | |
90 var button = buttons[i]; | |
91 buttonStrip.appendChild(button); | |
92 } | |
93 } | |
94 }, | |
95 | |
96 /** | |
97 * Updates a step's css classes to reflect left, current, or right position. | |
98 * @param {number} stepIndex step index. | |
99 * @param {string} state one of 'left', 'current', 'right'. | |
100 */ | |
101 updateStep_: function(stepIndex, state) { | |
102 var stepId = this.screens_[stepIndex]; | |
103 var step = $(stepId); | |
104 var header = $('header-' + stepId); | |
105 var states = [ 'left', 'right', 'current' ]; | |
106 for (var i = 0; i < states.length; ++i) { | |
107 if (states[i] != state) { | |
108 step.classList.remove(states[i]); | |
109 header.classList.remove(states[i]); | |
110 } | |
111 } | |
112 step.classList.add(state); | |
113 header.classList.add(state); | |
114 }, | |
115 | |
116 /** | |
117 * Switches to the next OOBE step. | |
118 * @param {number} nextStepIndex Index of the next step. | |
119 */ | |
120 toggleStep_: function(nextStepIndex, screenData) { | |
121 var currentStepId = this.screens_[this.currentStep_]; | |
122 var nextStepId = this.screens_[nextStepIndex]; | |
123 var oldStep = $(currentStepId); | |
124 var newStep = $(nextStepId); | |
125 var newHeader = $('header-' + nextStepId); | |
126 | |
127 if (oldStep.onBeforeHide) | |
128 oldStep.onBeforeHide(); | |
129 | |
130 if (newStep.onBeforeShow) | |
131 newStep.onBeforeShow(screenData); | |
132 | |
133 newStep.classList.remove('hidden'); | |
134 | |
135 if (Oobe.isOobeUI()) { | |
136 // Start gliding animation for OOBE steps. | |
137 if (nextStepIndex > this.currentStep_) { | |
138 for (var i = this.currentStep_; i < nextStepIndex; ++i) | |
139 this.updateStep_(i, 'left'); | |
140 this.updateStep_(nextStepIndex, 'current'); | |
141 } else if (nextStepIndex < this.currentStep_) { | |
142 for (var i = this.currentStep_; i > nextStepIndex; --i) | |
143 this.updateStep_(i, 'right'); | |
144 this.updateStep_(nextStepIndex, 'current'); | |
145 } | |
146 } else { | |
147 // Start fading animation for login display. | |
148 oldStep.classList.add('faded'); | |
149 newStep.classList.remove('faded'); | |
150 } | |
151 | |
152 // Adjust inner container height based on new step's height. | |
153 $('inner-container').style.height = newStep.offsetHeight + 'px'; | |
154 | |
155 if (this.currentStep_ != nextStepIndex && | |
156 !oldStep.classList.contains('hidden')) { | |
157 oldStep.addEventListener('webkitTransitionEnd', function f(e) { | |
158 oldStep.removeEventListener('webkitTransitionEnd', f); | |
159 oldStep.classList.add('hidden'); | |
160 }); | |
161 } else { | |
162 // First screen on OOBE launch. | |
163 newHeader.classList.remove('right'); | |
164 } | |
165 this.currentStep_ = nextStepIndex; | |
166 $('oobe').className = nextStepId; | |
167 }, | |
168 | |
169 /** | |
170 * Show screen of given screen id. | |
171 * @param {Object} screen Screen params dict, | |
172 * e.g. {id: screenId, data: data} | |
173 */ | |
174 showScreen: function(screen) { | |
175 var screenId = screen.id; | |
176 | |
177 // Show sign-in screen instead of account picker if pod row is empty. | |
178 if (screenId == SCREEN_ACCOUNT_PICKER && $('pod-row').pods.length == 0) { | |
179 Oobe.showSigninUI(); | |
180 return; | |
181 } | |
182 | |
183 var data = screen.data; | |
184 var index = this.getScreenIndex_(screenId); | |
185 if (index >= 0) | |
186 this.toggleStep_(index, data); | |
187 $('offline-message').update(); | |
188 }, | |
189 | |
190 /** | |
191 * Gets index of given screen id in screens_. | |
192 * @param {string} screenId Id of the screen to look up. | |
193 * @private | |
194 */ | |
195 getScreenIndex_: function(screenId) { | |
196 for (var i = 0; i < this.screens_.length; ++i) { | |
197 if (this.screens_[i] == screenId) | |
198 return i; | |
199 } | |
200 return -1; | |
201 }, | |
202 | |
203 /** | |
204 * Register an oobe screen. | |
205 * @param {Element} el Decorated screen element. | |
206 */ | |
207 registerScreen: function(el) { | |
208 var screenId = el.id; | |
209 this.screens_.push(screenId); | |
210 | |
211 var header = document.createElement('span'); | |
212 header.id = 'header-' + screenId; | |
213 header.className = 'header-section right'; | |
214 header.textContent = el.header ? el.header : ''; | |
215 $('header-sections').appendChild(header); | |
216 | |
217 var dot = document.createElement('div'); | |
218 dot.id = screenId + '-dot'; | |
219 dot.className = 'progdot'; | |
220 $('progress').appendChild(dot); | |
221 | |
222 this.appendButtons_(el.buttons); | |
223 }, | |
224 | |
225 /** | |
226 * Updates headers and buttons of the screens. | |
227 * Should be executed on language change. | |
228 */ | |
229 updateHeadersAndButtons_: function() { | |
230 $('button-strip').innerHTML = ''; | |
231 for (var i = 0, screenId; screenId = this.screens_[i]; ++i) { | |
232 var screen = $(screenId); | |
233 $('header-' + screenId).textContent = screen.header; | |
234 this.appendButtons_(screen.buttons); | |
235 } | |
236 }, | |
237 | |
238 /** | |
239 * Prepares screens to use in login display. | |
240 */ | |
241 prepareForLoginDisplay_ : function() { | |
242 for (var i = 0, screenId; screenId = this.screens_[i]; ++i) { | |
243 var screen = $(screenId); | |
244 screen.classList.add('faded'); | |
245 screen.classList.remove('right'); | |
246 screen.classList.remove('left'); | |
247 } | |
248 } | |
249 }; | 28 }; |
250 | 29 |
251 /** | 30 /** |
252 * Setups given "select" element using the list and adds callback. | 31 * Setups given "select" element using the list and adds callback. |
253 * @param {!Element} select Select object to be updated. | 32 * @param {!Element} select Select object to be updated. |
254 * @param {!Object} list List of the options to be added. | 33 * @param {!Object} list List of the options to be added. |
255 * @param {string} callback Callback name which should be send to Chrome or | 34 * @param {string} callback Callback name which should be send to Chrome or |
256 * an empty string if the event listener shouldn't be added. | 35 * an empty string if the event listener shouldn't be added. |
257 */ | 36 */ |
258 Oobe.setupSelect = function(select, list, callback) { | 37 Oobe.setupSelect = function(select, list, callback) { |
259 select.options.length = 0; | 38 select.options.length = 0; |
260 for (var i = 0; i < list.length; ++i) { | 39 for (var i = 0; i < list.length; ++i) { |
261 var item = list[i]; | 40 var item = list[i]; |
262 var option = | 41 var option = |
263 new Option(item.title, item.value, item.selected, item.selected); | 42 new Option(item.title, item.value, item.selected, item.selected); |
264 select.appendChild(option); | 43 select.appendChild(option); |
265 } | 44 } |
266 if (callback) { | 45 if (callback) { |
267 select.addEventListener('change', function(event) { | 46 select.addEventListener('change', function(event) { |
268 chrome.send(callback, [select.options[select.selectedIndex].value]); | 47 chrome.send(callback, [select.options[select.selectedIndex].value]); |
269 }); | 48 }); |
270 } | 49 } |
271 } | 50 } |
272 | 51 |
273 /** | 52 /** |
274 * Returns offset (top, left) of the element. | |
275 * @param {!Element} element HTML element | |
276 * @return {!Object} The offset (top, left). | |
277 */ | |
278 Oobe.getOffset = function(element) { | |
279 var x = 0; | |
280 var y = 0; | |
281 while(element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) { | |
282 x += element.offsetLeft - element.scrollLeft; | |
283 y += element.offsetTop - element.scrollTop; | |
284 element = element.offsetParent; | |
285 } | |
286 return { top: y, left: x }; | |
287 }; | |
288 | |
289 /** | |
290 * Initializes the OOBE flow. This will cause all C++ handlers to | 53 * Initializes the OOBE flow. This will cause all C++ handlers to |
291 * be invoked to do final setup. | 54 * be invoked to do final setup. |
292 */ | 55 */ |
293 Oobe.initialize = function() { | 56 Oobe.initialize = function() { |
294 oobe.NetworkScreen.register(); | 57 oobe.NetworkScreen.register(); |
295 oobe.EulaScreen.register(); | 58 oobe.EulaScreen.register(); |
296 oobe.UpdateScreen.register(); | 59 oobe.UpdateScreen.register(); |
297 oobe.EnrollmentScreen.register(); | 60 oobe.EnrollmentScreen.register(); |
298 oobe.OAuthEnrollmentScreen.register(); | 61 oobe.OAuthEnrollmentScreen.register(); |
299 login.AccountPickerScreen.register(); | 62 login.AccountPickerScreen.register(); |
300 if (localStrings.getString('authType') == 'webui') | 63 login.GaiaSigninScreen.register(); |
301 login.SigninScreen.register(); | |
302 else | |
303 login.GaiaSigninScreen.register(); | |
304 oobe.UserImageScreen.register(); | 64 oobe.UserImageScreen.register(); |
305 login.OfflineMessageScreen.register(); | 65 login.OfflineMessageScreen.register(); |
306 | 66 |
307 cr.ui.Bubble.decorate($('bubble')); | 67 cr.ui.Bubble.decorate($('bubble')); |
| 68 login.HeaderBar.decorate($('login-header-bar')); |
308 | 69 |
309 $('security-link').addEventListener('click', function(event) { | 70 $('security-link').addEventListener('click', function(event) { |
310 chrome.send('eulaOnTpmPopupOpened', []); | 71 chrome.send('eulaOnTpmPopupOpened', []); |
311 $('popup-overlay').hidden = false; | 72 $('popup-overlay').hidden = false; |
312 }); | 73 }); |
313 $('security-ok-button').addEventListener('click', function(event) { | 74 $('security-ok-button').addEventListener('click', function(event) { |
314 $('popup-overlay').hidden = true; | 75 $('popup-overlay').hidden = true; |
315 }); | 76 }); |
316 | 77 |
317 $('shutdown-button').addEventListener('click', function(e) { | |
318 chrome.send('shutdownSystem'); | |
319 }); | |
320 $('add-user-button').addEventListener('click', function(e) { | |
321 if (window.navigator.onLine) { | |
322 Oobe.showSigninUI(); | |
323 } else { | |
324 $('bubble').showTextForElement($('add-user-button'), | |
325 localStrings.getString('addUserOfflineMessage')); | |
326 } | |
327 }); | |
328 $('cancel-add-user-button').addEventListener('click', function(e) { | |
329 this.hidden = true; | |
330 $('add-user-button').hidden = false; | |
331 Oobe.showScreen({id: SCREEN_ACCOUNT_PICKER}); | |
332 }); | |
333 | |
334 chrome.send('screenStateInitialize', []); | 78 chrome.send('screenStateInitialize', []); |
335 }; | 79 }; |
336 | 80 |
337 /** | 81 /** |
338 * Handle accelerators. These are passed from native code instead of a JS | 82 * Handle accelerators. These are passed from native code instead of a JS |
339 * event handler in order to make sure that embedded iframes cannot swallow | 83 * event handler in order to make sure that embedded iframes cannot swallow |
340 * them. | 84 * them. |
341 * @param {string} name Accelerator name. | 85 * @param {string} name Accelerator name. |
342 */ | 86 */ |
343 Oobe.handleAccelerator = function(name) { | 87 Oobe.handleAccelerator = function(name) { |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
455 } else { | 199 } else { |
456 document.body.classList.add('login-display'); | 200 document.body.classList.add('login-display'); |
457 Oobe.getInstance().prepareForLoginDisplay_(); | 201 Oobe.getInstance().prepareForLoginDisplay_(); |
458 } | 202 } |
459 | 203 |
460 // Don't show header bar for OOBE. | 204 // Don't show header bar for OOBE. |
461 Oobe.getInstance().headerHidden = showOobe; | 205 Oobe.getInstance().headerHidden = showOobe; |
462 }; | 206 }; |
463 | 207 |
464 /** | 208 /** |
465 * Returns true if Oobe UI is shown. | |
466 */ | |
467 Oobe.isOobeUI = function() { | |
468 return !document.body.classList.contains('login-display'); | |
469 }; | |
470 | |
471 /** | |
472 * Shows signin UI. | 209 * Shows signin UI. |
473 * @param {string} opt_email An optional email for signin UI. | 210 * @param {string} opt_email An optional email for signin UI. |
474 */ | 211 */ |
475 Oobe.showSigninUI = function(opt_email) { | 212 Oobe.showSigninUI = function(opt_email) { |
476 $('add-user-button').hidden = true; | 213 DisplayManager.showSigninUI(opt_email); |
477 $('cancel-add-user-button').hidden = false; | |
478 $('add-user-header-bar-item').hidden = $('pod-row').pods.length == 0; | |
479 chrome.send('showAddUser', [opt_email]); | |
480 }; | 214 }; |
481 | 215 |
482 /** | 216 /** |
483 * Resets sign-in input fields. | 217 * Resets sign-in input fields. |
484 */ | 218 */ |
485 Oobe.resetSigninUI = function() { | 219 Oobe.resetSigninUI = function() { |
486 var currentScreenId = Oobe.getInstance().currentScreen.id; | 220 DisplayManager.resetSigninUI(); |
487 | |
488 if (localStrings.getString('authType') == 'webui') | |
489 $(SCREEN_SIGNIN).reset(currentScreenId == SCREEN_SIGNIN); | |
490 else | |
491 $(SCREEN_GAIA_SIGNIN).reset(currentScreenId == SCREEN_GAIA_SIGNIN); | |
492 | |
493 $('pod-row').reset(currentScreenId == SCREEN_ACCOUNT_PICKER); | |
494 }; | 221 }; |
495 | 222 |
496 /** | 223 /** |
497 * Shows sign-in error bubble. | 224 * Shows sign-in error bubble. |
498 * @param {number} loginAttempts Number of login attemps tried. | 225 * @param {number} loginAttempts Number of login attemps tried. |
499 * @param {string} message Error message to show. | 226 * @param {string} message Error message to show. |
500 * @param {string} link Text to use for help link. | 227 * @param {string} link Text to use for help link. |
501 * @param {number} helpId Help topic Id associated with help link. | 228 * @param {number} helpId Help topic Id associated with help link. |
502 */ | 229 */ |
503 Oobe.showSignInError = function(loginAttempts, message, link, helpId) { | 230 Oobe.showSignInError = function(loginAttempts, message, link, helpId) { |
504 var currentScreenId = Oobe.getInstance().currentScreen.id; | 231 DisplayManager.showSignInError(loginAttempts, message, link, helpId); |
505 var anchor = undefined; | |
506 var anchorPos = undefined; | |
507 if (currentScreenId == SCREEN_SIGNIN) { | |
508 anchor = $('email'); | |
509 | |
510 // Show email field so that bubble shows up at the right location. | |
511 $(SCREEN_SIGNIN).reset(true); | |
512 } else if (currentScreenId == SCREEN_GAIA_SIGNIN) { | |
513 // Use anchorPos since we won't be able to get the input fields of Gaia. | |
514 anchorPos = Oobe.getOffset(Oobe.getInstance().currentScreen); | |
515 | |
516 // Ideally, we should just use | |
517 // anchorPos = Oobe.getOffset($('signin-frame')); | |
518 // to get a good anchor point. However, this always gives (0,0) on | |
519 // the device. | |
520 // TODO(xiyuan): Figure out why the above fails and get rid of this. | |
521 anchorPos.left += 150; // (640 - 340) / 2 | |
522 | |
523 // TODO(xiyuan): Find a reliable way to align with Gaia UI. | |
524 anchorPos.left += 60; | |
525 anchorPos.top += 105; | |
526 } else if (currentScreenId == SCREEN_ACCOUNT_PICKER && | |
527 $('pod-row').activated) { | |
528 const MAX_LOGIN_ATTEMMPTS_IN_POD = 3; | |
529 if (loginAttempts > MAX_LOGIN_ATTEMMPTS_IN_POD) { | |
530 Oobe.showSigninUI($('pod-row').activated.user.emailAddress); | |
531 return; | |
532 } | |
533 | |
534 anchor = $('pod-row').activated.mainInput; | |
535 } | |
536 if (!anchor && !anchorPos) { | |
537 console.log('Warning: Failed to find anchor for error :' + | |
538 message); | |
539 return; | |
540 } | |
541 | |
542 var error = document.createElement('div'); | |
543 | |
544 var messageDiv = document.createElement('div'); | |
545 messageDiv.className = 'error-message'; | |
546 messageDiv.textContent = message; | |
547 error.appendChild(messageDiv); | |
548 | |
549 if (link) { | |
550 messageDiv.classList.add('error-message-padding'); | |
551 | |
552 var helpLink = document.createElement('a'); | |
553 helpLink.href = '#'; | |
554 helpLink.textContent = link; | |
555 helpLink.onclick = function(e) { | |
556 chrome.send('launchHelpApp', [helpId]); | |
557 } | |
558 error.appendChild(helpLink); | |
559 } | |
560 | |
561 if (anchor) | |
562 $('bubble').showContentForElement(anchor, error); | |
563 else if (anchorPos) | |
564 $('bubble').showContentAt(anchorPos.left, anchorPos.top, error); | |
565 }; | 232 }; |
566 | 233 |
567 /** | 234 /** |
568 * Clears error bubble. | 235 * Clears error bubble. |
569 */ | 236 */ |
570 Oobe.clearErrors = function() { | 237 Oobe.clearErrors = function() { |
571 $('bubble').hide(); | 238 DisplayManager.clearErrors(); |
572 }; | 239 }; |
573 | 240 |
574 /** | 241 /** |
575 * Handles login success notification. | 242 * Handles login success notification. |
576 */ | 243 */ |
577 Oobe.onLoginSuccess = function(username) { | 244 Oobe.onLoginSuccess = function(username) { |
578 if (Oobe.getInstance().currentScreen.id == SCREEN_ACCOUNT_PICKER) { | 245 if (Oobe.getInstance().currentScreen.id == SCREEN_ACCOUNT_PICKER) { |
579 // TODO(nkostylev): Enable animation back when session start jank | 246 // TODO(nkostylev): Enable animation back when session start jank |
580 // is reduced. See http://crosbug.com/11116 http://crosbug.com/18307 | 247 // is reduced. See http://crosbug.com/11116 http://crosbug.com/18307 |
581 // $('pod-row').startAuthenticatedAnimation(); | 248 // $('pod-row').startAuthenticatedAnimation(); |
582 } | 249 } |
583 }; | 250 }; |
584 | 251 |
585 /** | 252 /** |
586 * Sets text content for a div with |labelId|. | 253 * Sets text content for a div with |labelId|. |
587 * @param {string} labelId Id of the label div. | 254 * @param {string} labelId Id of the label div. |
588 * @param {string} labelText Text for the label. | 255 * @param {string} labelText Text for the label. |
589 */ | 256 */ |
590 Oobe.setLabelText = function(labelId, labelText) { | 257 Oobe.setLabelText = function(labelId, labelText) { |
591 $(labelId).textContent = labelText; | 258 DisplayManager.setLabelText(labelId, labelText); |
592 }; | 259 }; |
593 | 260 |
594 // Export | 261 // Export |
595 return { | 262 return { |
596 Oobe: Oobe | 263 Oobe: Oobe |
597 }; | 264 }; |
598 }); | 265 }); |
599 | 266 |
600 var Oobe = cr.ui.Oobe; | 267 var Oobe = cr.ui.Oobe; |
601 | 268 |
602 disableTextSelectAndDrag(); | 269 disableTextSelectAndDrag(); |
603 | 270 |
604 document.addEventListener('DOMContentLoaded', cr.ui.Oobe.initialize); | 271 document.addEventListener('DOMContentLoaded', cr.ui.Oobe.initialize); |
OLD | NEW |