Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(69)

Side by Side Diff: chrome/browser/resources/uber/uber.js

Issue 298553002: Options: maintain history entries on the parent frame. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: dbeam comments Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 cr.define('uber', function() { 5 cr.define('uber', function() {
6 /** 6 /**
7 * Options for how web history should be handled. 7 * Options for how web history should be handled.
8 */ 8 */
9 var HISTORY_STATE_OPTION = { 9 var HISTORY_STATE_OPTION = {
10 PUSH: 1, // Push a new history state. 10 PUSH: 1, // Push a new history state.
11 REPLACE: 2, // Replace the current history state. 11 REPLACE: 2, // Replace the current history state.
12 NONE: 3, // Ignore this history state change. 12 NONE: 3, // Ignore this history state change.
13 }; 13 };
14 14
15 /** 15 /**
16 * We cache a reference to the #navigation frame here so we don't need to grab 16 * We cache a reference to the #navigation frame here so we don't need to grab
17 * it from the DOM on each scroll. 17 * it from the DOM on each scroll.
18 * @type {Node} 18 * @type {Node}
19 * @private 19 * @private
20 */ 20 */
21 var navFrame; 21 var navFrame;
22 22
23 /** 23 /**
24 * A queue of method invocations on one of the iframes; if the iframe has not
25 * loaded by the time there is a method to invoke, delay the invocation until
26 * it is ready.
27 * @type {Object}
28 * @private
29 */
30 var queuedInvokes = {};
31
32 /**
24 * Handles page initialization. 33 * Handles page initialization.
25 */ 34 */
26 function onLoad(e) { 35 function onLoad(e) {
27 navFrame = $('navigation'); 36 navFrame = $('navigation');
28 navFrame.dataset.width = navFrame.offsetWidth; 37 navFrame.dataset.width = navFrame.offsetWidth;
29 38
30 // Select a page based on the page-URL. 39 // Select a page based on the page-URL.
31 var params = resolvePageInfo(); 40 var params = resolvePageInfo();
32 showPage(params.id, HISTORY_STATE_OPTION.NONE, params.path); 41 showPage(params.id, HISTORY_STATE_OPTION.NONE, params.path);
33 42
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
85 params.id = getDefaultIframe().id; 94 params.id = getDefaultIframe().id;
86 95
87 return params; 96 return params;
88 } 97 }
89 98
90 /** 99 /**
91 * Handler for window.onpopstate. 100 * Handler for window.onpopstate.
92 * @param {Event} e The history event. 101 * @param {Event} e The history event.
93 */ 102 */
94 function onPopHistoryState(e) { 103 function onPopHistoryState(e) {
95 if (e.state && e.state.pageId) 104 if (e.state && e.state.pageId) {
96 showPage(e.state.pageId, HISTORY_STATE_OPTION.NONE); 105 var params = resolvePageInfo();
106 assert(params.id === e.state.pageId);
107
108 // If the page doesn't exist, create it. Otherwise, swap it in.
109 var frame = $(params.id).querySelector('iframe');
110 if (!frame)
111 showPage(params.id, HISTORY_STATE_OPTION.NONE, params.path);
112 else
113 selectPage(params.id);
114
115 // Either way, send the state down to it.
116 //
117 // Note: This assumes that the state and path parameters for every page
118 // under this origin are compatible. All of the downstream pages which
119 // navigate use pushState and replaceState.
120 invokeMethodOnPage(e.state.pageId, 'popState',
121 {state: e.state.pageState, path: params.path});
122 }
97 } 123 }
98 124
99 /** 125 /**
100 * @return {Object} The default iframe container. 126 * @return {Object} The default iframe container.
101 */ 127 */
102 function getDefaultIframe() { 128 function getDefaultIframe() {
103 return $(loadTimeData.getString('helpHost')); 129 return $(loadTimeData.getString('helpHost'));
104 } 130 }
105 131
106 /** 132 /**
(...skipping 16 matching lines...) Expand all
123 * |method| is required, while |params| is optional. Extra parameters required 149 * |method| is required, while |params| is optional. Extra parameters required
124 * by a method must be specified by that method's documentation. 150 * by a method must be specified by that method's documentation.
125 * 151 *
126 * @param {Event} e The posted object. 152 * @param {Event} e The posted object.
127 */ 153 */
128 function handleWindowMessage(e) { 154 function handleWindowMessage(e) {
129 if (e.data.method === 'beginInterceptingEvents') { 155 if (e.data.method === 'beginInterceptingEvents') {
130 backgroundNavigation(); 156 backgroundNavigation();
131 } else if (e.data.method === 'stopInterceptingEvents') { 157 } else if (e.data.method === 'stopInterceptingEvents') {
132 foregroundNavigation(); 158 foregroundNavigation();
133 } else if (e.data.method === 'setPath') { 159 } else if (e.data.method === 'ready') {
134 setPath(e.origin, e.data.params.path); 160 pageReady(e.origin);
161 } else if (e.data.method === 'updateHistory') {
162 updateHistory(e.origin, e.data.params.state, e.data.params.path,
163 e.data.params.replace);
135 } else if (e.data.method === 'setTitle') { 164 } else if (e.data.method === 'setTitle') {
136 setTitle(e.origin, e.data.params.title); 165 setTitle(e.origin, e.data.params.title);
137 } else if (e.data.method === 'showPage') { 166 } else if (e.data.method === 'showPage') {
138 showPage(e.data.params.pageId, 167 showPage(e.data.params.pageId,
139 HISTORY_STATE_OPTION.PUSH, 168 HISTORY_STATE_OPTION.PUSH,
140 e.data.params.path); 169 e.data.params.path);
141 } else if (e.data.method === 'navigationControlsLoaded') { 170 } else if (e.data.method === 'navigationControlsLoaded') {
142 onNavigationControlsLoaded(); 171 onNavigationControlsLoaded();
143 } else if (e.data.method === 'adjustToScroll') { 172 } else if (e.data.method === 'adjustToScroll') {
144 adjustToScroll(e.data.params); 173 adjustToScroll(e.data.params);
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
188 * @return {!HTMLElement} The frame associated to |origin| or null. 217 * @return {!HTMLElement} The frame associated to |origin| or null.
189 */ 218 */
190 function getIframeFromOrigin(origin) { 219 function getIframeFromOrigin(origin) {
191 assert(origin.substr(-1) != '/', 'invalid origin given'); 220 assert(origin.substr(-1) != '/', 'invalid origin given');
192 var query = '.iframe-container > iframe[src^="' + origin + '/"]'; 221 var query = '.iframe-container > iframe[src^="' + origin + '/"]';
193 return document.querySelector(query); 222 return document.querySelector(query);
194 } 223 }
195 224
196 /** 225 /**
197 * Changes the path past the page title (i.e. chrome://chrome/settings/(.*)). 226 * Changes the path past the page title (i.e. chrome://chrome/settings/(.*)).
227 * @param {Object} state The page's state object for the navigation.
198 * @param {string} path The new /path/ to be set after the page name. 228 * @param {string} path The new /path/ to be set after the page name.
199 * @param {number} historyOption The type of history modification to make. 229 * @param {number} historyOption The type of history modification to make.
200 */ 230 */
201 function changePathTo(path, historyOption) { 231 function changePathTo(state, path, historyOption) {
202 assert(!path || path.substr(-1) != '/', 'invalid path given'); 232 assert(!path || path.substr(-1) != '/', 'invalid path given');
203 233
204 var histFunc; 234 var histFunc;
205 if (historyOption == HISTORY_STATE_OPTION.PUSH) 235 if (historyOption == HISTORY_STATE_OPTION.PUSH)
206 histFunc = window.history.pushState; 236 histFunc = window.history.pushState;
207 else if (historyOption == HISTORY_STATE_OPTION.REPLACE) 237 else if (historyOption == HISTORY_STATE_OPTION.REPLACE)
208 histFunc = window.history.replaceState; 238 histFunc = window.history.replaceState;
209 239
210 assert(histFunc, 'invalid historyOption given ' + historyOption); 240 assert(histFunc, 'invalid historyOption given ' + historyOption);
211 241
212 var pageId = getSelectedIframe().id; 242 var pageId = getSelectedIframe().id;
213 var args = [{pageId: pageId}, '', '/' + pageId + '/' + (path || '')]; 243 var args = [{pageId: pageId, pageState: state},
244 '',
245 '/' + pageId + '/' + (path || '')];
214 histFunc.apply(window.history, args); 246 histFunc.apply(window.history, args);
215 } 247 }
216 248
217 /** 249 /**
218 * Sets the "path" of the page (actually the path after the first '/' char). 250 * Adds or replaces the current history entry based on a navigation from the
219 * @param {Object} origin The origin of the source iframe. 251 * source iframe.
220 * @param {string} title The new "path". 252 * @param {string} origin The origin of the source iframe.
253 * @param {Object} state The source iframe's state object.
254 * @param {string} path The new "path" (e.g. "/createProfile").
255 * @param {boolean} replace Whether to replace the current history entry.
221 */ 256 */
222 function setPath(origin, path) { 257 function updateHistory(origin, state, path, replace) {
223 assert(!path || path[0] != '/', 'invalid path sent from ' + origin); 258 assert(!path || path[0] != '/', 'invalid path sent from ' + origin);
259 var historyOption =
260 replace ? HISTORY_STATE_OPTION.REPLACE : HISTORY_STATE_OPTION.PUSH;
224 // Only update the currently displayed path if this is the visible frame. 261 // Only update the currently displayed path if this is the visible frame.
225 if (getIframeFromOrigin(origin).parentNode == getSelectedIframe()) 262 if (getIframeFromOrigin(origin).parentNode == getSelectedIframe())
226 changePathTo(path, HISTORY_STATE_OPTION.REPLACE); 263 changePathTo(state, path, historyOption);
227 } 264 }
228 265
229 /** 266 /**
230 * Sets the title of the page. 267 * Sets the title of the page.
231 * @param {Object} origin The origin of the source iframe. 268 * @param {string} origin The origin of the source iframe.
232 * @param {string} title The title of the page. 269 * @param {string} title The title of the page.
233 */ 270 */
234 function setTitle(origin, title) { 271 function setTitle(origin, title) {
235 // Cache the title for the client iframe, i.e., the iframe setting the 272 // Cache the title for the client iframe, i.e., the iframe setting the
236 // title. querySelector returns the actual iframe element, so use parentNode 273 // title. querySelector returns the actual iframe element, so use parentNode
237 // to get back to the container. 274 // to get back to the container.
238 var container = getIframeFromOrigin(origin).parentNode; 275 var container = getIframeFromOrigin(origin).parentNode;
239 container.dataset.title = title; 276 container.dataset.title = title;
240 277
241 // Only update the currently displayed title if this is the visible frame. 278 // Only update the currently displayed title if this is the visible frame.
242 if (container == getSelectedIframe()) 279 if (container == getSelectedIframe())
243 document.title = title; 280 document.title = title;
244 } 281 }
245 282
246 /** 283 /**
247 * Selects a subpage. This is called from uber-frame. 284 * Invokes a method on a subpage. If the subpage has not signaled readiness,
285 * queue the message for when it does.
286 * @param {string} pageId Should match an id of one of the iframe containers.
287 * @param {string} method The name of the method to invoke.
288 * @param {Object=} opt_params Optional property page of parameters to pass to
289 * the invoked method.
290 */
291 function invokeMethodOnPage(pageId, method, opt_params) {
292 var frame = $(pageId).querySelector('iframe');
293 if (!frame || !frame.dataset.ready) {
294 queuedInvokes[pageId] = (queuedInvokes[pageId] || []);
295 queuedInvokes[pageId].push([method, opt_params]);
296 } else {
297 uber.invokeMethodOnWindow(frame.contentWindow, method, opt_params);
298 }
299 }
300
301 /**
302 * Called in response to a page declaring readiness. Calls any deferred method
303 * invocations from invokeMethodOnPage.
304 * @param {string} origin The origin of the source iframe.
305 */
306 function pageReady(origin) {
307 var frame = getIframeFromOrigin(origin);
308 var container = frame.parentNode;
309 frame.dataset.ready = true;
310 var queue = queuedInvokes[container.id] || [];
311 queuedInvokes[container.id] = undefined;
312 for (var i = 0; i < queue.length; i++) {
313 uber.invokeMethodOnWindow(frame.contentWindow, queue[i][0], queue[i][1]);
314 }
315 }
316
317 /**
318 * Selects and navigates a subpage. This is called from uber-frame.
248 * @param {string} pageId Should match an id of one of the iframe containers. 319 * @param {string} pageId Should match an id of one of the iframe containers.
249 * @param {integer} historyOption Indicates whether we should push or replace 320 * @param {integer} historyOption Indicates whether we should push or replace
250 * browser history. 321 * browser history.
251 * @param {string} path A sub-page path. 322 * @param {string} path A sub-page path.
252 */ 323 */
253 function showPage(pageId, historyOption, path) { 324 function showPage(pageId, historyOption, path) {
254 var container = $(pageId); 325 var container = getRequiredElement(pageId);
255 var lastSelected = document.querySelector('.iframe-container.selected');
256 326
257 // Lazy load of iframe contents. 327 // Lazy load of iframe contents.
258 var sourceUrl = container.dataset.url + (path || ''); 328 var sourceUrl = container.dataset.url + (path || '');
259 var frame = container.querySelector('iframe'); 329 var frame = container.querySelector('iframe');
260 if (!frame) { 330 if (!frame) {
261 frame = container.ownerDocument.createElement('iframe'); 331 frame = container.ownerDocument.createElement('iframe');
262 frame.name = pageId; 332 frame.name = pageId;
263 container.appendChild(frame); 333 container.appendChild(frame);
264 frame.src = sourceUrl; 334 frame.src = sourceUrl;
265 } else { 335 } else {
266 // There's no particularly good way to know what the current URL of the 336 // There's no particularly good way to know what the current URL of the
267 // content frame is as we don't have access to its contentWindow's 337 // content frame is as we don't have access to its contentWindow's
268 // location, so just replace every time until necessary to do otherwise. 338 // location, so just replace every time until necessary to do otherwise.
269 frame.contentWindow.location.replace(sourceUrl); 339 frame.contentWindow.location.replace(sourceUrl);
340 frame.dataset.ready = false;
270 } 341 }
271 342
343 selectPage(pageId);
344
345 if (historyOption != HISTORY_STATE_OPTION.NONE)
346 changePathTo({}, path, historyOption);
347 }
348
349 /**
350 * Switches to a subpage. The subpage must already exist.
351 * @param {string} pageId Should match an id of one of the iframe containers.
352 */
353 function selectPage(pageId) {
354 var container = $(pageId);
355 var lastSelected = document.querySelector('.iframe-container.selected');
356
272 // If the last selected container is already showing, ignore the rest. 357 // If the last selected container is already showing, ignore the rest.
273 if (lastSelected === container) 358 if (lastSelected === container)
274 return; 359 return;
275 360
276 if (lastSelected) { 361 if (lastSelected) {
277 lastSelected.classList.remove('selected'); 362 lastSelected.classList.remove('selected');
278 // Setting aria-hidden hides the container from assistive technology 363 // Setting aria-hidden hides the container from assistive technology
279 // immediately. The 'hidden' attribute is set after the transition 364 // immediately. The 'hidden' attribute is set after the transition
280 // finishes - that ensures it's not possible to accidentally focus 365 // finishes - that ensures it's not possible to accidentally focus
281 // an element in an unselected container. 366 // an element in an unselected container.
282 lastSelected.setAttribute('aria-hidden', 'true'); 367 lastSelected.setAttribute('aria-hidden', 'true');
283 } 368 }
284 369
285 // Containers that aren't selected have to be hidden so that their 370 // Containers that aren't selected have to be hidden so that their
286 // content isn't focusable. 371 // content isn't focusable.
287 container.hidden = false; 372 container.hidden = false;
288 container.setAttribute('aria-hidden', 'false'); 373 container.setAttribute('aria-hidden', 'false');
289 374
290 // Trigger a layout after making it visible and before setting 375 // Trigger a layout after making it visible and before setting
291 // the class to 'selected', so that it animates in. 376 // the class to 'selected', so that it animates in.
292 container.offsetTop; 377 container.offsetTop;
293 container.classList.add('selected'); 378 container.classList.add('selected');
294 379
295 setContentChanging(true); 380 setContentChanging(true);
296 adjustToScroll(0); 381 adjustToScroll(0);
297 382
298 var selectedFrame = getSelectedIframe().querySelector('iframe'); 383 var selectedFrame = getSelectedIframe().querySelector('iframe');
299 uber.invokeMethodOnWindow(selectedFrame.contentWindow, 'frameSelected'); 384 uber.invokeMethodOnWindow(selectedFrame.contentWindow, 'frameSelected');
300 385
301 if (historyOption != HISTORY_STATE_OPTION.NONE)
302 changePathTo(path, historyOption);
303
304 if (container.dataset.title) 386 if (container.dataset.title)
305 document.title = container.dataset.title; 387 document.title = container.dataset.title;
306 $('favicon').href = 'chrome://theme/' + container.dataset.favicon; 388 $('favicon').href = 'chrome://theme/' + container.dataset.favicon;
307 $('favicon2x').href = 'chrome://theme/' + container.dataset.favicon + '@2x'; 389 $('favicon2x').href = 'chrome://theme/' + container.dataset.favicon + '@2x';
308 390
309 updateNavigationControls(); 391 updateNavigationControls();
310 } 392 }
311 393
312 function onNavigationControlsLoaded() { 394 function onNavigationControlsLoaded() {
313 updateNavigationControls(); 395 updateNavigationControls();
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
378 } 460 }
379 461
380 return { 462 return {
381 onLoad: onLoad, 463 onLoad: onLoad,
382 onPopHistoryState: onPopHistoryState 464 onPopHistoryState: onPopHistoryState
383 }; 465 };
384 }); 466 });
385 467
386 window.addEventListener('popstate', uber.onPopHistoryState); 468 window.addEventListener('popstate', uber.onPopHistoryState);
387 document.addEventListener('DOMContentLoaded', uber.onLoad); 469 document.addEventListener('DOMContentLoaded', uber.onLoad);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698