Home | History | Annotate | Download | only in uber
      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
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @fileoverview A collection of utility methods for UberPage and its contained
      7  *     pages.
      8  */
      9 
     10 cr.define('uber', function() {
     11 
     12   /**
     13    * Fixed position header elements on the page to be shifted by handleScroll.
     14    * @type {NodeList}
     15    */
     16   var headerElements;
     17 
     18   /**
     19    * This should be called by uber content pages when DOM content has loaded.
     20    */
     21   function onContentFrameLoaded() {
     22     headerElements = document.getElementsByTagName('header');
     23     document.addEventListener('scroll', handleScroll);
     24 
     25     invokeMethodOnParent('ready');
     26 
     27     // Prevent the navigation from being stuck in a disabled state when a
     28     // content page is reloaded while an overlay is visible (crbug.com/246939).
     29     invokeMethodOnParent('stopInterceptingEvents');
     30 
     31     // Trigger the scroll handler to tell the navigation if our page started
     32     // with some scroll (happens when you use tab restore).
     33     handleScroll();
     34 
     35     window.addEventListener('message', handleWindowMessage);
     36   }
     37 
     38   /**
     39    * Handles scroll events on the document. This adjusts the position of all
     40    * headers and updates the parent frame when the page is scrolled.
     41    * @private
     42    */
     43   function handleScroll() {
     44     var scrollLeft = scrollLeftForDocument(document);
     45     var offset = scrollLeft * -1;
     46     for (var i = 0; i < headerElements.length; i++) {
     47       // As a workaround for http://crbug.com/231830, set the transform to
     48       // 'none' rather than 0px.
     49       headerElements[i].style.webkitTransform = offset ?
     50           'translateX(' + offset + 'px)' : 'none';
     51     }
     52 
     53     invokeMethodOnParent('adjustToScroll', scrollLeft);
     54   }
     55 
     56   /**
     57    * Handles 'message' events on window.
     58    * @param {Event} e The message event.
     59    */
     60   function handleWindowMessage(e) {
     61     e = /** @type {!MessageEvent.<!{method: string, params: *}>} */(e);
     62     if (e.data.method === 'frameSelected')
     63       handleFrameSelected();
     64     else if (e.data.method === 'mouseWheel')
     65       handleMouseWheel(
     66           /** @type {{deltaX: number, deltaY: number}} */(e.data.params));
     67     else if (e.data.method === 'popState')
     68       handlePopState(e.data.params.state, e.data.params.path);
     69   }
     70 
     71   /**
     72    * This is called when a user selects this frame via the navigation bar
     73    * frame (and is triggered via postMessage() from the uber page).
     74    * @private
     75    */
     76   function handleFrameSelected() {
     77     setScrollTopForDocument(document, 0);
     78   }
     79 
     80   /**
     81    * Called when a user mouse wheels (or trackpad scrolls) over the nav frame.
     82    * The wheel event is forwarded here and we scroll the body.
     83    * There's no way to figure out the actual scroll amount for a given delta.
     84    * It differs for every platform and even initWebKitWheelEvent takes a
     85    * pixel amount instead of a wheel delta. So we just choose something
     86    * reasonable and hope no one notices the difference.
     87    * @param {{deltaX: number, deltaY: number}} params A structure that holds
     88    *     wheel deltas in X and Y.
     89    */
     90   function handleMouseWheel(params) {
     91     window.scrollBy(-params.deltaX * 49 / 120, -params.deltaY * 49 / 120);
     92   }
     93 
     94   /**
     95    * Called when the parent window restores some state saved by uber.pushState
     96    * or uber.replaceState. Simulates a popstate event.
     97    * @param {PopStateEvent} state A state object for replaceState and pushState.
     98    * @param {string} path The path the page navigated to.
     99    * @suppress {checkTypes}
    100    */
    101   function handlePopState(state, path) {
    102     window.history.replaceState(state, '', path);
    103     window.dispatchEvent(new PopStateEvent('popstate', {state: state}));
    104   }
    105 
    106   /**
    107    * @return {boolean} Whether this frame has a parent.
    108    */
    109   function hasParent() {
    110     return window != window.parent;
    111   }
    112 
    113   /**
    114    * Invokes a method on the parent window (UberPage). This is a convenience
    115    * method for API calls into the uber page.
    116    * @param {string} method The name of the method to invoke.
    117    * @param {?=} opt_params Optional property bag of parameters to pass to the
    118    *     invoked method.
    119    * @private
    120    */
    121   function invokeMethodOnParent(method, opt_params) {
    122     if (!hasParent())
    123       return;
    124 
    125     invokeMethodOnWindow(window.parent, method, opt_params, 'chrome://chrome');
    126   }
    127 
    128   /**
    129    * Invokes a method on the target window.
    130    * @param {string} method The name of the method to invoke.
    131    * @param {?=} opt_params Optional property bag of parameters to pass to the
    132    *     invoked method.
    133    * @param {string=} opt_url The origin of the target window.
    134    * @private
    135    */
    136   function invokeMethodOnWindow(targetWindow, method, opt_params, opt_url) {
    137     var data = {method: method, params: opt_params};
    138     targetWindow.postMessage(data, opt_url ? opt_url : '*');
    139   }
    140 
    141   /**
    142    * Updates the page's history state. If the page is embedded in a child,
    143    * forward the information to the parent for it to manage history for us. This
    144    * is a replacement of history.replaceState and history.pushState.
    145    * @param {Object} state A state object for replaceState and pushState.
    146    * @param {string} path The path the page navigated to.
    147    * @param {boolean} replace If true, navigate with replacement.
    148    * @private
    149    */
    150   function updateHistory(state, path, replace) {
    151     var historyFunction = replace ?
    152         window.history.replaceState :
    153         window.history.pushState;
    154 
    155     if (hasParent()) {
    156       // If there's a parent, always replaceState. The parent will do the actual
    157       // pushState.
    158       historyFunction = window.history.replaceState;
    159       invokeMethodOnParent('updateHistory', {
    160         state: state, path: path, replace: replace});
    161     }
    162     historyFunction.call(window.history, state, '', '/' + path);
    163   }
    164 
    165   /**
    166    * Sets the current title for the page. If the page is embedded in a child,
    167    * forward the information to the parent. This is a replacement for setting
    168    * document.title.
    169    * @param {string} title The new title for the page.
    170    */
    171   function setTitle(title) {
    172     document.title = title;
    173     invokeMethodOnParent('setTitle', {title: title});
    174   }
    175 
    176   /**
    177    * Pushes new history state for the page. If the page is embedded in a child,
    178    * forward the information to the parent; when embedded, all history entries
    179    * are attached to the parent. This is a replacement of history.pushState.
    180    * @param {Object} state A state object for replaceState and pushState.
    181    * @param {string} path The path the page navigated to.
    182    */
    183   function pushState(state, path) {
    184     updateHistory(state, path, false);
    185   }
    186 
    187   /**
    188    * Replaces the page's history state. If the page is embedded in a child,
    189    * forward the information to the parent; when embedded, all history entries
    190    * are attached to the parent. This is a replacement of history.replaceState.
    191    * @param {Object} state A state object for replaceState and pushState.
    192    * @param {string} path The path the page navigated to.
    193    */
    194   function replaceState(state, path) {
    195     updateHistory(state, path, true);
    196   }
    197 
    198   return {
    199     invokeMethodOnParent: invokeMethodOnParent,
    200     invokeMethodOnWindow: invokeMethodOnWindow,
    201     onContentFrameLoaded: onContentFrameLoaded,
    202     pushState: pushState,
    203     replaceState: replaceState,
    204     setTitle: setTitle,
    205   };
    206 });
    207