• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
10cr.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