Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(815)

Side by Side Diff: chrome/browser/resources/options/options_page.js

Issue 7003007: Apply content-security-policy to the HTML options page. This is a (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 cr.define('options', function() {
6 /////////////////////////////////////////////////////////////////////////////
7 // OptionsPage class:
8
9 /**
10 * Base class for options page.
11 * @constructor
12 * @param {string} name Options page name, also defines id of the div element
13 * containing the options view and the name of options page navigation bar
14 * item as name+'PageNav'.
15 * @param {string} title Options page title, used for navigation bar
16 * @extends {EventTarget}
17 */
18 function OptionsPage(name, title, pageDivName) {
19 this.name = name;
20 this.title = title;
21 this.pageDivName = pageDivName;
22 this.pageDiv = $(this.pageDivName);
23 this.tab = null;
24 this.managed = false;
25 }
26
27 const SUBPAGE_SHEET_COUNT = 2;
28
29 /**
30 * Main level option pages.
31 * @protected
32 */
33 OptionsPage.registeredPages = {};
34
35 /**
36 * Pages which are meant to behave like modal dialogs.
37 * @protected
38 */
39 OptionsPage.registeredOverlayPages = {};
40
41 /**
42 * Whether or not |initialize| has been called.
43 * @private
44 */
45 OptionsPage.initialized_ = false;
46
47 /**
48 * Gets the default page (to be shown on initial load).
49 */
50 OptionsPage.getDefaultPage = function() {
51 return BrowserOptions.getInstance();
52 };
53
54 /**
55 * Shows the default page.
56 */
57 OptionsPage.showDefaultPage = function() {
58 this.navigateToPage(this.getDefaultPage().name);
59 };
60
61 /**
62 * "Navigates" to a page, meaning that the page will be shown and the
63 * appropriate entry is placed in the history.
64 * @param {string} pageName Page name.
65 */
66 OptionsPage.navigateToPage = function(pageName) {
67 this.showPageByName(pageName, true);
68 };
69
70 /**
71 * Shows a registered page. This handles both top-level pages and sub-pages.
72 * @param {string} pageName Page name.
73 * @param {boolean} updateHistory True if we should update the history after
74 * showing the page.
75 * @private
76 */
77 OptionsPage.showPageByName = function(pageName, updateHistory) {
78 // Find the currently visible root-level page.
79 var rootPage = null;
80 for (var name in this.registeredPages) {
81 var page = this.registeredPages[name];
82 if (page.visible && !page.parentPage) {
83 rootPage = page;
84 break;
85 }
86 }
87
88 // Find the target page.
89 var targetPage = this.registeredPages[pageName];
90 if (!targetPage || !targetPage.canShowPage()) {
91 // If it's not a page, try it as an overlay.
92 if (!targetPage && this.showOverlay_(pageName, rootPage)) {
93 if (updateHistory)
94 this.updateHistoryState_();
95 return;
96 } else {
97 targetPage = this.getDefaultPage();
98 }
99 }
100
101 pageName = targetPage.name;
102
103 // Determine if the root page is 'sticky', meaning that it
104 // shouldn't change when showing a sub-page. This can happen for special
105 // pages like Search.
106 var isRootPageLocked =
107 rootPage && rootPage.sticky && targetPage.parentPage;
108
109 // Notify pages if they will be hidden.
110 for (var name in this.registeredPages) {
111 var page = this.registeredPages[name];
112 if (!page.parentPage && isRootPageLocked)
113 continue;
114 if (page.willHidePage && name != pageName &&
115 !page.isAncestorOfPage(targetPage))
116 page.willHidePage();
117 }
118
119 // Update visibilities to show only the hierarchy of the target page.
120 for (var name in this.registeredPages) {
121 var page = this.registeredPages[name];
122 if (!page.parentPage && isRootPageLocked)
123 continue;
124 page.visible = name == pageName ||
125 (!document.documentElement.classList.contains('hide-menu') &&
126 page.isAncestorOfPage(targetPage));
127 }
128
129 // Update the history and current location.
130 if (updateHistory)
131 this.updateHistoryState_();
132
133 // Always update the page title.
134 document.title = targetPage.title;
135
136 // Notify pages if they were shown.
137 for (var name in this.registeredPages) {
138 var page = this.registeredPages[name];
139 if (!page.parentPage && isRootPageLocked)
140 continue;
141 if (page.didShowPage && (name == pageName ||
142 page.isAncestorOfPage(targetPage)))
143 page.didShowPage();
144 }
145 };
146
147 /**
148 * Updates the visibility and stacking order of the subpage backdrop
149 * according to which subpage is topmost and visible.
150 * @private
151 */
152 OptionsPage.updateSubpageBackdrop_ = function () {
153 var topmostPage = this.getTopmostVisibleNonOverlayPage_();
154 var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0;
155
156 var subpageBackdrop = $('subpage-backdrop');
157 if (nestingLevel > 0) {
158 var container = $('subpage-sheet-container-' + nestingLevel);
159 subpageBackdrop.style.zIndex =
160 parseInt(window.getComputedStyle(container).zIndex) - 1;
161 subpageBackdrop.hidden = false;
162 } else {
163 subpageBackdrop.hidden = true;
164 }
165 };
166
167 /**
168 * Pushes the current page onto the history stack, overriding the last page
169 * if it is the generic chrome://settings/.
170 * @private
171 */
172 OptionsPage.updateHistoryState_ = function() {
173 var page = this.getTopmostVisiblePage();
174 var path = location.pathname;
175 if (path)
176 path = path.slice(1);
177 // The page is already in history (the user may have clicked the same link
178 // twice). Do nothing.
179 if (path == page.name)
180 return;
181
182 // If there is no path, the current location is chrome://settings/.
183 // Override this with the new page.
184 var historyFunction = path ? window.history.pushState :
185 window.history.replaceState;
186 historyFunction.call(window.history,
187 {pageName: page.name},
188 page.title,
189 '/' + page.name);
190 // Update tab title.
191 document.title = page.title;
192 };
193
194 /**
195 * Shows a registered Overlay page. Does not update history.
196 * @param {string} overlayName Page name.
197 * @param {OptionPage} rootPage The currently visible root-level page.
198 * @return {boolean} whether we showed an overlay.
199 */
200 OptionsPage.showOverlay_ = function(overlayName, rootPage) {
201 var overlay = this.registeredOverlayPages[overlayName];
202 if (!overlay || !overlay.canShowPage())
203 return false;
204
205 if ((!rootPage || !rootPage.sticky) && overlay.parentPage)
206 this.showPageByName(overlay.parentPage.name, false);
207
208 overlay.visible = true;
209 if (overlay.didShowPage) overlay.didShowPage();
210 return true;
211 };
212
213 /**
214 * Returns whether or not an overlay is visible.
215 * @return {boolean} True if an overlay is visible.
216 * @private
217 */
218 OptionsPage.isOverlayVisible_ = function() {
219 return this.getVisibleOverlay_() != null;
220 };
221
222 /**
223 * @return {boolean} True if the visible overlay should be closed.
224 * @private
225 */
226 OptionsPage.shouldCloseOverlay_ = function() {
227 var overlay = this.getVisibleOverlay_();
228 return overlay && overlay.shouldClose();
229 };
230
231 /**
232 * Returns the currently visible overlay, or null if no page is visible.
233 * @return {OptionPage} The visible overlay.
234 */
235 OptionsPage.getVisibleOverlay_ = function() {
236 for (var name in this.registeredOverlayPages) {
237 var page = this.registeredOverlayPages[name];
238 if (page.visible)
239 return page;
240 }
241 return null;
242 };
243
244 /**
245 * Closes the visible overlay. Updates the history state after closing the
246 * overlay.
247 */
248 OptionsPage.closeOverlay = function() {
249 var overlay = this.getVisibleOverlay_();
250 if (!overlay)
251 return;
252
253 overlay.visible = false;
254 if (overlay.didClosePage) overlay.didClosePage();
255 this.updateHistoryState_();
256 };
257
258 /**
259 * Hides the visible overlay. Does not affect the history state.
260 * @private
261 */
262 OptionsPage.hideOverlay_ = function() {
263 var overlay = this.getVisibleOverlay_();
264 if (overlay)
265 overlay.visible = false;
266 };
267
268 /**
269 * Returns the topmost visible page (overlays excluded).
270 * @return {OptionPage} The topmost visible page aside any overlay.
271 * @private
272 */
273 OptionsPage.getTopmostVisibleNonOverlayPage_ = function() {
274 var topPage = null;
275 for (var name in this.registeredPages) {
276 var page = this.registeredPages[name];
277 if (page.visible &&
278 (!topPage || page.nestingLevel > topPage.nestingLevel))
279 topPage = page;
280 }
281
282 return topPage;
283 };
284
285 /**
286 * Returns the topmost visible page, or null if no page is visible.
287 * @return {OptionPage} The topmost visible page.
288 */
289 OptionsPage.getTopmostVisiblePage = function() {
290 // Check overlays first since they're top-most if visible.
291 return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_();
292 };
293
294 /**
295 * Closes the topmost open subpage, if any.
296 * @private
297 */
298 OptionsPage.closeTopSubPage_ = function() {
299 var topPage = this.getTopmostVisiblePage();
300 if (topPage && !topPage.isOverlay && topPage.parentPage)
301 topPage.visible = false;
302
303 this.updateHistoryState_();
304 };
305
306 /**
307 * Closes all subpages below the given level.
308 * @param {number} level The nesting level to close below.
309 */
310 OptionsPage.closeSubPagesToLevel = function(level) {
311 var topPage = this.getTopmostVisiblePage();
312 while (topPage && topPage.nestingLevel > level) {
313 topPage.visible = false;
314 topPage = topPage.parentPage;
315 }
316
317 this.updateHistoryState_();
318 };
319
320 /**
321 * Updates managed banner visibility state based on the topmost page.
322 */
323 OptionsPage.updateManagedBannerVisibility = function() {
324 var topPage = this.getTopmostVisiblePage();
325 if (topPage)
326 topPage.updateManagedBannerVisibility();
327 };
328
329 /**
330 * Shows the tab contents for the given navigation tab.
331 * @param {!Element} tab The tab that the user clicked.
332 */
333 OptionsPage.showTab = function(tab) {
334 // Search parents until we find a tab, or the nav bar itself. This allows
335 // tabs to have child nodes, e.g. labels in separately-styled spans.
336 while (tab && !tab.classList.contains('subpages-nav-tabs') &&
337 !tab.classList.contains('tab')) {
338 tab = tab.parentNode;
339 }
340 if (!tab || !tab.classList.contains('tab'))
341 return;
342
343 if (this.activeNavTab != null) {
344 this.activeNavTab.classList.remove('active-tab');
345 $(this.activeNavTab.getAttribute('tab-contents')).classList.
346 remove('active-tab-contents');
347 }
348
349 tab.classList.add('active-tab');
350 $(tab.getAttribute('tab-contents')).classList.add('active-tab-contents');
351 this.activeNavTab = tab;
352 };
353
354 /**
355 * Registers new options page.
356 * @param {OptionsPage} page Page to register.
357 */
358 OptionsPage.register = function(page) {
359 this.registeredPages[page.name] = page;
360 // Create and add new page <li> element to navbar.
361 var pageNav = document.createElement('li');
362 pageNav.id = page.name + 'PageNav';
363 pageNav.className = 'navbar-item';
364 pageNav.setAttribute('pageName', page.name);
365 pageNav.textContent = page.pageDiv.querySelector('h1').textContent;
366 pageNav.tabIndex = 0;
367 pageNav.onclick = function(event) {
368 OptionsPage.navigateToPage(this.getAttribute('pageName'));
369 };
370 pageNav.onkeypress = function(event) {
371 // Enter or space
372 if (event.keyCode == 13 || event.keyCode == 32) {
373 OptionsPage.navigateToPage(this.getAttribute('pageName'));
374 }
375 };
376 var navbar = $('navbar');
377 navbar.appendChild(pageNav);
378 page.tab = pageNav;
379 page.initializePage();
380 };
381
382 /**
383 * Find an enclosing section for an element if it exists.
384 * @param {Element} element Element to search.
385 * @return {OptionPage} The section element, or null.
386 * @private
387 */
388 OptionsPage.findSectionForNode_ = function(node) {
389 while (node = node.parentNode) {
390 if (node.nodeName == 'SECTION')
391 return node;
392 }
393 return null;
394 };
395
396 /**
397 * Registers a new Sub-page.
398 * @param {OptionsPage} subPage Sub-page to register.
399 * @param {OptionsPage} parentPage Associated parent page for this page.
400 * @param {Array} associatedControls Array of control elements that lead to
401 * this sub-page. The first item is typically a button in a root-level
402 * page. There may be additional buttons for nested sub-pages.
403 */
404 OptionsPage.registerSubPage = function(subPage,
405 parentPage,
406 associatedControls) {
407 this.registeredPages[subPage.name] = subPage;
408 subPage.parentPage = parentPage;
409 if (associatedControls) {
410 subPage.associatedControls = associatedControls;
411 if (associatedControls.length) {
412 subPage.associatedSection =
413 this.findSectionForNode_(associatedControls[0]);
414 }
415 }
416 subPage.tab = undefined;
417 subPage.initializePage();
418 };
419
420 /**
421 * Registers a new Overlay page.
422 * @param {OptionsPage} overlay Overlay to register.
423 * @param {OptionsPage} parentPage Associated parent page for this overlay.
424 * @param {Array} associatedControls Array of control elements associated with
425 * this page.
426 */
427 OptionsPage.registerOverlay = function(overlay,
428 parentPage,
429 associatedControls) {
430 this.registeredOverlayPages[overlay.name] = overlay;
431 overlay.parentPage = parentPage;
432 if (associatedControls) {
433 overlay.associatedControls = associatedControls;
434 if (associatedControls.length) {
435 overlay.associatedSection =
436 this.findSectionForNode_(associatedControls[0]);
437 }
438 }
439 overlay.tab = undefined;
440 overlay.isOverlay = true;
441 overlay.initializePage();
442 };
443
444 /**
445 * Callback for window.onpopstate.
446 * @param {Object} data State data pushed into history.
447 */
448 OptionsPage.setState = function(data) {
449 if (data && data.pageName) {
450 // It's possible an overlay may be the last top-level page shown.
451 if (this.isOverlayVisible_() &&
452 this.registeredOverlayPages[data.pageName] == undefined) {
453 this.hideOverlay_();
454 }
455
456 this.showPageByName(data.pageName, false);
457 }
458 };
459
460 /**
461 * Freezes/unfreezes the scroll position of given level's page container.
462 * @param {boolean} freeze Whether the page should be frozen.
463 * @param {number} level The level to freeze/unfreeze.
464 * @private
465 */
466 OptionsPage.setPageFrozenAtLevel_ = function(freeze, level) {
467 var container = level == 0 ? $('toplevel-page-container')
468 : $('subpage-sheet-container-' + level);
469
470 if (container.classList.contains('frozen') == freeze)
471 return;
472
473 if (freeze) {
474 var scrollPosition = document.body.scrollTop;
475 // Lock the width, since auto width computation may change.
476 container.style.width = window.getComputedStyle(container).width;
477 container.classList.add('frozen');
478 container.style.top = -scrollPosition + 'px';
479 this.updateFrozenElementHorizontalPosition_(container);
480 } else {
481 var scrollPosition = - parseInt(container.style.top, 10);
482 container.classList.remove('frozen');
483 container.style.top = '';
484 container.style.left = '';
485 container.style.right = '';
486 container.style.width = '';
487 // Restore the scroll position.
488 if (!container.hidden)
489 window.scroll(document.body.scrollLeft, scrollPosition);
490 }
491 };
492
493 /**
494 * Freezes/unfreezes the scroll position of visible pages based on the current
495 * page stack.
496 */
497 OptionsPage.updatePageFreezeStates = function() {
498 var topPage = OptionsPage.getTopmostVisiblePage();
499 if (!topPage)
500 return;
501 var nestingLevel = topPage.isOverlay ? 100 : topPage.nestingLevel;
502 for (var i = 0; i <= SUBPAGE_SHEET_COUNT; i++) {
503 this.setPageFrozenAtLevel_(i < nestingLevel, i);
504 }
505 };
506
507 /**
508 * Initializes the complete options page. This will cause all C++ handlers to
509 * be invoked to do final setup.
510 */
511 OptionsPage.initialize = function() {
512 chrome.send('coreOptionsInitialize');
513 this.initialized_ = true;
514
515 document.addEventListener('scroll', this.handleScroll_.bind(this));
516 window.addEventListener('resize', this.handleResize_.bind(this));
517
518 if (!document.documentElement.classList.contains('hide-menu')) {
519 // Close subpages if the user clicks on the html body. Listen in the
520 // capturing phase so that we can stop the click from doing anything.
521 document.body.addEventListener('click',
522 this.bodyMouseEventHandler_.bind(this),
523 true);
524 // We also need to cancel mousedowns on non-subpage content.
525 document.body.addEventListener('mousedown',
526 this.bodyMouseEventHandler_.bind(this),
527 true);
528
529 var self = this;
530 // Hook up the close buttons.
531 subpageCloseButtons = document.querySelectorAll('.close-subpage');
532 for (var i = 0; i < subpageCloseButtons.length; i++) {
533 subpageCloseButtons[i].onclick = function() {
534 self.closeTopSubPage_();
535 };
536 };
537
538 // Install handler for key presses.
539 document.addEventListener('keydown',
540 this.keyDownEventHandler_.bind(this));
541
542 document.addEventListener('focus', this.manageFocusChange_.bind(this),
543 true);
544 }
545
546 // Calculate and store the horizontal locations of elements that may be
547 // frozen later.
548 var sidebarWidth =
549 parseInt(window.getComputedStyle($('mainview')).webkitPaddingStart, 10);
550 $('toplevel-page-container').horizontalOffset = sidebarWidth +
551 parseInt(window.getComputedStyle(
552 $('mainview-content')).webkitPaddingStart, 10);
553 for (var level = 1; level <= SUBPAGE_SHEET_COUNT; level++) {
554 var containerId = 'subpage-sheet-container-' + level;
555 $(containerId).horizontalOffset = sidebarWidth;
556 }
557 $('subpage-backdrop').horizontalOffset = sidebarWidth;
558 // Trigger the resize handler manually to set the initial state.
559 this.handleResize_(null);
560 };
561
562 /**
563 * Does a bounds check for the element on the given x, y client coordinates.
564 * @param {Element} e The DOM element.
565 * @param {number} x The client X to check.
566 * @param {number} y The client Y to check.
567 * @return {boolean} True if the point falls within the element's bounds.
568 * @private
569 */
570 OptionsPage.elementContainsPoint_ = function(e, x, y) {
571 var clientRect = e.getBoundingClientRect();
572 return x >= clientRect.left && x <= clientRect.right &&
573 y >= clientRect.top && y <= clientRect.bottom;
574 };
575
576 /**
577 * Called when focus changes; ensures that focus doesn't move outside
578 * the topmost subpage/overlay.
579 * @param {Event} e The focus change event.
580 * @private
581 */
582 OptionsPage.manageFocusChange_ = function(e) {
583 var focusableItemsRoot;
584 var topPage = this.getTopmostVisiblePage();
585 if (!topPage)
586 return;
587
588 if (topPage.isOverlay) {
589 // If an overlay is visible, that defines the tab loop.
590 focusableItemsRoot = topPage.pageDiv;
591 } else {
592 // If a subpage is visible, use its parent as the tab loop constraint.
593 // (The parent is used because it contains the close button.)
594 if (topPage.nestingLevel > 0)
595 focusableItemsRoot = topPage.pageDiv.parentNode;
596 }
597
598 if (focusableItemsRoot && !focusableItemsRoot.contains(e.target))
599 topPage.focusFirstElement();
600 };
601
602 /**
603 * Called when the page is scrolled; moves elements that are position:fixed
604 * but should only behave as if they are fixed for vertical scrolling.
605 * @param {Event} e The scroll event.
606 * @private
607 */
608 OptionsPage.handleScroll_ = function(e) {
609 var scrollHorizontalOffset = document.body.scrollLeft;
610 // position:fixed doesn't seem to work for horizontal scrolling in RTL mode,
611 // so only adjust in LTR mode (where scroll values will be positive).
612 if (scrollHorizontalOffset >= 0) {
613 $('navbar-container').style.left = -scrollHorizontalOffset + 'px';
614 var subpageBackdrop = $('subpage-backdrop');
615 subpageBackdrop.style.left = subpageBackdrop.horizontalOffset -
616 scrollHorizontalOffset + 'px';
617 this.updateAllFrozenElementPositions_();
618 }
619 };
620
621 /**
622 * Updates all frozen pages to match the horizontal scroll position.
623 * @private
624 */
625 OptionsPage.updateAllFrozenElementPositions_ = function() {
626 var frozenElements = document.querySelectorAll('.frozen');
627 for (var i = 0; i < frozenElements.length; i++) {
628 this.updateFrozenElementHorizontalPosition_(frozenElements[i]);
629 }
630 };
631
632 /**
633 * Updates the given frozen element to match the horizontal scroll position.
634 * @param {HTMLElement} e The frozen element to update
635 * @private
636 */
637 OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) {
638 if (document.documentElement.dir == 'rtl')
639 e.style.right = e.horizontalOffset + 'px';
640 else
641 e.style.left = e.horizontalOffset - document.body.scrollLeft + 'px';
642 };
643
644 /**
645 * Called when the page is resized; adjusts the size of elements that depend
646 * on the veiwport.
647 * @param {Event} e The resize event.
648 * @private
649 */
650 OptionsPage.handleResize_ = function(e) {
651 // Set an explicit height equal to the viewport on all the subpage
652 // containers shorter than the viewport. This is used instead of
653 // min-height: 100% so that there is an explicit height for the subpages'
654 // min-height: 100%.
655 var viewportHeight = document.documentElement.clientHeight;
656 var subpageContainers =
657 document.querySelectorAll('.subpage-sheet-container');
658 for (var i = 0; i < subpageContainers.length; i++) {
659 if (subpageContainers[i].scrollHeight > viewportHeight)
660 subpageContainers[i].style.removeProperty('height');
661 else
662 subpageContainers[i].style.height = viewportHeight + 'px';
663 }
664 };
665
666 /**
667 * A function to handle mouse events (mousedown or click) on the html body by
668 * closing subpages and/or stopping event propagation.
669 * @return {Event} a mousedown or click event.
670 * @private
671 */
672 OptionsPage.bodyMouseEventHandler_ = function(event) {
673 // Do nothing if a subpage isn't showing.
674 var topPage = this.getTopmostVisiblePage();
675 if (!topPage || topPage.isOverlay || !topPage.parentPage)
676 return;
677
678 // Do nothing if the client coordinates are not within the source element.
679 // This situation is indicative of a Webkit bug where clicking on a
680 // radio/checkbox label span will generate an event with client coordinates
681 // of (-scrollX, -scrollY).
682 // See https://bugs.webkit.org/show_bug.cgi?id=56606
683 if (event.clientX == -document.body.scrollLeft &&
684 event.clientY == -document.body.scrollTop) {
685 return;
686 }
687
688 // Don't interfere with navbar clicks.
689 if ($('navbar').contains(event.target))
690 return;
691
692 // Figure out which page the click happened in.
693 for (var level = topPage.nestingLevel; level >= 0; level--) {
694 var clickIsWithinLevel = level == 0 ? true :
695 OptionsPage.elementContainsPoint_(
696 $('subpage-sheet-' + level), event.clientX, event.clientY);
697
698 if (!clickIsWithinLevel)
699 continue;
700
701 // Event was within the topmost page; do nothing.
702 if (topPage.nestingLevel == level)
703 return;
704
705 // Block propgation of both clicks and mousedowns, but only close subpages
706 // on click.
707 if (event.type == 'click')
708 this.closeSubPagesToLevel(level);
709 event.stopPropagation();
710 event.preventDefault();
711 return;
712 }
713 };
714
715 /**
716 * A function to handle key press events.
717 * @return {Event} a keydown event.
718 * @private
719 */
720 OptionsPage.keyDownEventHandler_ = function(event) {
721 // Close the top overlay or sub-page on esc.
722 if (event.keyCode == 27) { // Esc
723 if (this.isOverlayVisible_()) {
724 if (this.shouldCloseOverlay_())
725 this.closeOverlay();
726 } else {
727 this.closeTopSubPage_();
728 }
729 }
730 };
731
732 OptionsPage.setClearPluginLSODataEnabled = function(enabled) {
733 if (enabled) {
734 document.documentElement.setAttribute(
735 'flashPluginSupportsClearSiteData', '');
736 } else {
737 document.documentElement.removeAttribute(
738 'flashPluginSupportsClearSiteData');
739 }
740 };
741
742 /**
743 * Re-initializes the C++ handlers if necessary. This is called if the
744 * handlers are torn down and recreated but the DOM may not have been (in
745 * which case |initialize| won't be called again). If |initialize| hasn't been
746 * called, this does nothing (since it will be later, once the DOM has
747 * finished loading).
748 */
749 OptionsPage.reinitializeCore = function() {
750 if (this.initialized_)
751 chrome.send('coreOptionsInitialize');
752 }
753
754 OptionsPage.prototype = {
755 __proto__: cr.EventTarget.prototype,
756
757 /**
758 * The parent page of this option page, or null for top-level pages.
759 * @type {OptionsPage}
760 */
761 parentPage: null,
762
763 /**
764 * The section on the parent page that is associated with this page.
765 * Can be null.
766 * @type {Element}
767 */
768 associatedSection: null,
769
770 /**
771 * An array of controls that are associated with this page. The first
772 * control should be located on a top-level page.
773 * @type {OptionsPage}
774 */
775 associatedControls: null,
776
777 /**
778 * Initializes page content.
779 */
780 initializePage: function() {},
781
782 /**
783 * Sets managed banner visibility state.
784 */
785 setManagedBannerVisibility: function(visible) {
786 this.managed = visible;
787 if (this.visible) {
788 this.updateManagedBannerVisibility();
789 }
790 },
791
792 /**
793 * Updates managed banner visibility state. This function iterates over
794 * all input fields of a window and if any of these is marked as managed
795 * it triggers the managed banner to be visible. The banner can be enforced
796 * being on through the managed flag of this class but it can not be forced
797 * being off if managed items exist.
798 */
799 updateManagedBannerVisibility: function() {
800 var bannerDiv = $('managed-prefs-banner');
801
802 var hasManaged = this.managed;
803 if (!hasManaged) {
804 var inputElements = this.pageDiv.querySelectorAll('input');
805 for (var i = 0, len = inputElements.length; i < len; i++) {
806 if (inputElements[i].managed) {
807 hasManaged = true;
808 break;
809 }
810 }
811 }
812 if (hasManaged) {
813 bannerDiv.hidden = false;
814 var height = window.getComputedStyle($('managed-prefs-banner')).height;
815 $('subpage-backdrop').style.top = height;
816 } else {
817 bannerDiv.hidden = true;
818 $('subpage-backdrop').style.top = '0';
819 }
820 },
821
822 /**
823 * Gets page visibility state.
824 */
825 get visible() {
826 var page = $(this.pageDivName);
827 return page && page.ownerDocument.defaultView.getComputedStyle(
828 page).display == 'block';
829 },
830
831 /**
832 * Sets page visibility.
833 */
834 set visible(visible) {
835 if ((this.visible && visible) || (!this.visible && !visible))
836 return;
837
838 this.setContainerVisibility_(visible);
839 if (visible) {
840 this.pageDiv.classList.remove('hidden');
841
842 if (this.tab)
843 this.tab.classList.add('navbar-item-selected');
844 } else {
845 this.pageDiv.classList.add('hidden');
846
847 if (this.tab)
848 this.tab.classList.remove('navbar-item-selected');
849 }
850
851 OptionsPage.updatePageFreezeStates();
852
853 // A subpage was shown or hidden.
854 if (!this.isOverlay && this.nestingLevel > 0) {
855 OptionsPage.updateSubpageBackdrop_();
856 if (visible) {
857 // Scroll to the top of the newly-opened subpage.
858 window.scroll(document.body.scrollLeft, 0)
859 }
860 }
861
862 // The managed prefs banner is global, so after any visibility change
863 // update it based on the topmost page, not necessarily this page
864 // (e.g., if an ancestor is made visible after a child).
865 OptionsPage.updateManagedBannerVisibility();
866
867 cr.dispatchPropertyChange(this, 'visible', visible, !visible);
868 },
869
870 /**
871 * Shows or hides this page's container.
872 * @param {boolean} visible Whether the container should be visible or not.
873 * @private
874 */
875 setContainerVisibility_: function(visible) {
876 var container = null;
877 if (this.isOverlay) {
878 container = $('overlay');
879 } else {
880 var nestingLevel = this.nestingLevel;
881 if (nestingLevel > 0)
882 container = $('subpage-sheet-container-' + nestingLevel);
883 }
884 var isSubpage = !this.isOverlay;
885
886 if (!container || container.hidden != visible)
887 return;
888
889 if (visible) {
890 container.hidden = false;
891 if (isSubpage) {
892 var computedStyle = window.getComputedStyle(container);
893 container.style.WebkitPaddingStart =
894 parseInt(computedStyle.WebkitPaddingStart, 10) + 100 + 'px';
895 }
896 // Separate animating changes from the removal of display:none.
897 window.setTimeout(function() {
898 container.classList.remove('transparent');
899 if (isSubpage)
900 container.style.WebkitPaddingStart = '';
901 });
902 } else {
903 var self = this;
904 container.addEventListener('webkitTransitionEnd', function f(e) {
905 if (e.propertyName != 'opacity')
906 return;
907 container.removeEventListener('webkitTransitionEnd', f);
908 self.fadeCompleted_(container);
909 });
910 container.classList.add('transparent');
911 }
912 },
913
914 /**
915 * Called when a container opacity transition finishes.
916 * @param {HTMLElement} container The container element.
917 * @private
918 */
919 fadeCompleted_: function(container) {
920 if (container.classList.contains('transparent'))
921 container.hidden = true;
922 },
923
924 /**
925 * Focuses the first control on the page.
926 */
927 focusFirstElement: function() {
928 // Sets focus on the first interactive element in the page.
929 var focusElement =
930 this.pageDiv.querySelector('button, input, list, select');
931 if (focusElement)
932 focusElement.focus();
933 },
934
935 /**
936 * The nesting level of this page.
937 * @type {number} The nesting level of this page (0 for top-level page)
938 */
939 get nestingLevel() {
940 var level = 0;
941 var parent = this.parentPage;
942 while (parent) {
943 level++;
944 parent = parent.parentPage;
945 }
946 return level;
947 },
948
949 /**
950 * Whether the page is considered 'sticky', such that it will
951 * remain a top-level page even if sub-pages change.
952 * @type {boolean} True if this page is sticky.
953 */
954 get sticky() {
955 return false;
956 },
957
958 /**
959 * Checks whether this page is an ancestor of the given page in terms of
960 * subpage nesting.
961 * @param {OptionsPage} page
962 * @return {boolean} True if this page is nested under |page|
963 */
964 isAncestorOfPage: function(page) {
965 var parent = page.parentPage;
966 while (parent) {
967 if (parent == this)
968 return true;
969 parent = parent.parentPage;
970 }
971 return false;
972 },
973
974 /**
975 * Whether it should be possible to show the page.
976 * @return {boolean} True if the page should be shown
977 */
978 canShowPage: function() {
979 return true;
980 },
981
982 /**
983 * Whether an overlay should be closed. Used by overlay implementation to
984 * handle special closing behaviors.
985 * @return {boolean} True if the overlay should be closed.
986 */
987 shouldClose: function() {
988 return true;
989 },
990 };
991
992 // Export
993 return {
994 OptionsPage: OptionsPage
995 };
996 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698