summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/bluetooth_internals/page_manager.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/bluetooth_internals/page_manager.js')
-rw-r--r--chromium/chrome/browser/resources/bluetooth_internals/page_manager.js800
1 files changed, 800 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/bluetooth_internals/page_manager.js b/chromium/chrome/browser/resources/bluetooth_internals/page_manager.js
new file mode 100644
index 00000000000..bca2b6dbb76
--- /dev/null
+++ b/chromium/chrome/browser/resources/bluetooth_internals/page_manager.js
@@ -0,0 +1,800 @@
+// Copyright 2014 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('cr.ui.pageManager', function() {
+ /**
+ * PageManager contains a list of root Page and overlay Page objects and
+ * handles "navigation" by showing and hiding these pages and overlays. On
+ * initial load, PageManager can use the path to open the correct hierarchy
+ * of pages and overlay(s). Handlers for user events, like pressing buttons,
+ * can call into PageManager to open a particular overlay or cancel an
+ * existing overlay.
+ */
+ const PageManager = {
+ /**
+ * True if page is served from a dialog.
+ * @type {boolean}
+ */
+ isDialog: false,
+
+ /**
+ * Offset of page container in pixels. Uber pages that use the side menu
+ * can override this with the setter.
+ * @type {number}
+ */
+ horizontalOffset_: 23,
+
+ /**
+ * Root pages. Maps lower-case page names to the respective page object.
+ * @type {!Object<!cr.ui.pageManager.Page>}
+ */
+ registeredPages: {},
+
+ /**
+ * Pages which are meant to behave like modal dialogs. Maps lower-case
+ * overlay names to the respective overlay object.
+ * @type {!Object<!cr.ui.pageManager.Page>}
+ * @private
+ */
+ registeredOverlayPages: {},
+
+ /**
+ * Observers will be notified when opening and closing overlays.
+ * @type {!Array<!cr.ui.pageManager.PageManager.Observer>}
+ */
+ observers_: [],
+
+ /**
+ * Initializes the complete page.
+ * @param {cr.ui.pageManager.Page} defaultPage The page to be shown when no
+ * page is specified in the path.
+ */
+ initialize: function(defaultPage) {
+ this.defaultPage_ = defaultPage;
+
+ cr.ui.FocusOutlineManager.forDocument(document);
+ document.addEventListener('scroll', this.handleScroll_.bind(this));
+
+ // Trigger the scroll handler manually to set the initial state.
+ this.handleScroll_();
+
+ // Shake the dialog if the user clicks outside the dialog bounds.
+ const containers = /** @type {!NodeList<!HTMLElement>} */ (
+ document.querySelectorAll('body > .overlay'));
+ for (let i = 0; i < containers.length; i++) {
+ const overlay = containers[i];
+ cr.ui.overlay.setupOverlay(overlay);
+ overlay.addEventListener(
+ 'cancelOverlay', this.cancelOverlay.bind(this));
+ }
+
+ cr.ui.overlay.globalInitialization();
+ },
+
+ /**
+ * Registers new page.
+ * @param {!cr.ui.pageManager.Page} page Page to register.
+ */
+ register: function(page) {
+ this.registeredPages[page.name.toLowerCase()] = page;
+ page.initializePage();
+ },
+
+ /**
+ * Unregisters an existing page.
+ * @param {!cr.ui.pageManager.Page} page Page to unregister.
+ */
+ unregister: function(page) {
+ delete this.registeredPages[page.name.toLowerCase()];
+ },
+
+ /**
+ * Registers a new Overlay page.
+ * @param {!cr.ui.pageManager.Page} overlay Overlay to register.
+ * @param {cr.ui.pageManager.Page} parentPage Associated parent page for
+ * this overlay.
+ * @param {Array} associatedControls Array of control elements associated
+ * with this page.
+ */
+ 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]);
+ }
+
+ // Sanity check.
+ for (let i = 0; i < associatedControls.length; ++i) {
+ assert(associatedControls[i], 'Invalid element passed.');
+ }
+ }
+
+ overlay.tab = undefined;
+ overlay.isOverlay = true;
+
+ overlay.reverseButtonStrip();
+ overlay.initializePage();
+ },
+
+ /**
+ * Shows the default page.
+ * @param {boolean=} opt_updateHistory If we should update the history after
+ * showing the page (defaults to true).
+ */
+ showDefaultPage: function(opt_updateHistory) {
+ assert(
+ this.defaultPage_ instanceof cr.ui.pageManager.Page,
+ 'PageManager must be initialized with a default page.');
+ this.showPageByName(this.defaultPage_.name, opt_updateHistory);
+ },
+
+ /**
+ * Shows a registered page. This handles both root and overlay pages.
+ * @param {string} pageName Page name.
+ * @param {boolean=} opt_updateHistory If we should update the history after
+ * showing the page (defaults to true).
+ * @param {Object=} opt_propertyBag An optional bag of properties including
+ * replaceState (if history state should be replaced instead of pushed).
+ * hash (a hash state to attach to the page).
+ */
+ showPageByName: function(pageName, opt_updateHistory, opt_propertyBag) {
+ opt_updateHistory = opt_updateHistory !== false;
+ opt_propertyBag = opt_propertyBag || {};
+
+ // If a bubble is currently being shown, hide it.
+ this.hideBubble();
+
+ // Find the currently visible root-level page.
+ let rootPage = null;
+ for (const name in this.registeredPages) {
+ const page = this.registeredPages[name];
+ if (page.visible && !page.parentPage) {
+ rootPage = page;
+ break;
+ }
+ }
+
+ // Find the target page.
+ let targetPage = this.registeredPages[pageName.toLowerCase()];
+ if (!targetPage || !targetPage.canShowPage()) {
+ // If it's not a page, try it as an overlay.
+ const hash = opt_propertyBag.hash || '';
+ if (!targetPage && this.showOverlay_(pageName, hash, rootPage)) {
+ if (opt_updateHistory) {
+ this.updateHistoryState_(!!opt_propertyBag.replaceState);
+ }
+ this.updateTitle_();
+ return;
+ }
+ targetPage = this.defaultPage_;
+ }
+
+ pageName = targetPage.name.toLowerCase();
+ const targetPageWasVisible = targetPage.visible;
+
+ // Determine if the root page is 'sticky', meaning that it
+ // shouldn't change when showing an overlay. This can happen for special
+ // pages like Search.
+ const isRootPageLocked =
+ rootPage && rootPage.sticky && targetPage.parentPage;
+
+ // Notify pages if they will be hidden.
+ this.forEachPage_(!isRootPageLocked, function(page) {
+ if (page.name != pageName && !this.isAncestorOfPage(page, targetPage)) {
+ page.willHidePage();
+ }
+ });
+
+ // Update the page's hash.
+ targetPage.hash = opt_propertyBag.hash || '';
+
+ // Update visibilities to show only the hierarchy of the target page.
+ this.forEachPage_(!isRootPageLocked, function(page) {
+ page.visible =
+ page.name == pageName || this.isAncestorOfPage(page, targetPage);
+ });
+
+ // Update the history and current location.
+ if (opt_updateHistory) {
+ this.updateHistoryState_(!!opt_propertyBag.replaceState);
+ }
+
+ // Update focus if any other control was focused on the previous page,
+ // or the previous page is not known.
+ if (document.activeElement != document.body &&
+ (!rootPage || rootPage.pageDiv.contains(document.activeElement))) {
+ targetPage.focus();
+ }
+
+ // Notify pages if they were shown.
+ this.forEachPage_(!isRootPageLocked, function(page) {
+ if (!targetPageWasVisible &&
+ (page.name == pageName ||
+ this.isAncestorOfPage(page, targetPage))) {
+ page.didShowPage();
+ }
+ });
+
+ // If the target page was already visible, notify it that its hash
+ // changed externally.
+ if (targetPageWasVisible) {
+ targetPage.didChangeHash();
+ }
+
+ // Update the document title. Do this after didShowPage was called, in
+ // case a page decides to change its title.
+ this.updateTitle_();
+ },
+
+ /**
+ * Returns the name of the page from the current path.
+ * @return {string} Name of the page specified by the current path.
+ */
+ getPageNameFromPath: function() {
+ const path = location.pathname;
+ if (path.length <= 1) {
+ return this.defaultPage_.name;
+ }
+
+ // Skip starting slash and remove trailing slash (if any).
+ return path.slice(1).replace(/\/$/, '');
+ },
+
+ /**
+ * Gets the level of the page. Root pages (e.g., BrowserOptions) are at
+ * level 0.
+ * @return {number} How far down this page is from the root page.
+ */
+ getNestingLevel: function(page) {
+ let level = 0;
+ let parent = page.parentPage;
+ while (parent) {
+ level++;
+ parent = parent.parentPage;
+ }
+ return level;
+ },
+
+ /**
+ * Checks whether one page is an ancestor of the other page in terms of
+ * subpage nesting.
+ * @param {cr.ui.pageManager.Page} potentialAncestor Potential ancestor.
+ * @param {cr.ui.pageManager.Page} potentialDescendent Potential descendent.
+ * @return {boolean} True if |potentialDescendent| is nested under
+ * |potentialAncestor|.
+ */
+ isAncestorOfPage: function(potentialAncestor, potentialDescendent) {
+ let parent = potentialDescendent.parentPage;
+ while (parent) {
+ if (parent == potentialAncestor) {
+ return true;
+ }
+ parent = parent.parentPage;
+ }
+ return false;
+ },
+
+ /**
+ * Returns true if the page is a direct descendent of a root page, or if
+ * the page is considered always on top. Doesn't consider visibility.
+ * @param {cr.ui.pageManager.Page} page Page to check.
+ * @return {boolean} True if |page| is a top-level overlay.
+ */
+ isTopLevelOverlay: function(page) {
+ return page.isOverlay &&
+ (page.alwaysOnTop || this.getNestingLevel(page) == 1);
+ },
+
+ /**
+ * Called when an page is shown or hidden to update the root page
+ * based on the page's new visibility.
+ * @param {cr.ui.pageManager.Page} page The page being made visible or
+ * invisible.
+ */
+ onPageVisibilityChanged: function(page) {
+ this.updateRootPageFreezeState();
+
+ for (let i = 0; i < this.observers_.length; ++i) {
+ this.observers_[i].onPageVisibilityChanged(page);
+ }
+
+ if (!page.visible && this.isTopLevelOverlay(page)) {
+ this.updateScrollPosition_();
+ }
+ },
+
+ /**
+ * Called when a page's hash changes. If the page is the topmost visible
+ * page, the history state is updated.
+ * @param {cr.ui.pageManager.Page} page The page whose hash has changed.
+ */
+ onPageHashChanged: function(page) {
+ if (page == this.getTopmostVisiblePage()) {
+ this.updateHistoryState_(false);
+ }
+ },
+
+ /**
+ * Returns the topmost visible page, or null if no page is visible.
+ * @return {cr.ui.pageManager.Page} The topmost visible page.
+ */
+ getTopmostVisiblePage: function() {
+ // Check overlays first since they're top-most if visible.
+ return this.getVisibleOverlay_() ||
+ this.getTopmostVisibleNonOverlayPage_();
+ },
+
+ /**
+ * Closes the visible overlay. Updates the history state after closing the
+ * overlay.
+ */
+ closeOverlay: function() {
+ const overlay = this.getVisibleOverlay_();
+ if (!overlay) {
+ return;
+ }
+
+ overlay.visible = false;
+ overlay.didClosePage();
+
+ this.updateHistoryState_(false);
+ this.updateTitle_();
+
+ this.restoreLastFocusedElement_();
+ },
+
+ /**
+ * Closes all overlays and updates the history after each closed overlay.
+ */
+ closeAllOverlays: function() {
+ while (this.isOverlayVisible_()) {
+ this.closeOverlay();
+ }
+ },
+
+ /**
+ * Cancels (closes) the overlay, due to the user pressing <Esc>.
+ */
+ cancelOverlay: function() {
+ // Blur the active element to ensure any changed pref value is saved.
+ document.activeElement.blur();
+ const overlay = this.getVisibleOverlay_();
+ if (!overlay) {
+ return;
+ }
+ // Let the overlay handle the <Esc> if it wants to.
+ if (overlay.handleCancel) {
+ overlay.handleCancel();
+ this.restoreLastFocusedElement_();
+ } else {
+ this.closeOverlay();
+ }
+ },
+
+ /**
+ * Shows an informational bubble displaying |content| and pointing at the
+ * |target| element. If |content| has focusable elements, they join the
+ * current page's tab order as siblings of |domSibling|.
+ * @param {HTMLDivElement} content The content of the bubble.
+ * @param {HTMLElement} target The element at which the bubble points.
+ * @param {HTMLElement} domSibling The element after which the bubble is
+ * added to the DOM.
+ * @param {cr.ui.ArrowLocation} location The arrow location.
+ */
+ showBubble: function(content, target, domSibling, location) {
+ this.hideBubble();
+
+ const bubble = new cr.ui.AutoCloseBubble;
+ bubble.anchorNode = target;
+ bubble.domSibling = domSibling;
+ bubble.arrowLocation = location;
+ bubble.content = content;
+ bubble.show();
+ this.bubble_ = bubble;
+ },
+
+ /**
+ * Hides the currently visible bubble, if any.
+ */
+ hideBubble: function() {
+ if (this.bubble_) {
+ this.bubble_.hide();
+ }
+ },
+
+ /**
+ * Returns the currently visible bubble, or null if no bubble is visible.
+ * @return {cr.ui.AutoCloseBubble} The bubble currently being shown.
+ */
+ getVisibleBubble: function() {
+ const bubble = this.bubble_;
+ return bubble && !bubble.hidden ? bubble : null;
+ },
+
+ /**
+ * Callback for window.onpopstate to handle back/forward navigations.
+ * @param {string} pageName The current page name.
+ * @param {string} hash The hash to pass into the page.
+ * @param {Object} data State data pushed into history.
+ */
+ setState: function(pageName, hash, data) {
+ const currentOverlay = this.getVisibleOverlay_();
+ const lowercaseName = pageName.toLowerCase();
+ const newPage = this.registeredPages[lowercaseName] ||
+ this.registeredOverlayPages[lowercaseName] || this.defaultPage_;
+ if (currentOverlay && !this.isAncestorOfPage(currentOverlay, newPage)) {
+ currentOverlay.visible = false;
+ currentOverlay.didClosePage();
+ }
+ this.showPageByName(pageName, false, {hash: hash});
+ },
+
+
+ /**
+ * Whether the page is still loading (i.e. onload hasn't finished running).
+ * @return {boolean} Whether the page is still loading.
+ */
+ isLoading: function() {
+ return document.documentElement.classList.contains('loading');
+ },
+
+ /**
+ * Callback for window.onbeforeunload. Used to notify overlays that they
+ * will be closed.
+ */
+ willClose: function() {
+ const overlay = this.getVisibleOverlay_();
+ if (overlay) {
+ overlay.didClosePage();
+ }
+ },
+
+ /**
+ * Freezes/unfreezes the scroll position of the root page based on the
+ * current page stack.
+ */
+ updateRootPageFreezeState: function() {
+ const topPage = this.getTopmostVisiblePage();
+ if (topPage) {
+ this.setRootPageFrozen_(topPage.isOverlay);
+ }
+ },
+
+ /**
+ * Change the horizontal offset used to reposition elements while showing an
+ * overlay from the default.
+ */
+ set horizontalOffset(value) {
+ this.horizontalOffset_ = value;
+ },
+
+ /**
+ * @param {!cr.ui.pageManager.PageManager.Observer} observer The observer to
+ * register.
+ */
+ addObserver: function(observer) {
+ this.observers_.push(observer);
+ },
+
+ /**
+ * Shows a registered overlay page. Does not update history.
+ * @param {string} overlayName Page name.
+ * @param {string} hash The hash state to associate with the overlay.
+ * @param {cr.ui.pageManager.Page} rootPage The currently visible root-level
+ * page.
+ * @return {boolean} Whether we showed an overlay.
+ * @private
+ */
+ showOverlay_: function(overlayName, hash, rootPage) {
+ const overlay = this.registeredOverlayPages[overlayName.toLowerCase()];
+ if (!overlay || !overlay.canShowPage()) {
+ return false;
+ }
+
+ const focusOutlineManager =
+ cr.ui.FocusOutlineManager.forDocument(document);
+
+ // Save the currently focused element in the page for restoration later.
+ const currentPage = this.getTopmostVisiblePage();
+ if (currentPage && focusOutlineManager.visible) {
+ currentPage.lastFocusedElement = document.activeElement;
+ }
+
+ if ((!rootPage || !rootPage.sticky) && overlay.parentPage &&
+ !overlay.parentPage.visible) {
+ this.showPageByName(overlay.parentPage.name, false);
+ }
+
+ overlay.hash = hash;
+ if (!overlay.visible) {
+ overlay.visible = true;
+ overlay.didShowPage();
+ } else {
+ overlay.didChangeHash();
+ }
+
+ if (focusOutlineManager.visible) {
+ overlay.focus();
+ }
+
+ if (!overlay.pageDiv.contains(document.activeElement)) {
+ document.activeElement.blur();
+ }
+
+ if ($('search-field') && $('search-field').value == '') {
+ const section = overlay.associatedSection;
+ if (section) {
+ /** @suppress {checkTypes|checkVars} */
+ (function() {
+ options.BrowserOptions.scrollToSection(section);
+ })();
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Returns whether or not an overlay is visible.
+ * @return {boolean} True if an overlay is visible.
+ * @private
+ */
+ isOverlayVisible_: function() {
+ return this.getVisibleOverlay_() != null;
+ },
+
+ /**
+ * Returns the currently visible overlay, or null if no page is visible.
+ * @return {cr.ui.pageManager.Page} The visible overlay.
+ * @private
+ */
+ getVisibleOverlay_: function() {
+ let topmostPage = null;
+ for (const name in this.registeredOverlayPages) {
+ const page = this.registeredOverlayPages[name];
+ if (!page.visible) {
+ continue;
+ }
+
+ if (page.alwaysOnTop) {
+ return page;
+ }
+
+ if (!topmostPage ||
+ this.getNestingLevel(page) > this.getNestingLevel(topmostPage)) {
+ topmostPage = page;
+ }
+ }
+ return topmostPage;
+ },
+
+ /**
+ * Returns the topmost visible page (overlays excluded).
+ * @return {cr.ui.pageManager.Page} The topmost visible page aside from any
+ * overlays.
+ * @private
+ */
+ getTopmostVisibleNonOverlayPage_: function() {
+ for (const name in this.registeredPages) {
+ const page = this.registeredPages[name];
+ if (page.visible) {
+ return page;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Scrolls the page to the correct position (the top when opening an
+ * overlay, or the old scroll position a previously hidden overlay
+ * becomes visible).
+ * @private
+ */
+ updateScrollPosition_: function() {
+ const container = $('page-container');
+ const scrollTop = container.oldScrollTop || 0;
+ container.oldScrollTop = undefined;
+ window.scroll(scrollLeftForDocument(document), scrollTop);
+ },
+
+ /**
+ * Updates the title to the title of the current page, or of the topmost
+ * visible page with a non-empty title.
+ * @private
+ */
+ updateTitle_: function() {
+ let page = this.getTopmostVisiblePage();
+ while (page) {
+ if (page.title) {
+ for (let i = 0; i < this.observers_.length; ++i) {
+ this.observers_[i].updateTitle(page.title);
+ }
+ return;
+ }
+ page = page.parentPage;
+ }
+ },
+
+ /**
+ * Constructs a new path to push onto the history stack, using observers
+ * to update the history.
+ * @param {boolean} replace If true, handlers should replace the current
+ * history event rather than create new ones.
+ * @private
+ */
+ updateHistoryState_: function(replace) {
+ if (this.isDialog) {
+ return;
+ }
+
+ const page = this.getTopmostVisiblePage();
+ let path = window.location.pathname + window.location.hash;
+ if (path) {
+ // Remove trailing slash.
+ path = path.slice(1).replace(/\/(?:#|$)/, '');
+ }
+
+ // If the page is already in history (the user may have clicked the same
+ // link twice, or this is the initial load), do nothing.
+ const newPath = (page == this.defaultPage_ ? '' : page.name) + page.hash;
+ if (path == newPath) {
+ return;
+ }
+
+ for (let i = 0; i < this.observers_.length; ++i) {
+ this.observers_[i].updateHistory(newPath, replace);
+ }
+ },
+
+ /**
+ * Restores the last focused element on a given page.
+ * @private
+ */
+ restoreLastFocusedElement_: function() {
+ const currentPage = this.getTopmostVisiblePage();
+
+ if (!currentPage.lastFocusedElement) {
+ return;
+ }
+
+ if (cr.ui.FocusOutlineManager.forDocument(document).visible) {
+ currentPage.lastFocusedElement.focus();
+ }
+
+ currentPage.lastFocusedElement = null;
+ },
+
+ /**
+ * Find an enclosing section for an element if it exists.
+ * @param {Node} node Element to search.
+ * @return {Node} The section element, or null.
+ * @private
+ */
+ findSectionForNode_: function(node) {
+ while (node = node.parentNode) {
+ if (node.nodeName == 'SECTION') {
+ return node;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Freezes/unfreezes the scroll position of the root page container.
+ * @param {boolean} freeze Whether the page should be frozen.
+ * @private
+ */
+ setRootPageFrozen_: function(freeze) {
+ const container = $('page-container');
+ 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 = scrollTopForDocument(document);
+ container.classList.add('frozen');
+ const 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 = '';
+ }
+ },
+
+ /**
+ * Called when the page is scrolled; moves elements that are position:fixed
+ * but should only behave as if they are fixed for vertical scrolling.
+ * @private
+ */
+ handleScroll_: function() {
+ this.updateAllFrozenElementPositions_();
+ },
+
+ /**
+ * Updates all frozen pages to match the horizontal scroll position.
+ * @private
+ */
+ updateAllFrozenElementPositions_: function() {
+ const frozenElements = document.querySelectorAll('.frozen');
+ for (let 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
+ */
+ updateFrozenElementHorizontalPosition_: function(e) {
+ if (isRTL()) {
+ e.style.right = this.horizontalOffset + 'px';
+ } else {
+ const scrollLeft = scrollLeftForDocument(document);
+ e.style.left = this.horizontalOffset - scrollLeft + 'px';
+ }
+ },
+
+ /**
+ * Calls the given callback with each registered page.
+ * @param {boolean} includeRootPages Whether the callback should be called
+ * for the root pages.
+ * @param {function(cr.ui.pageManager.Page)} callback The callback.
+ * @private
+ */
+ forEachPage_: function(includeRootPages, callback) {
+ let pageNames = Object.keys(this.registeredOverlayPages);
+ if (includeRootPages) {
+ pageNames = Object.keys(this.registeredPages).concat(pageNames);
+ }
+
+ pageNames.forEach(function(name) {
+ callback.call(
+ this,
+ this.registeredOverlayPages[name] || this.registeredPages[name]);
+ }, this);
+ },
+ };
+
+ /**
+ * An observer of PageManager.
+ * @constructor
+ */
+ PageManager.Observer = function() {};
+
+ PageManager.Observer.prototype = {
+ /**
+ * Called when a page is being shown or has been hidden.
+ * @param {cr.ui.pageManager.Page} page The page being shown or hidden.
+ */
+ onPageVisibilityChanged: function(page) {},
+
+ /**
+ * Called when a new title should be set.
+ * @param {string} title The title to set.
+ */
+ updateTitle: function(title) {},
+
+ /**
+ * Called when a page is navigated to.
+ * @param {string} path The path of the page being visited.
+ * @param {boolean} replace If true, allow no history events to be created.
+ */
+ updateHistory: function(path, replace) {},
+ };
+
+ // Export
+ return {PageManager: PageManager};
+});