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

Unified Diff: chrome/browser/resources/options2/options_page.js

Issue 8895023: Options2: Pull the trigger. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: DIAF. Created 9 years 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/options2/options_page.js
diff --git a/chrome/browser/resources/options2/options_page.js b/chrome/browser/resources/options2/options_page.js
new file mode 100644
index 0000000000000000000000000000000000000000..0599e0e46d9beac65c823e850c553cd4e7238b02
--- /dev/null
+++ b/chrome/browser/resources/options2/options_page.js
@@ -0,0 +1,1076 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('options', function() {
+ /////////////////////////////////////////////////////////////////////////////
+ // OptionsPage class:
+
+ /**
+ * Base class for options page.
+ * @constructor
+ * @param {string} name Options page name, also defines id of the div element
+ * containing the options view and the name of options page navigation bar
+ * item as name+'PageNav'.
+ * @param {string} title Options page title, used for navigation bar
+ * @extends {EventTarget}
+ */
+ function OptionsPage(name, title, pageDivName) {
+ this.name = name;
+ this.title = title;
+ this.pageDivName = pageDivName;
+ this.pageDiv = $(this.pageDivName);
+ this.tab = null;
+ }
+
+ const SUBPAGE_SHEET_COUNT = 2;
+
+ /**
+ * Main level option pages. Maps lower-case page names to the respective page
+ * object.
+ * @protected
+ */
+ OptionsPage.registeredPages = {};
+
+ /**
+ * Pages which are meant to behave like modal dialogs. Maps lower-case overlay
+ * names to the respective overlay object.
+ * @protected
+ */
+ OptionsPage.registeredOverlayPages = {};
+
+ /**
+ * Whether or not |initialize| has been called.
+ * @private
+ */
+ OptionsPage.initialized_ = false;
+
+ /**
+ * Gets the default page (to be shown on initial load).
+ */
+ OptionsPage.getDefaultPage = function() {
+ return BrowserOptions.getInstance();
+ };
+
+ /**
+ * Shows the default page.
+ */
+ OptionsPage.showDefaultPage = function() {
+ this.navigateToPage(this.getDefaultPage().name);
+ };
+
+ /**
+ * "Navigates" to a page, meaning that the page will be shown and the
+ * appropriate entry is placed in the history.
+ * @param {string} pageName Page name.
+ */
+ OptionsPage.navigateToPage = function(pageName) {
+ this.showPageByName(pageName, true);
+ };
+
+ /**
+ * Shows a registered page. This handles both top-level pages and sub-pages.
+ * @param {string} pageName Page name.
+ * @param {boolean} updateHistory True if we should update the history after
+ * showing the page.
+ * @private
+ */
+ OptionsPage.showPageByName = function(pageName, updateHistory) {
+ // Find the currently visible root-level page.
+ var rootPage = null;
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (page.visible && !page.parentPage) {
+ rootPage = page;
+ break;
+ }
+ }
+
+ // Find the target page.
+ var targetPage = this.registeredPages[pageName.toLowerCase()];
+ if (!targetPage || !targetPage.canShowPage()) {
+ // If it's not a page, try it as an overlay.
+ if (!targetPage && this.showOverlay_(pageName, rootPage)) {
+ if (updateHistory)
+ this.updateHistoryState_();
+ return;
+ } else {
+ targetPage = this.getDefaultPage();
+ }
+ }
+
+ pageName = targetPage.name.toLowerCase();
+ var targetPageWasVisible = targetPage.visible;
+
+ // Determine if the root page is 'sticky', meaning that it
+ // shouldn't change when showing a sub-page. This can happen for special
+ // pages like Search.
+ var isRootPageLocked =
+ rootPage && rootPage.sticky && targetPage.parentPage;
+
+ // Notify pages if they will be hidden.
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (!page.parentPage && isRootPageLocked)
+ continue;
+ if (page.willHidePage && name != pageName &&
+ !page.isAncestorOfPage(targetPage))
+ page.willHidePage();
+ }
+
+ // Update visibilities to show only the hierarchy of the target page.
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (!page.parentPage && isRootPageLocked)
+ continue;
+ page.visible = name == pageName ||
+ (!document.documentElement.classList.contains('hide-menu') &&
+ page.isAncestorOfPage(targetPage));
+ }
+
+ // Update the history and current location.
+ if (updateHistory)
+ this.updateHistoryState_();
+
+ // Always update the page title.
+ document.title = targetPage.title;
+
+ // Notify pages if they were shown.
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (!page.parentPage && isRootPageLocked)
+ continue;
+ if (!targetPageWasVisible && page.didShowPage && (name == pageName ||
+ page.isAncestorOfPage(targetPage)))
+ page.didShowPage();
+ }
+ };
+
+ /**
+ * Updates the visibility and stacking order of the subpage backdrop
+ * according to which subpage is topmost and visible.
+ * @private
+ */
+ OptionsPage.updateSubpageBackdrop_ = function () {
+ var topmostPage = this.getTopmostVisibleNonOverlayPage_();
+ var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0;
+
+ var subpageBackdrop = $('subpage-backdrop');
+ if (nestingLevel > 0) {
+ var container = $('subpage-sheet-container-' + nestingLevel);
+ subpageBackdrop.style.zIndex =
+ parseInt(window.getComputedStyle(container).zIndex) - 1;
+ subpageBackdrop.hidden = false;
+ } else {
+ subpageBackdrop.hidden = true;
+ }
+ };
+
+ /**
+ * Scrolls the page to the correct position (the top when opening a subpage,
+ * or the old scroll position a previously hidden subpage becomes visible).
+ * @private
+ */
+ OptionsPage.updateScrollPosition_ = function () {
+ var topmostPage = this.getTopmostVisibleNonOverlayPage_();
+ var nestingLevel = topmostPage ? topmostPage.nestingLevel : 0;
+
+ var container = (nestingLevel > 0) ?
+ $('subpage-sheet-container-' + nestingLevel) : $('page-container');
+
+ var scrollTop = container.oldScrollTop || 0;
+ container.oldScrollTop = undefined;
+ window.scroll(document.body.scrollLeft, scrollTop);
+ };
+
+ /**
+ * Pushes the current page onto the history stack, overriding the last page
+ * if it is the generic chrome://settings/.
+ * @private
+ */
+ OptionsPage.updateHistoryState_ = function() {
+ var page = this.getTopmostVisiblePage();
+ var path = location.pathname;
+ if (path)
+ path = path.slice(1).replace(/\/$/, ''); // Remove trailing slash.
+ // The page is already in history (the user may have clicked the same link
+ // twice). Do nothing.
+ if (path == page.name)
+ return;
+
+ // If there is no path, the current location is chrome://settings/.
+ // Override this with the new page.
+ var historyFunction = path ? window.history.pushState :
+ window.history.replaceState;
+ historyFunction.call(window.history,
+ {pageName: page.name},
+ page.title,
+ '/' + page.name);
+ // Update tab title.
+ document.title = page.title;
+ };
+
+ /**
+ * Shows a registered Overlay page. Does not update history.
+ * @param {string} overlayName Page name.
+ * @param {OptionPage} rootPage The currently visible root-level page.
+ * @return {boolean} whether we showed an overlay.
+ */
+ OptionsPage.showOverlay_ = function(overlayName, rootPage) {
+ var overlay = this.registeredOverlayPages[overlayName.toLowerCase()];
+ if (!overlay || !overlay.canShowPage())
+ return false;
+
+ if ((!rootPage || !rootPage.sticky) && overlay.parentPage)
+ this.showPageByName(overlay.parentPage.name, false);
+
+ if (!overlay.visible) {
+ overlay.visible = true;
+ if (overlay.didShowPage) overlay.didShowPage();
+ }
+
+ return true;
+ };
+
+ /**
+ * Returns whether or not an overlay is visible.
+ * @return {boolean} True if an overlay is visible.
+ * @private
+ */
+ OptionsPage.isOverlayVisible_ = function() {
+ return this.getVisibleOverlay_() != null;
+ };
+
+ /**
+ * Returns the currently visible overlay, or null if no page is visible.
+ * @return {OptionPage} The visible overlay.
+ */
+ OptionsPage.getVisibleOverlay_ = function() {
+ for (var name in this.registeredOverlayPages) {
+ var page = this.registeredOverlayPages[name];
+ if (page.visible)
+ return page;
+ }
+ return null;
+ };
+
+ /**
+ * Closes the visible overlay. Updates the history state after closing the
+ * overlay.
+ */
+ OptionsPage.closeOverlay = function() {
+ var overlay = this.getVisibleOverlay_();
+ if (!overlay)
+ return;
+
+ overlay.visible = false;
+ if (overlay.didClosePage) overlay.didClosePage();
+ this.updateHistoryState_();
+ };
+
+ /**
+ * Hides the visible overlay. Does not affect the history state.
+ * @private
+ */
+ OptionsPage.hideOverlay_ = function() {
+ var overlay = this.getVisibleOverlay_();
+ if (overlay)
+ overlay.visible = false;
+ };
+
+ /**
+ * Returns the topmost visible page (overlays excluded).
+ * @return {OptionPage} The topmost visible page aside any overlay.
+ * @private
+ */
+ OptionsPage.getTopmostVisibleNonOverlayPage_ = function() {
+ var topPage = null;
+ for (var name in this.registeredPages) {
+ var page = this.registeredPages[name];
+ if (page.visible &&
+ (!topPage || page.nestingLevel > topPage.nestingLevel))
+ topPage = page;
+ }
+
+ return topPage;
+ };
+
+ /**
+ * Returns the topmost visible page, or null if no page is visible.
+ * @return {OptionPage} The topmost visible page.
+ */
+ OptionsPage.getTopmostVisiblePage = function() {
+ // Check overlays first since they're top-most if visible.
+ return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_();
+ };
+
+ /**
+ * Closes the topmost open subpage, if any.
+ * @private
+ */
+ OptionsPage.closeTopSubPage_ = function() {
+ var topPage = this.getTopmostVisiblePage();
+ if (topPage && !topPage.isOverlay && topPage.parentPage) {
+ if (topPage.willHidePage)
+ topPage.willHidePage();
+ topPage.visible = false;
+ }
+
+ this.updateHistoryState_();
+ };
+
+ /**
+ * Closes all subpages below the given level.
+ * @param {number} level The nesting level to close below.
+ */
+ OptionsPage.closeSubPagesToLevel = function(level) {
+ var topPage = this.getTopmostVisiblePage();
+ while (topPage && topPage.nestingLevel > level) {
+ if (topPage.willHidePage)
+ topPage.willHidePage();
+ topPage.visible = false;
+ topPage = topPage.parentPage;
+ }
+
+ this.updateHistoryState_();
+ };
+
+ /**
+ * Updates managed banner visibility state based on the topmost page.
+ */
+ OptionsPage.updateManagedBannerVisibility = function() {
+ var topPage = this.getTopmostVisiblePage();
+ if (topPage)
+ topPage.updateManagedBannerVisibility();
+ };
+
+ /**
+ * Shows the tab contents for the given navigation tab.
+ * @param {!Element} tab The tab that the user clicked.
+ */
+ OptionsPage.showTab = function(tab) {
+ // Search parents until we find a tab, or the nav bar itself. This allows
+ // tabs to have child nodes, e.g. labels in separately-styled spans.
+ while (tab && !tab.classList.contains('subpages-nav-tabs') &&
+ !tab.classList.contains('tab')) {
+ tab = tab.parentNode;
+ }
+ if (!tab || !tab.classList.contains('tab'))
+ return;
+
+ // Find tab bar of the tab.
+ var tabBar = tab;
+ while (tabBar && !tabBar.classList.contains('subpages-nav-tabs')) {
+ tabBar = tabBar.parentNode;
+ }
+ if (!tabBar)
+ return;
+
+ if (tabBar.activeNavTab != null) {
+ tabBar.activeNavTab.classList.remove('active-tab');
+ $(tabBar.activeNavTab.getAttribute('tab-contents')).classList.
+ remove('active-tab-contents');
+ }
+
+ tab.classList.add('active-tab');
+ $(tab.getAttribute('tab-contents')).classList.add('active-tab-contents');
+ tabBar.activeNavTab = tab;
+ };
+
+ /**
+ * Registers new options page.
+ * @param {OptionsPage} page Page to register.
+ */
+ OptionsPage.register = function(page) {
+ this.registeredPages[page.name.toLowerCase()] = page;
+ // Create and add new page <li> element to navbar.
+ var pageNav = document.createElement('li');
+ pageNav.id = page.name + 'PageNav';
+ pageNav.className = 'navbar-item';
+ pageNav.setAttribute('pageName', page.name);
+ pageNav.setAttribute('role', 'tab');
+ pageNav.textContent = page.pageDiv.querySelector('h1').textContent;
+ pageNav.tabIndex = -1;
+ pageNav.onclick = function(event) {
+ OptionsPage.navigateToPage(this.getAttribute('pageName'));
+ };
+ pageNav.onkeydown = function(event) {
+ if ((event.keyCode == 37 || event.keyCode==38) &&
+ this.previousSibling && this.previousSibling.onkeydown) {
+ // Left and up arrow moves back one tab.
+ OptionsPage.navigateToPage(
+ this.previousSibling.getAttribute('pageName'));
+ this.previousSibling.focus();
+ } else if ((event.keyCode == 39 || event.keyCode == 40) &&
+ this.nextSibling) {
+ // Right and down arrows move forward one tab.
+ OptionsPage.navigateToPage(this.nextSibling.getAttribute('pageName'));
+ this.nextSibling.focus();
+ }
+ };
+ pageNav.onkeypress = function(event) {
+ // Enter or space
+ if (event.keyCode == 13 || event.keyCode == 32) {
+ OptionsPage.navigateToPage(this.getAttribute('pageName'));
+ }
+ };
+ var navbar = $('navbar');
+ navbar.appendChild(pageNav);
+ page.tab = pageNav;
+ page.initializePage();
+ };
+
+ /**
+ * Find an enclosing section for an element if it exists.
+ * @param {Element} element Element to search.
+ * @return {OptionPage} The section element, or null.
+ * @private
+ */
+ OptionsPage.findSectionForNode_ = function(node) {
+ while (node = node.parentNode) {
+ if (node.nodeName == 'SECTION')
+ return node;
+ }
+ return null;
+ };
+
+ /**
+ * Registers a new Sub-page.
+ * @param {OptionsPage} subPage Sub-page to register.
+ * @param {OptionsPage} parentPage Associated parent page for this page.
+ * @param {Array} associatedControls Array of control elements that lead to
+ * this sub-page. The first item is typically a button in a root-level
+ * page. There may be additional buttons for nested sub-pages.
+ */
+ OptionsPage.registerSubPage = function(subPage,
+ parentPage,
+ associatedControls) {
+ this.registeredPages[subPage.name.toLowerCase()] = subPage;
+ subPage.parentPage = parentPage;
+ if (associatedControls) {
+ subPage.associatedControls = associatedControls;
+ if (associatedControls.length) {
+ subPage.associatedSection =
+ this.findSectionForNode_(associatedControls[0]);
+ }
+ }
+ subPage.tab = undefined;
+ subPage.initializePage();
+ };
+
+ /**
+ * Registers a new Overlay page.
+ * @param {OptionsPage} overlay Overlay to register.
+ * @param {OptionsPage} parentPage Associated parent page for this overlay.
+ * @param {Array} associatedControls Array of control elements associated with
+ * this page.
+ */
+ OptionsPage.registerOverlay = function(overlay,
+ parentPage,
+ associatedControls) {
+ this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay;
+ overlay.parentPage = parentPage;
+ if (associatedControls) {
+ overlay.associatedControls = associatedControls;
+ if (associatedControls.length) {
+ overlay.associatedSection =
+ this.findSectionForNode_(associatedControls[0]);
+ }
+ }
+
+ // Reverse the button strip for views. See the documentation of
+ // reverseButtonStrip_() for an explanation of why this is necessary.
+ if (cr.isViews)
+ this.reverseButtonStrip_(overlay);
+
+ overlay.tab = undefined;
+ overlay.isOverlay = true;
+ overlay.initializePage();
+ };
+
+ /**
+ * Reverses the child elements of a button strip. This is necessary because
+ * WebKit does not alter the tab order for elements that are visually reversed
+ * using -webkit-box-direction: reverse, and the button order is reversed for
+ * views. See https://bugs.webkit.org/show_bug.cgi?id=62664 for more
+ * information.
+ * @param {Object} overlay The overlay containing the button strip to reverse.
+ * @private
+ */
+ OptionsPage.reverseButtonStrip_ = function(overlay) {
+ var buttonStrips = overlay.pageDiv.querySelectorAll('.button-strip');
+
+ // Reverse all button-strips in the overlay.
+ for (var j = 0; j < buttonStrips.length; j++) {
+ var buttonStrip = buttonStrips[j];
+
+ var childNodes = buttonStrip.childNodes;
+ for (var i = childNodes.length - 1; i >= 0; i--)
+ buttonStrip.appendChild(childNodes[i]);
+ }
+ };
+
+ /**
+ * Callback for window.onpopstate.
+ * @param {Object} data State data pushed into history.
+ */
+ OptionsPage.setState = function(data) {
+ if (data && data.pageName) {
+ // It's possible an overlay may be the last top-level page shown.
+ if (this.isOverlayVisible_() &&
+ !this.registeredOverlayPages[data.pageName.toLowerCase()]) {
+ this.hideOverlay_();
+ }
+
+ this.showPageByName(data.pageName, false);
+ }
+ };
+
+ /**
+ * Callback for window.onbeforeunload. Used to notify overlays that they will
+ * be closed.
+ */
+ OptionsPage.willClose = function() {
+ var overlay = this.getVisibleOverlay_();
+ if (overlay && overlay.didClosePage)
+ overlay.didClosePage();
+ };
+
+ /**
+ * Freezes/unfreezes the scroll position of given level's page container.
+ * @param {boolean} freeze Whether the page should be frozen.
+ * @param {number} level The level to freeze/unfreeze.
+ * @private
+ */
+ OptionsPage.setPageFrozenAtLevel_ = function(freeze, level) {
+ var container = level == 0 ? $('page-container')
+ : $('subpage-sheet-container-' + level);
+
+ if (container.classList.contains('frozen') == freeze)
+ return;
+
+ if (freeze) {
+ // Lock the width, since auto width computation may change.
+ container.style.width = window.getComputedStyle(container).width;
+ container.oldScrollTop = document.body.scrollTop;
+ container.classList.add('frozen');
+ var verticalPosition =
+ container.getBoundingClientRect().top - container.oldScrollTop;
+ container.style.top = verticalPosition + 'px';
+ this.updateFrozenElementHorizontalPosition_(container);
+ } else {
+ container.classList.remove('frozen');
+ container.style.top = '';
+ container.style.left = '';
+ container.style.right = '';
+ container.style.width = '';
+ }
+ };
+
+ /**
+ * Freezes/unfreezes the scroll position of visible pages based on the current
+ * page stack.
+ */
+ OptionsPage.updatePageFreezeStates = function() {
+ var topPage = OptionsPage.getTopmostVisiblePage();
+ if (!topPage)
+ return;
+ var nestingLevel = topPage.isOverlay ? 100 : topPage.nestingLevel;
+ for (var i = 0; i <= SUBPAGE_SHEET_COUNT; i++) {
+ this.setPageFrozenAtLevel_(i < nestingLevel, i);
+ }
+ };
+
+ /**
+ * Initializes the complete options page. This will cause all C++ handlers to
+ * be invoked to do final setup.
+ */
+ OptionsPage.initialize = function() {
+ chrome.send('coreOptionsInitialize');
+ this.initialized_ = true;
+
+ document.addEventListener('scroll', this.handleScroll_.bind(this));
+ window.addEventListener('resize', this.handleResize_.bind(this));
+
+ if (!document.documentElement.classList.contains('hide-menu')) {
+ // Close subpages if the user clicks on the html body. Listen in the
+ // capturing phase so that we can stop the click from doing anything.
+ document.body.addEventListener('click',
+ this.bodyMouseEventHandler_.bind(this),
+ true);
+ // We also need to cancel mousedowns on non-subpage content.
+ document.body.addEventListener('mousedown',
+ this.bodyMouseEventHandler_.bind(this),
+ true);
+
+ var self = this;
+ // Hook up the close buttons.
+ subpageCloseButtons = document.querySelectorAll('.close-subpage');
+ for (var i = 0; i < subpageCloseButtons.length; i++) {
+ subpageCloseButtons[i].onclick = function() {
+ self.closeTopSubPage_();
+ };
+ };
+
+ // Install handler for key presses.
+ document.addEventListener('keydown',
+ this.keyDownEventHandler_.bind(this));
+
+ document.addEventListener('focus', this.manageFocusChange_.bind(this),
+ true);
+ }
+
+ // Calculate and store the horizontal locations of elements that may be
+ // frozen later.
+ var sidebarWidth =
+ parseInt(window.getComputedStyle($('mainview')).webkitPaddingStart, 10);
+ $('page-container').horizontalOffset = sidebarWidth +
+ parseInt(window.getComputedStyle(
+ $('mainview-content')).webkitPaddingStart, 10);
+ for (var level = 1; level <= SUBPAGE_SHEET_COUNT; level++) {
+ var containerId = 'subpage-sheet-container-' + level;
+ $(containerId).horizontalOffset = sidebarWidth;
+ }
+ $('subpage-backdrop').horizontalOffset = sidebarWidth;
+ // Trigger the resize handler manually to set the initial state.
+ this.handleResize_(null);
+ };
+
+ /**
+ * Does a bounds check for the element on the given x, y client coordinates.
+ * @param {Element} e The DOM element.
+ * @param {number} x The client X to check.
+ * @param {number} y The client Y to check.
+ * @return {boolean} True if the point falls within the element's bounds.
+ * @private
+ */
+ OptionsPage.elementContainsPoint_ = function(e, x, y) {
+ var clientRect = e.getBoundingClientRect();
+ return x >= clientRect.left && x <= clientRect.right &&
+ y >= clientRect.top && y <= clientRect.bottom;
+ };
+
+ /**
+ * Called when focus changes; ensures that focus doesn't move outside
+ * the topmost subpage/overlay.
+ * @param {Event} e The focus change event.
+ * @private
+ */
+ OptionsPage.manageFocusChange_ = function(e) {
+ var focusableItemsRoot;
+ var topPage = this.getTopmostVisiblePage();
+ if (!topPage)
+ return;
+
+ if (topPage.isOverlay) {
+ // If an overlay is visible, that defines the tab loop.
+ focusableItemsRoot = topPage.pageDiv;
+ } else {
+ // If a subpage is visible, use its parent as the tab loop constraint.
+ // (The parent is used because it contains the close button.)
+ if (topPage.nestingLevel > 0)
+ focusableItemsRoot = topPage.pageDiv.parentNode;
+ }
+
+ if (focusableItemsRoot && !focusableItemsRoot.contains(e.target))
+ topPage.focusFirstElement();
+ };
+
+ /**
+ * Called when the page is scrolled; moves elements that are position:fixed
+ * but should only behave as if they are fixed for vertical scrolling.
+ * @param {Event} e The scroll event.
+ * @private
+ */
+ OptionsPage.handleScroll_ = function(e) {
+ var scrollHorizontalOffset = document.body.scrollLeft;
+ // position:fixed doesn't seem to work for horizontal scrolling in RTL mode,
+ // so only adjust in LTR mode (where scroll values will be positive).
+ if (scrollHorizontalOffset >= 0) {
+ $('navbar-container').style.left = -scrollHorizontalOffset + 'px';
+ var subpageBackdrop = $('subpage-backdrop');
+ subpageBackdrop.style.left = subpageBackdrop.horizontalOffset -
+ scrollHorizontalOffset + 'px';
+ this.updateAllFrozenElementPositions_();
+ }
+ };
+
+ /**
+ * Updates all frozen pages to match the horizontal scroll position.
+ * @private
+ */
+ OptionsPage.updateAllFrozenElementPositions_ = function() {
+ var frozenElements = document.querySelectorAll('.frozen');
+ for (var i = 0; i < frozenElements.length; i++) {
+ this.updateFrozenElementHorizontalPosition_(frozenElements[i]);
+ }
+ };
+
+ /**
+ * Updates the given frozen element to match the horizontal scroll position.
+ * @param {HTMLElement} e The frozen element to update
+ * @private
+ */
+ OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) {
+ if (document.documentElement.dir == 'rtl')
+ e.style.right = e.horizontalOffset + 'px';
+ else
+ e.style.left = e.horizontalOffset - document.body.scrollLeft + 'px';
+ };
+
+ /**
+ * Called when the page is resized; adjusts the size of elements that depend
+ * on the veiwport.
+ * @param {Event} e The resize event.
+ * @private
+ */
+ OptionsPage.handleResize_ = function(e) {
+ // Set an explicit height equal to the viewport on all the subpage
+ // containers shorter than the viewport. This is used instead of
+ // min-height: 100% so that there is an explicit height for the subpages'
+ // min-height: 100%.
+ var viewportHeight = document.documentElement.clientHeight;
+ var subpageContainers =
+ document.querySelectorAll('.subpage-sheet-container');
+ for (var i = 0; i < subpageContainers.length; i++) {
+ if (subpageContainers[i].scrollHeight > viewportHeight)
+ subpageContainers[i].style.removeProperty('height');
+ else
+ subpageContainers[i].style.height = viewportHeight + 'px';
+ }
+ };
+
+ /**
+ * A function to handle mouse events (mousedown or click) on the html body by
+ * closing subpages and/or stopping event propagation.
+ * @return {Event} a mousedown or click event.
+ * @private
+ */
+ OptionsPage.bodyMouseEventHandler_ = function(event) {
+ // Do nothing if a subpage isn't showing.
+ var topPage = this.getTopmostVisiblePage();
+ if (!topPage || topPage.isOverlay || !topPage.parentPage)
+ return;
+
+ // Don't close subpages if a user is clicking in a select element.
+ // This is necessary because WebKit sends click events with strange
+ // coordinates when a user selects a new entry in a select element.
+ // See: http://crbug.com/87199
+ if (event.srcElement.nodeName == 'SELECT')
+ return;
+
+ // Do nothing if the client coordinates are not within the source element.
+ // This occurs if the user toggles a checkbox by pressing spacebar.
+ // This is a workaround to prevent keyboard events from closing the window.
+ // See: crosbug.com/15678
+ if (event.clientX == -document.body.scrollLeft &&
+ event.clientY == -document.body.scrollTop) {
+ return;
+ }
+
+ // Don't interfere with navbar clicks.
+ if ($('navbar').contains(event.target))
+ return;
+
+ // Figure out which page the click happened in.
+ for (var level = topPage.nestingLevel; level >= 0; level--) {
+ var clickIsWithinLevel = level == 0 ? true :
+ OptionsPage.elementContainsPoint_(
+ $('subpage-sheet-' + level), event.clientX, event.clientY);
+
+ if (!clickIsWithinLevel)
+ continue;
+
+ // Event was within the topmost page; do nothing.
+ if (topPage.nestingLevel == level)
+ return;
+
+ // Block propgation of both clicks and mousedowns, but only close subpages
+ // on click.
+ if (event.type == 'click')
+ this.closeSubPagesToLevel(level);
+ event.stopPropagation();
+ event.preventDefault();
+ return;
+ }
+ };
+
+ /**
+ * A function to handle key press events.
+ * @return {Event} a keydown event.
+ * @private
+ */
+ OptionsPage.keyDownEventHandler_ = function(event) {
+ // Close the top overlay or sub-page on esc.
+ if (event.keyCode == 27) { // Esc
+ if (this.isOverlayVisible_())
+ this.closeOverlay();
+ else
+ this.closeTopSubPage_();
+ }
+ };
+
+ OptionsPage.setClearPluginLSODataEnabled = function(enabled) {
+ if (enabled) {
+ document.documentElement.setAttribute(
+ 'flashPluginSupportsClearSiteData', '');
+ } else {
+ document.documentElement.removeAttribute(
+ 'flashPluginSupportsClearSiteData');
+ }
+ };
+
+ /**
+ * Re-initializes the C++ handlers if necessary. This is called if the
+ * handlers are torn down and recreated but the DOM may not have been (in
+ * which case |initialize| won't be called again). If |initialize| hasn't been
+ * called, this does nothing (since it will be later, once the DOM has
+ * finished loading).
+ */
+ OptionsPage.reinitializeCore = function() {
+ if (this.initialized_)
+ chrome.send('coreOptionsInitialize');
+ }
+
+ OptionsPage.prototype = {
+ __proto__: cr.EventTarget.prototype,
+
+ /**
+ * The parent page of this option page, or null for top-level pages.
+ * @type {OptionsPage}
+ */
+ parentPage: null,
+
+ /**
+ * The section on the parent page that is associated with this page.
+ * Can be null.
+ * @type {Element}
+ */
+ associatedSection: null,
+
+ /**
+ * An array of controls that are associated with this page. The first
+ * control should be located on a top-level page.
+ * @type {OptionsPage}
+ */
+ associatedControls: null,
+
+ /**
+ * Initializes page content.
+ */
+ initializePage: function() {},
+
+ /**
+ * Updates managed banner visibility state. This function iterates over
+ * all input fields of a window and if any of these is marked as managed
+ * it triggers the managed banner to be visible. The banner can be enforced
+ * being on through the managed flag of this class but it can not be forced
+ * being off if managed items exist.
+ */
+ updateManagedBannerVisibility: function() {
+ var bannerDiv = $('managed-prefs-banner');
+
+ var controlledByPolicy = false;
+ var controlledByExtension = false;
+ var inputElements = this.pageDiv.querySelectorAll('input[controlled-by]');
+ for (var i = 0, len = inputElements.length; i < len; i++) {
+ if (inputElements[i].controlledBy == 'policy')
+ controlledByPolicy = true;
+ else if (inputElements[i].controlledBy == 'extension')
+ controlledByExtension = true;
+ }
+ if (!controlledByPolicy && !controlledByExtension) {
+ bannerDiv.hidden = true;
+ } else {
+ bannerDiv.hidden = false;
+ var height = window.getComputedStyle(bannerDiv).height;
+ if (controlledByPolicy && !controlledByExtension) {
+ $('managed-prefs-text').textContent =
+ templateData.policyManagedPrefsBannerText;
+ } else if (!controlledByPolicy && controlledByExtension) {
+ $('managed-prefs-text').textContent =
+ templateData.extensionManagedPrefsBannerText;
+ } else if (controlledByPolicy && controlledByExtension) {
+ $('managed-prefs-text').textContent =
+ templateData.policyAndExtensionManagedPrefsBannerText;
+ }
+ }
+ },
+
+ /**
+ * Gets page visibility state.
+ */
+ get visible() {
+ return !this.pageDiv.hidden;
+ },
+
+ /**
+ * Sets page visibility.
+ */
+ set visible(visible) {
+ if ((this.visible && visible) || (!this.visible && !visible))
+ return;
+
+ this.setContainerVisibility_(visible);
+ if (visible) {
+ this.pageDiv.hidden = false;
+
+ if (this.tab) {
+ this.tab.classList.add('navbar-item-selected');
+ this.tab.setAttribute('aria-selected', 'true');
+ this.tab.tabIndex = 0;
+ }
+ } else {
+ this.pageDiv.hidden = true;
+
+ if (this.tab) {
+ this.tab.classList.remove('navbar-item-selected');
+ this.tab.setAttribute('aria-selected', 'false');
+ this.tab.tabIndex = -1;
+ }
+ }
+
+ OptionsPage.updatePageFreezeStates();
+
+ // The managed prefs banner is global, so after any visibility change
+ // update it based on the topmost page, not necessarily this page
+ // (e.g., if an ancestor is made visible after a child).
+ OptionsPage.updateManagedBannerVisibility();
+
+ // A subpage was shown or hidden.
+ if (!this.isOverlay && this.nestingLevel > 0) {
+ OptionsPage.updateSubpageBackdrop_();
+ OptionsPage.updateScrollPosition_();
+ }
+
+ cr.dispatchPropertyChange(this, 'visible', visible, !visible);
+ },
+
+ /**
+ * Shows or hides this page's container.
+ * @param {boolean} visible Whether the container should be visible or not.
+ * @private
+ */
+ setContainerVisibility_: function(visible) {
+ var container = null;
+ if (this.isOverlay) {
+ container = $('overlay');
+ } else {
+ var nestingLevel = this.nestingLevel;
+ if (nestingLevel > 0)
+ container = $('subpage-sheet-container-' + nestingLevel);
+ }
+ var isSubpage = !this.isOverlay;
+
+ if (!container)
+ return;
+
+ if (container.hidden != visible) {
+ if (visible) {
+ // If the container is set hidden and then immediately set visible
+ // again, the fadeCompleted_ callback would cause it to be erroneously
+ // hidden again. Removing the transparent tag avoids that.
+ container.classList.remove('transparent');
+ }
+ return;
+ }
+
+ if (visible) {
+ container.hidden = false;
+ if (isSubpage) {
+ var computedStyle = window.getComputedStyle(container);
+ container.style.WebkitPaddingStart =
+ parseInt(computedStyle.WebkitPaddingStart, 10) + 100 + 'px';
+ }
+ // Separate animating changes from the removal of display:none.
+ window.setTimeout(function() {
+ container.classList.remove('transparent');
+ if (isSubpage)
+ container.style.WebkitPaddingStart = '';
+ });
+ } else {
+ var self = this;
+ container.addEventListener('webkitTransitionEnd', function f(e) {
+ if (e.propertyName != 'opacity')
+ return;
+ container.removeEventListener('webkitTransitionEnd', f);
+ self.fadeCompleted_(container);
+ });
+ container.classList.add('transparent');
+ }
+ },
+
+ /**
+ * Called when a container opacity transition finishes.
+ * @param {HTMLElement} container The container element.
+ * @private
+ */
+ fadeCompleted_: function(container) {
+ if (container.classList.contains('transparent'))
+ container.hidden = true;
+ },
+
+ /**
+ * Focuses the first control on the page.
+ */
+ focusFirstElement: function() {
+ // Sets focus on the first interactive element in the page.
+ var focusElement =
+ this.pageDiv.querySelector('button, input, list, select');
+ if (focusElement)
+ focusElement.focus();
+ },
+
+ /**
+ * The nesting level of this page.
+ * @type {number} The nesting level of this page (0 for top-level page)
+ */
+ get nestingLevel() {
+ var level = 0;
+ var parent = this.parentPage;
+ while (parent) {
+ level++;
+ parent = parent.parentPage;
+ }
+ return level;
+ },
+
+ /**
+ * Whether the page is considered 'sticky', such that it will
+ * remain a top-level page even if sub-pages change.
+ * @type {boolean} True if this page is sticky.
+ */
+ get sticky() {
+ return false;
+ },
+
+ /**
+ * Checks whether this page is an ancestor of the given page in terms of
+ * subpage nesting.
+ * @param {OptionsPage} page
+ * @return {boolean} True if this page is nested under |page|
+ */
+ isAncestorOfPage: function(page) {
+ var parent = page.parentPage;
+ while (parent) {
+ if (parent == this)
+ return true;
+ parent = parent.parentPage;
+ }
+ return false;
+ },
+
+ /**
+ * Whether it should be possible to show the page.
+ * @return {boolean} True if the page should be shown
+ */
+ canShowPage: function() {
+ return true;
+ },
+ };
+
+ // Export
+ return {
+ OptionsPage: OptionsPage
+ };
+});
« no previous file with comments | « chrome/browser/resources/options2/options_page.css ('k') | chrome/browser/resources/options2/pack_extension_overlay.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698