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