OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 /** | |
6 * @fileoverview Login Screen | |
7 * This is the main code for the login screen. | |
8 */ | |
9 | |
10 /** | |
11 * Simple common assertion API | |
12 * @param {*} condition The condition to test. Note that this may be used to | |
13 * test whether a value is defined or not, and we don't want to force a | |
14 * cast to Boolean. | |
15 * @param {string=} opt_message A message to use in any error. | |
16 */ | |
17 function assert(condition, opt_message) { | |
18 if (condition) | |
19 return; | |
20 | |
21 var msg = 'Assertion failed'; | |
22 if (opt_message) | |
23 msg = msg + ': ' + opt_message; | |
24 throw new Error(msg); | |
25 } | |
26 | |
27 var LoginScreen = (function() { | |
28 'use strict'; | |
29 | |
30 function LoginScreen() { | |
31 } | |
32 | |
33 LoginScreen.prototype = { | |
34 /** | |
35 * The guest card object | |
36 * @type {Slider|undefined} | |
37 * private | |
38 */ | |
39 guestCard_: null, | |
40 | |
41 /** | |
42 * The Slider object to use for scrolling through users. | |
43 * @type {Slider|undefined} | |
44 */ | |
45 slider_: null, | |
46 | |
47 /** | |
48 * Template to use for creating new 'user-card' elements | |
49 * @type {!Element|undefined} | |
50 */ | |
51 userCardTemplate_: null, | |
52 | |
53 /** | |
54 * Indicates whether the shift key is down. | |
55 * @type {bool} | |
56 */ | |
57 isShiftPressed_: false, | |
58 | |
59 /** | |
60 * Indicates whether the alt key is down. | |
61 * @type {bool} | |
62 */ | |
63 isAltPressed_: false, | |
64 | |
65 /** | |
66 * Indicates whether the ctrl key is down. | |
67 * @type {bool} | |
68 */ | |
69 isCtrlPressed_: false, | |
70 | |
71 /** | |
72 * Holds all event handlers tied to users (and so subject to removal when | |
73 * the user list is refreshed) | |
74 * @type {!EventTracker} | |
75 */ | |
76 userEvents_: null, | |
77 | |
78 /** | |
79 * Removes all children of an element which have a given class in | |
80 * their classList. | |
81 * @param {!Element} element The parent element to examine. | |
82 * @param {string} className The class to look for. | |
83 */ | |
84 removeChildrenByClassName_: function (element, className) { | |
85 var child = element.firstElementChild; | |
86 while (child) { | |
87 var prev = child; | |
88 child = child.nextElementSibling; | |
89 if (prev.classList.contains(className)) | |
90 element.removeChild(prev); | |
91 } | |
92 }, | |
93 | |
94 /** | |
95 * Search an elements ancestor chain for the nearest element that is a | |
96 * member of the specified class. | |
97 * @param {!Element} element The element to start searching from. | |
98 * @param {string} className The name of the class to locate. | |
99 * @return {Element} The first ancestor of the specified class or null. | |
100 */ | |
101 getParentByClassName_: function(element, className) { | |
102 for (var e = element; e; e = e.parentElement) { | |
103 if (e.classList.contains(className)) | |
104 return e; | |
105 } | |
106 return null; | |
107 }, | |
108 | |
109 /** | |
110 * Create a new user element and attach it to the end of the slider. | |
111 * @param {!Element} parent The element where the user should be inserted. | |
112 * @param {!Object} user The user object to user box for. | |
113 */ | |
114 addUser: function(parent, user, index) { | |
115 // Make a deep copy of the template and clear its ID | |
116 var containerElement = this.userCardTemplate_.cloneNode(true); | |
117 this.userCardTemplate_.id = null; | |
118 | |
119 var userElement = containerElement.getElementsByClassName('user')[0]; | |
120 assert(userElement, 'Expected user-template to have a user child'); | |
121 assert(typeof(user.name) == 'string', | |
122 'Expected every user to have a name.'); | |
123 | |
124 userElement.setAttribute('display-name', user.name); | |
125 userElement.setAttribute('user-name', user.emailAddress); | |
126 userElement.setAttribute('user-index', index); | |
127 | |
128 // Find the span element (if any) and fill it in with the app name | |
129 var span = userElement.querySelector('span'); | |
130 if (span) | |
131 span.textContent = user.name; | |
132 | |
133 // Fill in the image | |
134 var userImg = userElement.getElementsByClassName('user-image')[0]; | |
135 if (userImg) { | |
136 userImg.style.backgroundImage = "url('" + user.imageUrl + "')"; | |
137 // We put a click handler just on the user image - so clicking on the | |
138 // margins between users doesn't do anything | |
139 var self = this; | |
140 this.userEvents_.add(userImg, 'click', function(e) { | |
141 self.userClick(e); | |
142 }, false); | |
143 } | |
144 var userLoginBox = | |
145 userElement.getElementsByClassName('user-login-box')[0]; | |
146 var guestLoginBox = | |
147 userElement.getElementsByClassName('guest-login-box')[0]; | |
148 var isGuest = !user.email_address; | |
149 if (!isGuest) { | |
150 containerElement.addEventListener(Slider.EventType.ACTIVATE, | |
151 this.userActivateCard); | |
152 containerElement.addEventListener(Slider.EventType.DEACTIVATE, | |
153 this.userDeactivateCard); | |
154 | |
155 var deleteButton = | |
156 userElement.getElementsByClassName('delete-button')[0]; | |
157 deleteButton.addEventListener('click', this.userDeleteCard); | |
158 | |
159 userLoginBox.addEventListener('keypress', function(e) { | |
160 userLoginKeyPress(e, userElement); | |
161 }); | |
162 } else { | |
163 this.guestCard_ = containerElement; | |
164 | |
165 containerElement.addEventListener(Slider.EventType.ACTIVATE, | |
166 this.guestActivateCard); | |
167 containerElement.addEventListener(Slider.EventType.DEACTIVATE, | |
168 this.guestDeactivateCard); | |
169 | |
170 userLoginBox.hidden = true; | |
171 | |
172 var loginButton = | |
173 guestLoginBox.getElementsByClassName('loginbutton')[0]; | |
174 loginButton.addEventListener('click', function(e) { | |
175 chrome.send('launchIncognito', []); | |
176 // Don't allow the click to trigger a link or anything | |
177 e.preventDefault(); | |
178 // Prevent the user-frame from receiving the event. | |
179 e.stopPropagation(); | |
180 }, true); | |
181 } | |
182 | |
183 // Insert at the end of the provided page | |
184 parent.appendChild(containerElement); | |
185 // Display the container element | |
186 containerElement.hidden = false; | |
187 }, | |
188 | |
189 /** | |
190 * Invoked when a user is clicked | |
191 * @param {Event} e The click event. | |
192 */ | |
193 userClick: function(e) { | |
194 var target = e.currentTarget; | |
195 var user = this.getParentByClassName_(target, 'user'); | |
196 assert(user, 'userClick should have been on a descendant of a user'); | |
197 | |
198 var userIndex = user.getAttribute('user-index'); | |
199 assert(userIndex, 'unexpected user without userIndex'); | |
200 | |
201 if (this.slider.currentCardIndex == null) { | |
202 this.slider.currentCardIndex = userIndex; | |
203 } else { | |
204 // get out of user selected mode. | |
205 this.slider.currentCardIndex = null; | |
206 } | |
207 | |
208 // Don't allow the click to trigger a link or anything | |
209 e.preventDefault(); | |
210 // Prevent the user-frame from receiving the event. | |
211 e.stopPropagation(); | |
212 }, | |
213 | |
214 /** | |
215 * Invoked when the selected user card changes | |
216 * @param {Event} e The selection changed event. | |
217 */ | |
218 userFrameSelectionChanged: function(e) { | |
219 var userCards = $('user-list').getElementsByClassName('user-card'); | |
220 for (var i = 0; i < userCards.length; i++) { | |
221 if (e.activeCardIndex == null || i == e.activeCardIndex) { | |
222 userCards[i].classList.remove('user-card-grayed'); | |
223 } else { | |
224 userCards[i].classList.add('user-card-grayed'); | |
225 } | |
226 } | |
227 }, | |
228 | |
229 /** | |
230 * Invoked when the guest card is activated. | |
231 * @param {Event} e The Activate Card event | |
232 */ | |
233 guestActivateCard: function(e) { | |
234 var card = e.target; | |
235 var user = card.getElementsByClassName('user')[0]; | |
236 | |
237 // Focus the card. | |
238 user.classList.add('focus'); | |
239 | |
240 assert(user, 'user element not found'); | |
241 | |
242 // Hide the guest box. | |
243 var guestBox = user.querySelector('span'); | |
244 guestBox.hidden = true; | |
245 | |
246 // This is a guest. | |
247 var guestLoginBox = user.getElementsByClassName('guest-login-box')[0]; | |
248 assert(guestLoginBox, 'guest login box not found'); | |
249 // Show the guest login box. | |
250 guestLoginBox.hidden = false; | |
251 }, | |
252 | |
253 /** | |
254 * Invoked when the guest card is deactivated. | |
255 * @param {Event} e The Deactivate Card event | |
256 */ | |
257 guestDeactivateCard: function(e) { | |
258 var card = e.target; | |
259 var user = card.getElementsByClassName('user')[0]; | |
260 assert(user, 'user element not found'); | |
261 // Show the guest box. | |
262 var guestBox = user.querySelector('span'); | |
263 guestBox.hidden = false; | |
264 // Unfocus the card. | |
265 user.classList.remove('focus'); | |
266 // Hide the guest login box | |
267 var guestLoginBox = user.getElementsByClassName('guest-login-box')[0]; | |
268 assert(guestLoginBox, 'guest login box not found'); | |
269 guestLoginBox.hidden = true; | |
270 }, | |
271 | |
272 /** | |
273 * Invoked when the user card delete button is clicked | |
274 * @param {Event} e The Click event. | |
275 */ | |
276 userDeleteCard: function(e) { | |
277 var deleteButton = e.target; | |
278 var userImageBorderBox = deleteButton.parentNode; | |
279 var userElement = userImageBorderBox.parentNode; | |
280 var userCard = userElement.parentNode; | |
281 $('user-list').removeChild(userCard); | |
282 | |
283 // Update the user indicies | |
284 var usersOnList = $('user-list').getElementsByClassName('user-card'); | |
285 for (var userIndex = 0; userIndex < usersOnList.length; userIndex++) { | |
286 var uElement = usersOnList[userIndex].getElementsByClassName('user')[0]; | |
287 uElement.setAttribute('user-index', userIndex); | |
288 } | |
289 this.updateSliderCards(); | |
290 | |
291 chrome.send('removeUser', [getUserName(userElement)]); | |
292 }, | |
293 | |
294 /** | |
295 * Invoked when the user card is activated. | |
296 * @param {Event} e The Activate Card event | |
297 */ | |
298 userActivateCard: function(e) { | |
299 var card = e.target; | |
300 var user = card.getElementsByClassName('user')[0]; | |
301 assert(user, 'user element not found'); | |
302 | |
303 // Focus the card. | |
304 user.classList.add('focus'); | |
305 | |
306 // Hide the email box. | |
307 var emailBox = user.querySelector('span'); | |
308 emailBox.hidden = true; | |
309 | |
310 // Show the login box | |
311 var userLoginBox = user.getElementsByClassName('user-login-box')[0]; | |
312 assert(userLoginBox, 'user login box not found'); | |
313 userLoginBox.hidden = false; | |
314 | |
315 // Clear and focus the password box. | |
316 var passwordBox = userLoginBox.getElementsByClassName('passwordbox')[0]; | |
317 assert(passwordBox, 'passwordBox element not found'); | |
318 passwordBox.value = ''; | |
319 passwordBox.focus(); | |
320 | |
321 // Show the delete button. | |
322 var deleteButton = user.getElementsByClassName('delete-button')[0]; | |
323 deleteButton.hidden = false; | |
324 }, | |
325 | |
326 /** | |
327 * Invoked when the user card is deactivated. | |
328 * @param {Event} e The Deactivate Card event | |
329 */ | |
330 userDeactivateCard: function(e) { | |
331 var card = e.target; | |
332 var user = card.getElementsByClassName('user')[0]; | |
333 assert(user, 'user element not found'); | |
334 | |
335 // Unfocus the card. | |
336 user.classList.remove('focus'); | |
337 | |
338 // Hide the user login box | |
339 var userLoginBox = user.getElementsByClassName('user-login-box')[0]; | |
340 assert(userLoginBox, 'user login box not found'); | |
341 userLoginBox.hidden = true; | |
342 | |
343 // Show the email box. | |
344 var emailBox = user.querySelector('span'); | |
345 emailBox.hidden = false; | |
346 | |
347 // Hide the delete button. | |
348 var deleteButton = user.getElementsByClassName('delete-button')[0]; | |
349 deleteButton.hidden = true; | |
350 }, | |
351 | |
352 /** | |
353 * Invoked whenever the users in user-list have changed so that | |
354 * the Slider knows about the new elements. | |
355 */ | |
356 updateSliderCards: function() { | |
357 var userCards = $('user-list').getElementsByClassName('user-card'); | |
358 var cardArray = []; | |
359 for (var i = 0; i < userCards.length; i++) | |
360 cardArray[i] = userCards[i]; | |
361 this.slider.setCards(cardArray, 0); | |
362 }, | |
363 | |
364 getUsersCallback: function(users) { | |
365 this.userEvents_.removeAll(); | |
366 // grab the add-user-card before we remove it from the list. | |
367 var addUserCard = $('add-user-card'); | |
368 // remove all user cards | |
369 this.removeChildrenByClassName_($('user-list'), 'user-card'); | |
370 | |
371 // Add the "Add User" card to the user list. | |
372 $('user-list').appendChild(addUserCard); | |
373 // Add the users to the user list. | |
374 for (var i = 0; i < users.length; i++) { | |
375 var user = users[i]; | |
376 this.addUser($('user-list'), user, i + 1); | |
377 } | |
378 | |
379 // Tell the slider about the pages | |
380 this.updateSliderCards(); | |
381 | |
382 var dummy = $('user-frame').offsetWidth; | |
383 $('user-frame').style.visibility = 'visible'; | |
384 }, | |
385 | |
386 /** | |
387 * Returns the 'display-name' attribute of a 'user' element. | |
388 * @param {!Element} element The 'user' element. | |
389 */ | |
390 getDisplayName: function(userElement) { | |
391 // TODO: verify type of user element | |
392 return userElement.getAttribute('display-name'); | |
393 }, | |
394 | |
395 /** | |
396 * Returns the 'user-name' attribute of a 'user' element. | |
397 * @param {!Element} element The 'user' element. | |
398 */ | |
399 getUserName: function(userElement) { | |
400 // TODO: verify type of user element | |
401 return userElement.getAttribute('user-name'); | |
402 }, | |
403 | |
404 /** | |
405 * Handles keyboard navigation of the login screen. | |
406 * @param {!Event} element The keypress event. | |
407 */ | |
408 userLoginKeyPress: function(e, userElement) { | |
409 if (e.keyCode == 13) { | |
410 e.preventDefault(); | |
411 assert(userElement.classList.contains('user'), | |
412 'The parent element is not a user element.'); | |
413 | |
414 var userImg = userElement.getElementsByClassName('user-image')[0]; | |
415 var userLoginBox = | |
416 userElement.getElementsByClassName('user-login-box')[0]; | |
417 var passwordBox = userLoginBox.getElementsByClassName('passwordbox')[0]; | |
418 | |
419 var userName = getUserName(userElement); | |
420 var password = passwordBox.value; | |
421 if (password.length > 0) { | |
422 chrome.send('authenticateUser', [userName, password]); | |
423 return false; | |
424 } | |
425 } | |
426 return true; | |
427 }, | |
428 | |
429 loginKeyDown: function(e) { | |
430 switch (e.keyCode) { | |
431 // shift key down | |
432 case 16: | |
433 this.isShiftPressed_ = true; | |
434 break; | |
435 // ctrl key down | |
436 case 17: | |
437 this.isCtrlPressed_ = true; | |
438 break; | |
439 // alt key down | |
440 case 18: | |
441 this.isAltPressed_ = true; | |
442 break; | |
443 } | |
444 var keystroke = String.fromCharCode(e.keyCode); | |
445 switch (keystroke) { | |
446 case 'U': | |
447 if (this.isAltPressed_) { | |
448 this.slider.currentCardIndex = 0; | |
449 // focus the email box. | |
450 var emailBox = | |
451 $('add-user-card').getElementsByClassName('emailbox')[0]; | |
452 assert(emailBox, 'Add User e-mail box not found'); | |
453 emailBox.focus(); | |
454 } | |
455 break; | |
456 case 'P': | |
457 if (this.isAltPressed_) { | |
458 if (this.slider.currentCard == null) { | |
459 this.slider.currentCardIndex = 0; | |
460 } | |
461 var passwordBox = | |
462 this.slider.currentCard.getElementsByClassName('passwordbox')[0]; | |
463 assert(passwordBox, 'password box not found'); | |
464 passwordBox.focus(); | |
465 } | |
466 break; | |
467 case 'B': | |
468 if (this.isAltPressed_) { | |
469 var guestElement = | |
470 this.guestCard_.getElementsByClassName('user')[0]; | |
471 var guestIndex = guestElement.getAttribute('user-index'); | |
472 assert(guestIndex, 'unexpected user without userIndex'); | |
473 this.slider.currentCardIndex = guestIndex; | |
474 setTimeout(function() { | |
475 // TODO(fsamuel): Make this call a common function. | |
476 chrome.send('launchIncognito', []); | |
477 }, 50); | |
478 } | |
479 break; | |
480 default: | |
481 switch (e.keyCode) { | |
482 // enter pressed. | |
483 case 13: | |
484 if (this.slider.currentCard == guestCard_) { | |
485 // TODO(fsamuel): Enter Incognito mode | |
486 } | |
487 break; | |
488 // escape pressed. | |
489 case 27: | |
490 // If we pressed the escape key then don't select any user. | |
491 this.slider.currentCardIndex = null; | |
492 break; | |
493 // left arrow pressed. | |
494 case 37: | |
495 if (this.slider.currentCardIndex == null) { | |
496 // TODO(fsamuel): This should be go to last active card + 1 | |
497 this.slider.currentCardIndex = this.slider.cardCount() - 1; | |
498 } else if (this.slider.currentCardIndex > 0) { | |
499 this.slider.currentCardIndex -= 1; | |
500 } | |
501 break; | |
502 // right arrow pressed. | |
503 case 39: | |
504 if (this.slider.currentCardIndex == null) { | |
505 // TODO(fsamuel): This should be go to last active card - 1 | |
506 this.slider.currentCardIndex = 0; | |
507 } else if (this.slider.currentCardIndex < | |
508 this.slider.cardCount() - 1) { | |
509 this.slider.currentCardIndex += 1; | |
510 } | |
511 break; | |
512 } // switch | |
513 } // switch | |
514 }, | |
515 | |
516 loginKeyUp: function(e) { | |
517 switch (e.keyCode) { | |
518 // shift key up. | |
519 case 16: | |
520 this.isShiftPressed_ = false; | |
521 break; | |
522 // ctrl key up. | |
523 case 17: | |
524 this.isCtrlPressed_ = false; | |
525 break; | |
526 // alt key up. | |
527 case 18: | |
528 this.isAltPressed_ = false; | |
529 break; | |
530 } | |
531 }, | |
532 | |
533 /** | |
534 * Creates the Add User card and inserts it into the slider. | |
535 * TODO(fsamuel): This is here only temporarily. We should eventually remove | |
536 * this card. | |
537 */ | |
538 initializeAddUserCard: function(e) { | |
539 var emailBox = $('add-user-card').getElementsByClassName('emailbox')[0]; | |
540 assert(emailBox, 'Add User e-mail box not found'); | |
541 var passwordBox = | |
542 $('add-user-card').getElementsByClassName('passwordbox')[0]; | |
543 assert(passwordBox, 'Add User password box not found'); | |
544 | |
545 var user = $('add-user-card').getElementsByClassName('user')[0]; | |
546 // Clicking the Add User icon hides the icon and shows | |
547 // the user name and password input boxes | |
548 var self = this; | |
549 $('add-user').addEventListener('click', function(e) { | |
550 self.userClick(e); | |
551 }); | |
552 $('add-user-card').addEventListener(Slider.EventType.ACTIVATE, | |
553 function(e) { | |
554 //$('add-user-spacer').style.display = 'block'; | |
555 // Focus the card. | |
556 user.classList.add('focus'); | |
557 | |
558 // Hide the Add User Icon, and show the input boxes. | |
559 $('add-user').parentNode.hidden = true; | |
560 $('add-user-box').hidden = false; | |
561 | |
562 // Focus the email box and clear the password box. | |
563 emailBox.focus(); | |
564 passwordBox.value = ''; | |
565 }, false); | |
566 | |
567 $('add-user-card').addEventListener(Slider.EventType.DEACTIVATE, | |
568 function(e) { | |
569 //$('add-user-spacer').style.display = 'none'; | |
570 // Unfocus the card. | |
571 user.classList.remove('focus'); | |
572 | |
573 $('add-user').parentNode.hidden = false; | |
574 $('add-user-box').hidden = true; | |
575 document.body.focus(); | |
576 }, false); | |
577 | |
578 // Prevent blurring of a textfield if focus is shifted to the body. | |
579 // This makes sure that the keyboard remains up until we hide the | |
580 // Add User box. | |
581 var focusedElement = null; | |
582 emailBox.addEventListener('focus', function(e) { | |
583 focusedElement = emailBox; | |
584 }); | |
585 passwordBox.addEventListener('focus', function(e) { | |
586 focusedElement = passwordBox; | |
587 }); | |
588 document.body.addEventListener('focus', function(e) { | |
589 if (focusedElement) { | |
590 focusedElement.focus(); | |
591 e.preventDefault(); | |
592 } | |
593 }); | |
594 | |
595 // Prevent the slider from receiving click events and canceling | |
596 // the Add User card. | |
597 $('add-user-box').addEventListener('click', function(e) { | |
598 e.preventDefault(); | |
599 e.stopPropagation(); | |
600 }, false); | |
601 | |
602 emailBox.addEventListener('keypress', function(e) { | |
603 e.stopPropagation(); | |
604 if (e.keyCode == 13) { | |
605 if (emailBox.value.length > 0) { | |
606 passwordBox.focus(); | |
607 } | |
608 } | |
609 }); | |
610 | |
611 passwordBox.addEventListener('keypress', function(e) { | |
612 if (e.keyCode == 13) { | |
613 chrome.send('authenticateUser', [emailBox.value, passwordBox.value]); | |
614 } | |
615 }, true); | |
616 }, | |
617 | |
618 initialize: function() { | |
619 this.userEvents_ = new EventTracker(); | |
620 // Request data on the users so we can fill them in. | |
621 // Note that this is kicked off asynchronously. 'getUsersCallback' will | |
622 // be invoked at some point after this function returns. | |
623 chrome.send('getUsers'); | |
624 | |
625 // Prevent touch events from triggering any sort of native scrolling | |
626 document.addEventListener('touchmove', function(e) { | |
627 e.preventDefault(); | |
628 }, true); | |
629 | |
630 this.userCardTemplate_ = $('user-template'); | |
631 this.userCardTemplate_.id = null; | |
632 $('user-list').removeChild(this.userCardTemplate_); | |
633 $('user-frame').addEventListener(Slider.EventType.SELECTION_CHANGED, | |
634 this.userFrameSelectionChanged); | |
635 | |
636 // Initialize the slider without any cards at the moment | |
637 this.slider = new Slider($('user-frame'), $('user-list'), []); | |
638 this.slider.initialize(); | |
639 | |
640 this.initializeAddUserCard(); | |
641 | |
642 // Bind on bubble phase | |
643 var self = this; | |
644 $('user-frame').addEventListener('click', function() { | |
645 self.slider.currentCardIndex = null; | |
646 }, false); | |
647 document.addEventListener('keydown', function(e) { | |
648 self.loginKeyDown(e); | |
649 }, true); | |
650 document.addEventListener('keyup', function(e) { | |
651 self.loginKeyUp(e); | |
652 }, true); | |
653 } | |
654 }; | |
655 | |
656 return LoginScreen; | |
657 })(); | |
658 | |
659 | |
660 var getUsersCallback = null; | |
661 | |
662 document.addEventListener('DOMContentLoaded', function() { | |
663 var loginScreenObj = new LoginScreen(); | |
664 getUsersCallback = function(users) { | |
665 // call with the appropriate this. | |
666 loginScreenObj.getUsersCallback(users); | |
667 } | |
668 loginScreenObj.initialize(); | |
669 }, false); | |
670 | |
671 // Disable text selection. | |
672 document.onselectstart = function(e) { | |
673 e.preventDefault(); | |
674 } | |
675 | |
676 // Disable dragging. | |
677 document.ondragstart = function(e) { | |
678 e.preventDefault(); | |
679 } | |
OLD | NEW |