OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 cr.define('options', function() { | 5 cr.define('options', function() { |
6 ///////////////////////////////////////////////////////////////////////////// | 6 ///////////////////////////////////////////////////////////////////////////// |
7 // OptionsPage class: | 7 // OptionsPage class: |
8 | 8 |
9 /** | 9 /** |
10 * Base class for options page. | 10 * Base class for options page. |
11 * @constructor | 11 * @constructor |
12 * @param {string} name Options page name. | 12 * @param {string} name Options page name. |
13 * @param {string} title Options page title, used for navigation bar. | 13 * @param {string} title Options page title, used for navigation bar. |
14 * @extends {EventTarget} | 14 * @extends {EventTarget} |
15 */ | 15 */ |
16 function OptionsPage(name, title, pageDivName) { | 16 function OptionsPage(name, title, pageDivName) { |
17 this.name = name; | 17 this.name = name; |
18 this.title = title; | 18 this.title = title; |
19 this.pageDivName = pageDivName; | 19 this.pageDivName = pageDivName; |
20 this.pageDiv = $(this.pageDivName); | 20 this.pageDiv = $(this.pageDivName); |
21 this.tab = null; | 21 this.tab = null; |
22 } | 22 } |
23 | 23 |
24 const SUBPAGE_SHEET_COUNT = 1; | |
25 | |
26 const HORIZONTAL_OFFSET = 155; | 24 const HORIZONTAL_OFFSET = 155; |
27 | 25 |
28 /** | 26 /** |
29 * This is the absolute difference maintained between standard and | 27 * This is the absolute difference maintained between standard and |
30 * fixed-width font sizes. Refer http://crbug.com/91922. | 28 * fixed-width font sizes. Refer http://crbug.com/91922. |
31 */ | 29 */ |
32 OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD = 3; | 30 OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD = 3; |
33 | 31 |
34 /** | 32 /** |
35 * Main level option pages. Maps lower-case page names to the respective page | 33 * Main level option pages. Maps lower-case page names to the respective page |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
68 /** | 66 /** |
69 * "Navigates" to a page, meaning that the page will be shown and the | 67 * "Navigates" to a page, meaning that the page will be shown and the |
70 * appropriate entry is placed in the history. | 68 * appropriate entry is placed in the history. |
71 * @param {string} pageName Page name. | 69 * @param {string} pageName Page name. |
72 */ | 70 */ |
73 OptionsPage.navigateToPage = function(pageName) { | 71 OptionsPage.navigateToPage = function(pageName) { |
74 this.showPageByName(pageName, true); | 72 this.showPageByName(pageName, true); |
75 }; | 73 }; |
76 | 74 |
77 /** | 75 /** |
78 * Shows a registered page. This handles both top-level pages and sub-pages. | 76 * Shows a registered page. This handles both top-level and overlay pages. |
79 * @param {string} pageName Page name. | 77 * @param {string} pageName Page name. |
80 * @param {boolean} updateHistory True if we should update the history after | 78 * @param {boolean} updateHistory True if we should update the history after |
81 * showing the page. | 79 * showing the page. |
82 * @private | 80 * @private |
83 */ | 81 */ |
84 OptionsPage.showPageByName = function(pageName, updateHistory) { | 82 OptionsPage.showPageByName = function(pageName, updateHistory) { |
85 // Find the currently visible root-level page. | 83 // Find the currently visible root-level page. |
86 var rootPage = null; | 84 var rootPage = null; |
87 for (var name in this.registeredPages) { | 85 for (var name in this.registeredPages) { |
88 var page = this.registeredPages[name]; | 86 var page = this.registeredPages[name]; |
(...skipping 13 matching lines...) Expand all Loading... |
102 return; | 100 return; |
103 } else { | 101 } else { |
104 targetPage = this.getDefaultPage(); | 102 targetPage = this.getDefaultPage(); |
105 } | 103 } |
106 } | 104 } |
107 | 105 |
108 pageName = targetPage.name.toLowerCase(); | 106 pageName = targetPage.name.toLowerCase(); |
109 var targetPageWasVisible = targetPage.visible; | 107 var targetPageWasVisible = targetPage.visible; |
110 | 108 |
111 // Determine if the root page is 'sticky', meaning that it | 109 // Determine if the root page is 'sticky', meaning that it |
112 // shouldn't change when showing a sub-page. This can happen for special | 110 // shouldn't change when showing an overlay. This can happen for special |
113 // pages like Search. | 111 // pages like Search. |
114 var isRootPageLocked = | 112 var isRootPageLocked = |
115 rootPage && rootPage.sticky && targetPage.parentPage; | 113 rootPage && rootPage.sticky && targetPage.parentPage; |
116 | 114 |
117 // Notify pages if they will be hidden. | 115 // Notify pages if they will be hidden. |
118 for (var name in this.registeredPages) { | 116 for (var name in this.registeredPages) { |
119 var page = this.registeredPages[name]; | 117 var page = this.registeredPages[name]; |
120 if (!page.parentPage && isRootPageLocked) | 118 if (!page.parentPage && isRootPageLocked) |
121 continue; | 119 continue; |
122 if (page.willHidePage && name != pageName && | 120 if (page.willHidePage && name != pageName && |
123 !page.isAncestorOfPage(targetPage)) { | 121 !page.isAncestorOfPage(targetPage)) { |
124 page.willHidePage(); | 122 page.willHidePage(); |
125 } | 123 } |
126 } | 124 } |
127 | 125 |
128 // Update visibilities to show only the hierarchy of the target page. | 126 // Update visibilities to show only the hierarchy of the target page. |
129 for (var name in this.registeredPages) { | 127 for (var name in this.registeredPages) { |
130 var page = this.registeredPages[name]; | 128 var page = this.registeredPages[name]; |
131 if (!page.parentPage && isRootPageLocked) | 129 if (!page.parentPage && isRootPageLocked) |
132 continue; | 130 continue; |
133 page.visible = name == pageName || | 131 page.visible = name == pageName || page.isAncestorOfPage(targetPage); |
134 (!document.documentElement.classList.contains('hide-menu') && | |
135 page.isAncestorOfPage(targetPage)); | |
136 } | 132 } |
137 | 133 |
138 // Update the history and current location. | 134 // Update the history and current location. |
139 if (updateHistory) | 135 if (updateHistory) |
140 this.updateHistoryState_(); | 136 this.updateHistoryState_(); |
141 | 137 |
142 // Update tab title. | 138 // Update tab title. |
143 this.setTitle_(targetPage.title); | 139 this.setTitle_(targetPage.title); |
144 | 140 |
145 // Notify pages if they were shown. | 141 // Notify pages if they were shown. |
146 for (var name in this.registeredPages) { | 142 for (var name in this.registeredPages) { |
147 var page = this.registeredPages[name]; | 143 var page = this.registeredPages[name]; |
148 if (!page.parentPage && isRootPageLocked) | 144 if (!page.parentPage && isRootPageLocked) |
149 continue; | 145 continue; |
150 if (!targetPageWasVisible && page.didShowPage && | 146 if (!targetPageWasVisible && page.didShowPage && |
151 (name == pageName || page.isAncestorOfPage(targetPage))) { | 147 (name == pageName || page.isAncestorOfPage(targetPage))) { |
152 page.didShowPage(); | 148 page.didShowPage(); |
153 } | 149 } |
154 } | 150 } |
155 }; | 151 }; |
156 | 152 |
157 /** | 153 /** |
158 * Updates the parts of the UI necessary for correctly hiding or displaying | |
159 * subpages. | |
160 * @private | |
161 */ | |
162 OptionsPage.updateDisplayForShowOrHideSubpage_ = function() { | |
163 OptionsPage.updateSubpageBackdrop_(); | |
164 OptionsPage.updateAriaHiddenForPages_(); | |
165 OptionsPage.updateScrollPosition_(); | |
166 }; | |
167 | |
168 /** | |
169 * Sets the aria-hidden attribute for pages which have been 'overlapped' by a | |
170 * sub-page, and removes aria-hidden from the topmost page or subpage. | |
171 * @private | |
172 */ | |
173 OptionsPage.updateAriaHiddenForPages_ = function() { | |
174 var visiblePages = OptionsPage.getVisiblePages_(); | |
175 | |
176 // |visiblePages| is empty when switching top-level pages. | |
177 if (!visiblePages.length) | |
178 return; | |
179 | |
180 var topmostPage = visiblePages.pop(); | |
181 | |
182 for (var i = 0; i < visiblePages.length; ++i) { | |
183 var page = visiblePages[i]; | |
184 var nestingLevel = page.nestingLevel; | |
185 var container = nestingLevel > 0 ? | |
186 $('subpage-sheet-container-' + nestingLevel) : $('page-container'); | |
187 container.setAttribute('aria-hidden', true); | |
188 } | |
189 | |
190 var topmostPageContainer = topmostPage.nestingLevel > 0 ? | |
191 $('subpage-sheet-container-' + topmostPage.nestingLevel) : | |
192 $('page-container'); | |
193 topmostPageContainer.removeAttribute('aria-hidden'); | |
194 }; | |
195 | |
196 /** | |
197 * Sets the title of the page. This is accomplished by calling into the | 154 * Sets the title of the page. This is accomplished by calling into the |
198 * parent page API. | 155 * parent page API. |
199 * @param {String} title The title string. | 156 * @param {String} title The title string. |
200 * @private | 157 * @private |
201 */ | 158 */ |
202 OptionsPage.setTitle_ = function(title) { | 159 OptionsPage.setTitle_ = function(title) { |
203 uber.invokeMethodOnParent('setTitle', {title: title}); | 160 uber.invokeMethodOnParent('setTitle', {title: title}); |
204 }; | 161 }; |
205 | 162 |
206 /** | 163 /** |
207 * Updates the visibility and stacking order of the subpage backdrop | 164 * Scrolls the page to the correct position (the top when opening an overlay, |
208 * according to which subpage is topmost and visible. | 165 * or the old scroll position a previously hidden overlay becomes visible). |
209 * @private | |
210 */ | |
211 OptionsPage.updateSubpageBackdrop_ = function() { | |
212 var topmostPage = OptionsPage.getTopmostVisibleNonOverlayPage_(); | |
213 var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0; | |
214 | |
215 var subpageBackdrop = $('subpage-backdrop'); | |
216 if (nestingLevel > 0) { | |
217 var container = $('subpage-sheet-container-' + nestingLevel); | |
218 subpageBackdrop.style.zIndex = | |
219 parseInt(window.getComputedStyle(container).zIndex) - 1; | |
220 subpageBackdrop.hidden = false; | |
221 } else { | |
222 subpageBackdrop.hidden = true; | |
223 } | |
224 }; | |
225 | |
226 /** | |
227 * Scrolls the page to the correct position (the top when opening a subpage, | |
228 * or the old scroll position a previously hidden subpage becomes visible). | |
229 * @private | 166 * @private |
230 */ | 167 */ |
231 OptionsPage.updateScrollPosition_ = function() { | 168 OptionsPage.updateScrollPosition_ = function() { |
232 var topmostPage = OptionsPage.getTopmostVisibleNonOverlayPage_(); | 169 var container = $('page-container'); |
233 var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0; | |
234 | |
235 var container = (nestingLevel > 0) ? | |
236 $('subpage-sheet-container-' + nestingLevel) : $('page-container'); | |
237 | |
238 var scrollTop = container.oldScrollTop || 0; | 170 var scrollTop = container.oldScrollTop || 0; |
239 container.oldScrollTop = undefined; | 171 container.oldScrollTop = undefined; |
240 window.scroll(document.body.scrollLeft, scrollTop); | 172 window.scroll(document.body.scrollLeft, scrollTop); |
241 }; | 173 }; |
242 | 174 |
243 /** | 175 /** |
244 * Pushes the current page onto the history stack, overriding the last page | 176 * Pushes the current page onto the history stack, overriding the last page |
245 * if it is the generic chrome://settings/. | 177 * if it is the generic chrome://settings/. |
246 * @private | 178 * @private |
247 */ | 179 */ |
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
379 /** | 311 /** |
380 * Returns the topmost visible page, or null if no page is visible. | 312 * Returns the topmost visible page, or null if no page is visible. |
381 * @return {OptionPage} The topmost visible page. | 313 * @return {OptionPage} The topmost visible page. |
382 */ | 314 */ |
383 OptionsPage.getTopmostVisiblePage = function() { | 315 OptionsPage.getTopmostVisiblePage = function() { |
384 // Check overlays first since they're top-most if visible. | 316 // Check overlays first since they're top-most if visible. |
385 return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_(); | 317 return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_(); |
386 }; | 318 }; |
387 | 319 |
388 /** | 320 /** |
389 * Closes the topmost open subpage, if any. | |
390 * @private | |
391 */ | |
392 OptionsPage.closeTopSubPage_ = function() { | |
393 var topPage = this.getTopmostVisiblePage(); | |
394 if (topPage && !topPage.isOverlay && topPage.parentPage) { | |
395 if (topPage.willHidePage) | |
396 topPage.willHidePage(); | |
397 topPage.visible = false; | |
398 } | |
399 | |
400 this.updateHistoryState_(); | |
401 }; | |
402 | |
403 /** | |
404 * Closes all subpages below the given level. | |
405 * @param {number} level The nesting level to close below. | |
406 */ | |
407 OptionsPage.closeSubPagesToLevel = function(level) { | |
408 var topPage = this.getTopmostVisiblePage(); | |
409 while (topPage && topPage.nestingLevel > level) { | |
410 if (topPage.willHidePage) | |
411 topPage.willHidePage(); | |
412 topPage.visible = false; | |
413 topPage = topPage.parentPage; | |
414 } | |
415 | |
416 this.updateHistoryState_(); | |
417 }; | |
418 | |
419 /** | |
420 * Updates managed banner visibility state based on the topmost page. | 321 * Updates managed banner visibility state based on the topmost page. |
421 */ | 322 */ |
422 OptionsPage.updateManagedBannerVisibility = function() { | 323 OptionsPage.updateManagedBannerVisibility = function() { |
423 var topPage = this.getTopmostVisiblePage(); | 324 var topPage = this.getTopmostVisiblePage(); |
424 if (topPage) | 325 if (topPage) |
425 topPage.updateManagedBannerVisibility(); | 326 topPage.updateManagedBannerVisibility(); |
426 }; | 327 }; |
427 | 328 |
428 /** | 329 /** |
429 * Shows the tab contents for the given navigation tab. | 330 * Shows the tab contents for the given navigation tab. |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
475 */ | 376 */ |
476 OptionsPage.findSectionForNode_ = function(node) { | 377 OptionsPage.findSectionForNode_ = function(node) { |
477 while (node = node.parentNode) { | 378 while (node = node.parentNode) { |
478 if (node.nodeName == 'SECTION') | 379 if (node.nodeName == 'SECTION') |
479 return node; | 380 return node; |
480 } | 381 } |
481 return null; | 382 return null; |
482 }; | 383 }; |
483 | 384 |
484 /** | 385 /** |
485 * Registers a new Sub-page. | |
486 * @param {OptionsPage} subPage Sub-page to register. | |
487 * @param {OptionsPage} parentPage Associated parent page for this page. | |
488 * @param {Array} associatedControls Array of control elements that lead to | |
489 * this sub-page. The first item is typically a button in a root-level | |
490 * page. There may be additional buttons for nested sub-pages. | |
491 */ | |
492 OptionsPage.registerSubPage = function(subPage, | |
493 parentPage, | |
494 associatedControls) { | |
495 this.registeredPages[subPage.name.toLowerCase()] = subPage; | |
496 subPage.parentPage = parentPage; | |
497 if (associatedControls) { | |
498 subPage.associatedControls = associatedControls; | |
499 if (associatedControls.length) { | |
500 subPage.associatedSection = | |
501 this.findSectionForNode_(associatedControls[0]); | |
502 } | |
503 } | |
504 subPage.tab = undefined; | |
505 subPage.initializePage(); | |
506 }; | |
507 | |
508 /** | |
509 * Registers a new Overlay page. | 386 * Registers a new Overlay page. |
510 * @param {OptionsPage} overlay Overlay to register. | 387 * @param {OptionsPage} overlay Overlay to register. |
511 * @param {OptionsPage} parentPage Associated parent page for this overlay. | 388 * @param {OptionsPage} parentPage Associated parent page for this overlay. |
512 * @param {Array} associatedControls Array of control elements associated with | 389 * @param {Array} associatedControls Array of control elements associated with |
513 * this page. | 390 * this page. |
514 */ | 391 */ |
515 OptionsPage.registerOverlay = function(overlay, | 392 OptionsPage.registerOverlay = function(overlay, |
516 parentPage, | 393 parentPage, |
517 associatedControls) { | 394 associatedControls) { |
518 this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay; | 395 this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay; |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
577 * Callback for window.onbeforeunload. Used to notify overlays that they will | 454 * Callback for window.onbeforeunload. Used to notify overlays that they will |
578 * be closed. | 455 * be closed. |
579 */ | 456 */ |
580 OptionsPage.willClose = function() { | 457 OptionsPage.willClose = function() { |
581 var overlay = this.getVisibleOverlay_(); | 458 var overlay = this.getVisibleOverlay_(); |
582 if (overlay && overlay.didClosePage) | 459 if (overlay && overlay.didClosePage) |
583 overlay.didClosePage(); | 460 overlay.didClosePage(); |
584 }; | 461 }; |
585 | 462 |
586 /** | 463 /** |
587 * Freezes/unfreezes the scroll position of given level's page container. | 464 * Freezes/unfreezes the scroll position of the root page container. |
588 * @param {boolean} freeze Whether the page should be frozen. | 465 * @param {boolean} freeze Whether the page should be frozen. |
589 * @param {number} level The level to freeze/unfreeze. | |
590 * @private | 466 * @private |
591 */ | 467 */ |
592 OptionsPage.setPageFrozenAtLevel_ = function(freeze, level) { | 468 OptionsPage.setRootPageFrozen_ = function(freeze) { |
593 var container = level == 0 ? $('page-container') : | 469 var container = $('page-container'); |
594 $('subpage-sheet-container-' + level); | |
595 | |
596 if (container.classList.contains('frozen') == freeze) | 470 if (container.classList.contains('frozen') == freeze) |
597 return; | 471 return; |
598 | 472 |
599 if (freeze) { | 473 if (freeze) { |
600 // Lock the width, since auto width computation may change. | 474 // Lock the width, since auto width computation may change. |
601 container.style.width = window.getComputedStyle(container).width; | 475 container.style.width = window.getComputedStyle(container).width; |
602 container.oldScrollTop = document.body.scrollTop; | 476 container.oldScrollTop = document.body.scrollTop; |
603 container.classList.add('frozen'); | 477 container.classList.add('frozen'); |
604 var verticalPosition = | 478 var verticalPosition = |
605 container.getBoundingClientRect().top - container.oldScrollTop; | 479 container.getBoundingClientRect().top - container.oldScrollTop; |
606 container.style.top = verticalPosition + 'px'; | 480 container.style.top = verticalPosition + 'px'; |
607 this.updateFrozenElementHorizontalPosition_(container); | 481 this.updateFrozenElementHorizontalPosition_(container); |
608 } else { | 482 } else { |
609 container.classList.remove('frozen'); | 483 container.classList.remove('frozen'); |
610 container.style.top = ''; | 484 container.style.top = ''; |
611 container.style.left = ''; | 485 container.style.left = ''; |
612 container.style.right = ''; | 486 container.style.right = ''; |
613 container.style.width = ''; | 487 container.style.width = ''; |
614 } | 488 } |
615 }; | 489 }; |
616 | 490 |
617 /** | 491 /** |
618 * Freezes/unfreezes the scroll position of visible pages based on the current | 492 * Freezes/unfreezes the scroll position of the root page based on the current |
619 * page stack. | 493 * page stack. |
620 */ | 494 */ |
621 OptionsPage.updatePageFreezeStates = function() { | 495 OptionsPage.updateRootPageFreezeState = function() { |
622 var topPage = OptionsPage.getTopmostVisiblePage(); | 496 var topPage = OptionsPage.getTopmostVisiblePage(); |
623 if (!topPage) | 497 if (topPage) |
624 return; | 498 this.setRootPageFrozen_(topPage.isOverlay); |
625 var nestingLevel = topPage.isOverlay ? 100 : topPage.nestingLevel; | |
626 for (var i = 0; i <= SUBPAGE_SHEET_COUNT; i++) { | |
627 this.setPageFrozenAtLevel_(i < nestingLevel, i); | |
628 } | |
629 }; | 499 }; |
630 | 500 |
631 /** | 501 /** |
632 * Initializes the complete options page. This will cause all C++ handlers to | 502 * Initializes the complete options page. This will cause all C++ handlers to |
633 * be invoked to do final setup. | 503 * be invoked to do final setup. |
634 */ | 504 */ |
635 OptionsPage.initialize = function() { | 505 OptionsPage.initialize = function() { |
636 chrome.send('coreOptionsInitialize'); | 506 chrome.send('coreOptionsInitialize'); |
637 this.initialized_ = true; | 507 this.initialized_ = true; |
638 uber.onContentFrameLoaded(); | 508 uber.onContentFrameLoaded(); |
639 | 509 |
640 this.fixedHeaders_ = document.querySelectorAll('header'); | 510 this.fixedHeaders_ = document.querySelectorAll('header'); |
641 | 511 |
642 document.addEventListener('scroll', this.handleScroll_.bind(this)); | 512 document.addEventListener('scroll', this.handleScroll_.bind(this)); |
643 window.addEventListener('resize', this.handleResize_.bind(this)); | |
644 | 513 |
645 if (!document.documentElement.classList.contains('hide-menu')) { | 514 // Trigger the scroll handler manually to set the initial state. |
646 // Close subpages if the user clicks on the html body. Listen in the | |
647 // capturing phase so that we can stop the click from doing anything. | |
648 document.body.addEventListener('click', | |
649 this.bodyMouseEventHandler_.bind(this), | |
650 true); | |
651 // We also need to cancel mousedowns on non-subpage content. | |
652 document.body.addEventListener('mousedown', | |
653 this.bodyMouseEventHandler_.bind(this), | |
654 true); | |
655 | |
656 var self = this; | |
657 // Hook up the close buttons. | |
658 subpageCloseButtons = document.querySelectorAll('.close-subpage'); | |
659 for (var i = 0; i < subpageCloseButtons.length; i++) { | |
660 subpageCloseButtons[i].onclick = function() { | |
661 self.closeTopSubPage_(); | |
662 }; | |
663 } | |
664 | |
665 // Install handler for key presses. | |
666 document.addEventListener('keydown', | |
667 this.keyDownEventHandler_.bind(this), | |
668 true); | |
669 | |
670 document.addEventListener('focus', this.manageFocusChange_.bind(this), | |
671 true); | |
672 } | |
673 | |
674 // Trigger the resize and scroll handlers manually to set the initial state. | |
675 this.handleResize_(null); | |
676 this.handleScroll_(); | 515 this.handleScroll_(); |
677 | 516 |
678 // Shake the dialog if the user clicks outside the dialog bounds. | 517 // Shake the dialog if the user clicks outside the dialog bounds. |
679 var containers = [$('overlay-container-1'), $('overlay-container-2')]; | 518 var containers = [$('overlay-container-1'), $('overlay-container-2')]; |
680 for (var i = 0; i < containers.length; i++) { | 519 for (var i = 0; i < containers.length; i++) { |
681 var overlay = containers[i]; | 520 var overlay = containers[i]; |
682 cr.ui.overlay.setupOverlay(overlay); | 521 cr.ui.overlay.setupOverlay(overlay); |
683 overlay.addEventListener('closeOverlay', | 522 overlay.addEventListener('closeOverlay', |
684 OptionsPage.closeOverlay.bind(OptionsPage)); | 523 OptionsPage.closeOverlay.bind(OptionsPage)); |
685 } | 524 } |
686 }; | 525 }; |
687 | 526 |
688 /** | 527 /** |
689 * Does a bounds check for the element on the given x, y client coordinates. | 528 * Does a bounds check for the element on the given x, y client coordinates. |
690 * @param {Element} e The DOM element. | 529 * @param {Element} e The DOM element. |
691 * @param {number} x The client X to check. | 530 * @param {number} x The client X to check. |
692 * @param {number} y The client Y to check. | 531 * @param {number} y The client Y to check. |
693 * @return {boolean} True if the point falls within the element's bounds. | 532 * @return {boolean} True if the point falls within the element's bounds. |
694 * @private | 533 * @private |
695 */ | 534 */ |
696 OptionsPage.elementContainsPoint_ = function(e, x, y) { | 535 OptionsPage.elementContainsPoint_ = function(e, x, y) { |
697 var clientRect = e.getBoundingClientRect(); | 536 var clientRect = e.getBoundingClientRect(); |
698 return x >= clientRect.left && x <= clientRect.right && | 537 return x >= clientRect.left && x <= clientRect.right && |
699 y >= clientRect.top && y <= clientRect.bottom; | 538 y >= clientRect.top && y <= clientRect.bottom; |
700 }; | 539 }; |
701 | 540 |
702 /** | 541 /** |
703 * Called when focus changes; ensures that focus doesn't move outside | |
704 * the topmost subpage/overlay. | |
705 * @param {Event} e The focus change event. | |
706 * @private | |
707 */ | |
708 OptionsPage.manageFocusChange_ = function(e) { | |
709 var focusableItemsRoot; | |
710 var topPage = this.getTopmostVisiblePage(); | |
711 if (!topPage) | |
712 return; | |
713 | |
714 if (topPage.isOverlay) { | |
715 // This case is handled in overlay.js. | |
716 return; | |
717 } else { | |
718 // If a subpage is visible, use its parent as the tab loop constraint. | |
719 // (The parent is used because it contains the close button.) | |
720 if (topPage.nestingLevel > 0) | |
721 focusableItemsRoot = topPage.pageDiv.parentNode; | |
722 } | |
723 | |
724 if (focusableItemsRoot && !focusableItemsRoot.contains(e.target)) | |
725 topPage.focusFirstElement(); | |
726 }; | |
727 | |
728 /** | |
729 * Called when the page is scrolled; moves elements that are position:fixed | 542 * Called when the page is scrolled; moves elements that are position:fixed |
730 * but should only behave as if they are fixed for vertical scrolling. | 543 * but should only behave as if they are fixed for vertical scrolling. |
731 * @private | 544 * @private |
732 */ | 545 */ |
733 OptionsPage.handleScroll_ = function() { | 546 OptionsPage.handleScroll_ = function() { |
734 this.updateAllFrozenElementPositions_(); | 547 this.updateAllFrozenElementPositions_(); |
735 this.updateAllHeaderElementPositions_(); | 548 this.updateAllHeaderElementPositions_(); |
736 }; | 549 }; |
737 | 550 |
738 /** | 551 /** |
(...skipping 24 matching lines...) Expand all Loading... |
763 * @param {HTMLElement} e The frozen element to update. | 576 * @param {HTMLElement} e The frozen element to update. |
764 * @private | 577 * @private |
765 */ | 578 */ |
766 OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) { | 579 OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) { |
767 if (isRTL()) | 580 if (isRTL()) |
768 e.style.right = HORIZONTAL_OFFSET + 'px'; | 581 e.style.right = HORIZONTAL_OFFSET + 'px'; |
769 else | 582 else |
770 e.style.left = HORIZONTAL_OFFSET - document.body.scrollLeft + 'px'; | 583 e.style.left = HORIZONTAL_OFFSET - document.body.scrollLeft + 'px'; |
771 }; | 584 }; |
772 | 585 |
773 /** | |
774 * Called when the page is resized; adjusts the size of elements that depend | |
775 * on the veiwport. | |
776 * @param {Event} e The resize event. | |
777 * @private | |
778 */ | |
779 OptionsPage.handleResize_ = function(e) { | |
780 // Set an explicit height equal to the viewport on all the subpage | |
781 // containers shorter than the viewport. This is used instead of | |
782 // min-height: 100% so that there is an explicit height for the subpages' | |
783 // min-height: 100%. | |
784 var viewportHeight = document.documentElement.clientHeight; | |
785 var subpageContainers = | |
786 document.querySelectorAll('.subpage-sheet-container'); | |
787 for (var i = 0; i < subpageContainers.length; i++) { | |
788 if (subpageContainers[i].scrollHeight > viewportHeight) | |
789 subpageContainers[i].style.removeProperty('height'); | |
790 else | |
791 subpageContainers[i].style.height = viewportHeight + 'px'; | |
792 } | |
793 }; | |
794 | |
795 /** | |
796 * A function to handle mouse events (mousedown or click) on the html body by | |
797 * closing subpages and/or stopping event propagation. | |
798 * @return {Event} a mousedown or click event. | |
799 * @private | |
800 */ | |
801 OptionsPage.bodyMouseEventHandler_ = function(event) { | |
802 // Do nothing if a subpage isn't showing. | |
803 var topPage = this.getTopmostVisiblePage(); | |
804 if (!topPage || topPage.isOverlay || !topPage.parentPage) | |
805 return; | |
806 | |
807 // Don't close subpages if a user is clicking in a select element. | |
808 // This is necessary because WebKit sends click events with strange | |
809 // coordinates when a user selects a new entry in a select element. | |
810 // See: http://crbug.com/87199 | |
811 if (event.srcElement.nodeName == 'SELECT') | |
812 return; | |
813 | |
814 // Do nothing if the client coordinates are not within the source element. | |
815 // This occurs if the user toggles a checkbox by pressing spacebar. | |
816 // This is a workaround to prevent keyboard events from closing the window. | |
817 // See: crosbug.com/15678 | |
818 if (event.clientX == -document.body.scrollLeft && | |
819 event.clientY == -document.body.scrollTop) { | |
820 return; | |
821 } | |
822 | |
823 // Figure out which page the click happened in. | |
824 for (var level = topPage.nestingLevel; level >= 0; level--) { | |
825 var clickIsWithinLevel = level == 0 ? true : | |
826 OptionsPage.elementContainsPoint_( | |
827 $('subpage-sheet-' + level), event.clientX, event.clientY); | |
828 | |
829 if (!clickIsWithinLevel) | |
830 continue; | |
831 | |
832 // Event was within the topmost page; do nothing. | |
833 if (topPage.nestingLevel == level) | |
834 return; | |
835 | |
836 // Block propgation of both clicks and mousedowns, but only close subpages | |
837 // on click. | |
838 if (event.type == 'click') | |
839 this.closeSubPagesToLevel(level); | |
840 event.stopPropagation(); | |
841 event.preventDefault(); | |
842 return; | |
843 } | |
844 }; | |
845 | |
846 /** | |
847 * A function to handle key press events. | |
848 * @return {Event} a keydown event. | |
849 * @private | |
850 */ | |
851 OptionsPage.keyDownEventHandler_ = function(event) { | |
852 if (event.keyCode == 27 && // Esc | |
853 !this.isOverlayVisible_()) { | |
854 this.closeTopSubPage_(); | |
855 } | |
856 }; | |
857 | |
858 OptionsPage.setClearPluginLSODataEnabled = function(enabled) { | 586 OptionsPage.setClearPluginLSODataEnabled = function(enabled) { |
859 if (enabled) { | 587 if (enabled) { |
860 document.documentElement.setAttribute( | 588 document.documentElement.setAttribute( |
861 'flashPluginSupportsClearSiteData', ''); | 589 'flashPluginSupportsClearSiteData', ''); |
862 } else { | 590 } else { |
863 document.documentElement.removeAttribute( | 591 document.documentElement.removeAttribute( |
864 'flashPluginSupportsClearSiteData'); | 592 'flashPluginSupportsClearSiteData'); |
865 } | 593 } |
866 }; | 594 }; |
867 | 595 |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
966 /** | 694 /** |
967 * Sets page visibility. | 695 * Sets page visibility. |
968 */ | 696 */ |
969 set visible(visible) { | 697 set visible(visible) { |
970 if ((this.visible && visible) || (!this.visible && !visible)) | 698 if ((this.visible && visible) || (!this.visible && !visible)) |
971 return; | 699 return; |
972 | 700 |
973 this.pageDiv.hidden = !visible; | 701 this.pageDiv.hidden = !visible; |
974 this.setContainerVisibility_(visible); | 702 this.setContainerVisibility_(visible); |
975 | 703 |
976 OptionsPage.updatePageFreezeStates(); | 704 OptionsPage.updateRootPageFreezeState(); |
977 OptionsPage.updateManagedBannerVisibility(); | 705 OptionsPage.updateManagedBannerVisibility(); |
978 | 706 |
979 // A subpage was shown or hidden. | 707 if (this.isOverlay && !visible) |
980 if (!this.isOverlay && this.nestingLevel > 0) | |
981 OptionsPage.updateDisplayForShowOrHideSubpage_(); | |
982 else if (this.isOverlay && !visible) | |
983 OptionsPage.updateScrollPosition_(); | 708 OptionsPage.updateScrollPosition_(); |
984 | 709 |
985 cr.dispatchPropertyChange(this, 'visible', visible, !visible); | 710 cr.dispatchPropertyChange(this, 'visible', visible, !visible); |
986 }, | 711 }, |
987 | 712 |
988 /** | 713 /** |
989 * Shows or hides this page's container. | 714 * Shows or hides this page's container. |
990 * @param {boolean} visible Whether the container should be visible or not. | 715 * @param {boolean} visible Whether the container should be visible or not. |
991 * @private | 716 * @private |
992 */ | 717 */ |
993 setContainerVisibility_: function(visible) { | 718 setContainerVisibility_: function(visible) { |
994 var container = null; | 719 if (!this.isOverlay) |
995 if (this.isOverlay) { | 720 return; |
996 container = this.pageDiv.parentNode; | |
997 } else { | |
998 var nestingLevel = this.nestingLevel; | |
999 if (nestingLevel > 0) | |
1000 container = $('subpage-sheet-container-' + nestingLevel); | |
1001 } | |
1002 var isSubpage = !this.isOverlay; | |
1003 | 721 |
1004 if (!container) | 722 var container = this.pageDiv.parentNode; |
1005 return; | |
1006 | 723 |
1007 if (visible) | 724 if (visible) |
1008 uber.invokeMethodOnParent('beginInterceptingEvents'); | 725 uber.invokeMethodOnParent('beginInterceptingEvents'); |
1009 | 726 |
1010 if (container.hidden != visible) { | 727 if (container.hidden != visible) { |
1011 if (visible) { | 728 if (visible) { |
1012 // If the container is set hidden and then immediately set visible | 729 // If the container is set hidden and then immediately set visible |
1013 // again, the fadeCompleted_ callback would cause it to be erroneously | 730 // again, the fadeCompleted_ callback would cause it to be erroneously |
1014 // hidden again. Removing the transparent tag avoids that. | 731 // hidden again. Removing the transparent tag avoids that. |
1015 container.classList.remove('transparent'); | 732 container.classList.remove('transparent'); |
1016 } | 733 } |
1017 return; | 734 return; |
1018 } | 735 } |
1019 | 736 |
1020 if (visible) { | 737 if (visible) { |
1021 container.hidden = false; | 738 container.hidden = false; |
1022 if (document.documentElement.classList.contains('loading')) { | 739 if (document.documentElement.classList.contains('loading')) { |
1023 container.classList.remove('transparent'); | 740 container.classList.remove('transparent'); |
1024 } else { | 741 } else { |
1025 if (isSubpage) { | |
1026 var computedStyle = window.getComputedStyle(container); | |
1027 container.style.WebkitPaddingStart = | |
1028 parseInt(computedStyle.WebkitPaddingStart, 10) + 100 + 'px'; | |
1029 } | |
1030 // Separate animating changes from the removal of display:none. | 742 // Separate animating changes from the removal of display:none. |
1031 window.setTimeout(function() { | 743 window.setTimeout(function() { |
1032 container.classList.remove('transparent'); | 744 container.classList.remove('transparent'); |
1033 if (isSubpage) | |
1034 container.style.WebkitPaddingStart = ''; | |
1035 }); | 745 }); |
1036 } | 746 } |
1037 } else { | 747 } else { |
1038 var self = this; | 748 var self = this; |
1039 container.addEventListener('webkitTransitionEnd', function f(e) { | 749 container.addEventListener('webkitTransitionEnd', function f(e) { |
1040 if (e.propertyName != 'opacity') | 750 if (e.propertyName != 'opacity') |
1041 return; | 751 return; |
1042 container.removeEventListener('webkitTransitionEnd', f); | 752 container.removeEventListener('webkitTransitionEnd', f); |
1043 self.fadeCompleted_(container); | 753 self.fadeCompleted_(container); |
1044 }); | 754 }); |
1045 container.classList.add('transparent'); | 755 container.classList.add('transparent'); |
1046 } | 756 } |
1047 }, | 757 }, |
1048 | 758 |
1049 /** | 759 /** |
1050 * Called when a container opacity transition finishes. | 760 * Called when a container opacity transition finishes. |
1051 * @param {HTMLElement} container The container element. | 761 * @param {HTMLElement} container The container element. |
1052 * @private | 762 * @private |
1053 */ | 763 */ |
1054 fadeCompleted_: function(container) { | 764 fadeCompleted_: function(container) { |
1055 if (container.classList.contains('transparent')) { | 765 if (container.classList.contains('transparent')) { |
1056 container.hidden = true; | 766 container.hidden = true; |
1057 if (this.nestingLevel == 1) | 767 if (this.nestingLevel == 1) |
1058 uber.invokeMethodOnParent('stopInterceptingEvents'); | 768 uber.invokeMethodOnParent('stopInterceptingEvents'); |
1059 } | 769 } |
1060 }, | 770 }, |
1061 | 771 |
1062 /** | 772 /** |
1063 * Focuses the first control on the page. | |
1064 */ | |
1065 focusFirstElement: function() { | |
1066 // Sets focus on the first interactive element in the page. | |
1067 var focusElement = | |
1068 this.pageDiv.querySelector('button, input, list, select'); | |
1069 if (focusElement) | |
1070 focusElement.focus(); | |
1071 }, | |
1072 | |
1073 /** | |
1074 * The nesting level of this page. | 773 * The nesting level of this page. |
1075 * @type {number} The nesting level of this page (0 for top-level page) | 774 * @type {number} The nesting level of this page (0 for top-level page) |
1076 */ | 775 */ |
1077 get nestingLevel() { | 776 get nestingLevel() { |
1078 var level = 0; | 777 var level = 0; |
1079 var parent = this.parentPage; | 778 var parent = this.parentPage; |
1080 while (parent) { | 779 while (parent) { |
1081 level++; | 780 level++; |
1082 parent = parent.parentPage; | 781 parent = parent.parentPage; |
1083 } | 782 } |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1116 canShowPage: function() { | 815 canShowPage: function() { |
1117 return true; | 816 return true; |
1118 }, | 817 }, |
1119 }; | 818 }; |
1120 | 819 |
1121 // Export | 820 // Export |
1122 return { | 821 return { |
1123 OptionsPage: OptionsPage | 822 OptionsPage: OptionsPage |
1124 }; | 823 }; |
1125 }); | 824 }); |
OLD | NEW |