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     if (e.data.method === 'frameSelected')
     62       handleFrameSelected();
     63     else if (e.data.method === 'mouseWheel')
     64       handleMouseWheel(e.data.params);
     65     else if (e.data.method === 'popState')
     66       handlePopState(e.data.params.state, e.data.params.path);
     67   }
     68 
     69   /**
     70    * This is called when a user selects this frame via the navigation bar
     71    * frame (and is triggered via postMessage() from the uber page).
     72    * @private
     73    */
     74   function handleFrameSelected() {
     75     setScrollTopForDocument(document, 0);
     76   }
     77 
     78   /**
     79    * Called when a user mouse wheels (or trackpad scrolls) over the nav frame.
     80    * The wheel event is forwarded here and we scroll the body.
     81    * There's no way to figure out the actual scroll amount for a given delta.
     82    * It differs for every platform and even initWebKitWheelEvent takes a
     83    * pixel amount instead of a wheel delta. So we just choose something
     84    * reasonable and hope no one notices the difference.
     85    * @param {Object} params A structure that holds wheel deltas in X and Y.
     86    */
     87   function handleMouseWheel(params) {
     88     window.scrollBy(-params.deltaX * 49 / 120, -params.deltaY * 49 / 120);
     89   }
     90 
     91   /**
     92    * Called when the parent window restores some state saved by uber.pushState
     93    * or uber.replaceState. Simulates a popstate event.
     94    */
     95   function handlePopState(state, path) {
     96     history.replaceState(state, '', path);
     97     window.dispatchEvent(new PopStateEvent('popstate', {state: state}));
     98   }
     99 
    100   /**
    101    * @return {boolean} Whether this frame has a parent.
    102    */
    103   function hasParent() {
    104     return window != window.parent;
    105   }
    106 
    107   /**
    108    * Invokes a method on the parent window (UberPage). This is a convenience
    109    * method for API calls into the uber page.
    110    * @param {string} method The name of the method to invoke.
    111    * @param {Object=} opt_params Optional property bag of parameters to pass to
    112    *     the invoked method.
    113    * @private
    114    */
    115   function invokeMethodOnParent(method, opt_params) {
    116     if (!hasParent())
    117       return;
    118 
    119     invokeMethodOnWindow(window.parent, method, opt_params, 'chrome://chrome');
    120   }
    121 
    122   /**
    123    * Invokes a method on the target window.
    124    * @param {string} method The name of the method to invoke.
    125    * @param {Object=} opt_params Optional property bag of parameters to pass to
    126    *     the invoked method.
    127    * @param {string=} opt_url The origin of the target window.
    128    * @private
    129    */
    130   function invokeMethodOnWindow(targetWindow, method, opt_params, opt_url) {
    131     var data = {method: method, params: opt_params};
    132     targetWindow.postMessage(data, opt_url ? opt_url : '*');
    133   }
    134 
    135   /**
    136    * Updates the page's history state. If the page is embedded in a child,
    137    * forward the information to the parent for it to manage history for us. This
    138    * is a replacement of history.replaceState and history.pushState.
    139    * @param {Object} state A state object for replaceState and pushState.
    140    * @param {string} title The title of the page to replace.
    141    * @param {string} path The path the page navigated to.
    142    * @param {boolean} replace If true, navigate with replacement.
    143    * @private
    144    */
    145   function updateHistory(state, path, replace) {
    146     var historyFunction = replace ?
    147         window.history.replaceState :
    148         window.history.pushState;
    149 
    150     if (hasParent()) {
    151       // If there's a parent, always replaceState. The parent will do the actual
    152       // pushState.
    153       historyFunction = window.history.replaceState;
    154       invokeMethodOnParent('updateHistory', {
    155         state: state, path: path, replace: replace});
    156     }
    157     historyFunction.call(window.history, state, '', '/' + path);
    158   }
    159 
    160   /**
    161    * Sets the current title for the page. If the page is embedded in a child,
    162    * forward the information to the parent. This is a replacement for setting
    163    * document.title.
    164    * @param {string} title The new title for the page.
    165    */
    166   function setTitle(title) {
    167     document.title = title;
    168     invokeMethodOnParent('setTitle', {title: title});
    169   }
    170 
    171   /**
    172    * Pushes new history state for the page. If the page is embedded in a child,
    173    * forward the information to the parent; when embedded, all history entries
    174    * are attached to the parent. This is a replacement of history.pushState.
    175    * @param {Object} state A state object for replaceState and pushState.
    176    * @param {string} path The path the page navigated to.
    177    */
    178   function pushState(state, path) {
    179     updateHistory(state, path, false);
    180   }
    181 
    182   /**
    183    * Replaces the page's history state. If the page is embedded in a child,
    184    * forward the information to the parent; when embedded, all history entries
    185    * are attached to the parent. This is a replacement of history.replaceState.
    186    * @param {Object} state A state object for replaceState and pushState.
    187    * @param {string} path The path the page navigated to.
    188    */
    189   function replaceState(state, path) {
    190     updateHistory(state, path, true);
    191   }
    192 
    193   return {
    194     invokeMethodOnParent: invokeMethodOnParent,
    195     invokeMethodOnWindow: invokeMethodOnWindow,
    196     onContentFrameLoaded: onContentFrameLoaded,
    197     pushState: pushState,
    198     replaceState: replaceState,
    199     setTitle: setTitle,
    200   };
    201 });
    202