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