OLD | NEW |
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. |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
98 | 98 |
99 /** | 99 /** |
100 * Handler for window.onpopstate. | 100 * Handler for window.onpopstate. |
101 * @param {Event} e The history event. | 101 * @param {Event} e The history event. |
102 */ | 102 */ |
103 function onPopHistoryState(e) { | 103 function onPopHistoryState(e) { |
104 if (e.state && e.state.pageId) { | 104 if (e.state && e.state.pageId) { |
105 var params = resolvePageInfo(); | 105 var params = resolvePageInfo(); |
106 assert(params.id === e.state.pageId); | 106 assert(params.id === e.state.pageId); |
107 | 107 |
108 // If the page doesn't exist, create it. Otherwise, swap it in. | 108 // If the page isn't the current page, load it fresh. Even if the page is |
109 var frame = $(params.id).querySelector('iframe'); | 109 // already loaded, it may have state not reflected in the URL, such as the |
110 if (!frame) | 110 // history page's "Remove selected items" overlay. http://crbug.com/377386 |
| 111 if (getRequiredElement(params.id) !== getSelectedIframe()) |
111 showPage(params.id, HISTORY_STATE_OPTION.NONE, params.path); | 112 showPage(params.id, HISTORY_STATE_OPTION.NONE, params.path); |
112 else | |
113 selectPage(params.id); | |
114 | 113 |
115 // Either way, send the state down to it. | 114 // Either way, send the state down to it. |
116 // | 115 // |
117 // Note: This assumes that the state and path parameters for every page | 116 // Note: This assumes that the state and path parameters for every page |
118 // under this origin are compatible. All of the downstream pages which | 117 // under this origin are compatible. All of the downstream pages which |
119 // navigate use pushState and replaceState. | 118 // navigate use pushState and replaceState. |
120 invokeMethodOnPage(e.state.pageId, 'popState', | 119 invokeMethodOnPage(e.state.pageId, 'popState', |
121 {state: e.state.pageState, path: params.path}); | 120 {state: e.state.pageState, path: params.path}); |
122 } | 121 } |
123 } | 122 } |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
217 * @return {!HTMLElement} The frame associated to |origin| or null. | 216 * @return {!HTMLElement} The frame associated to |origin| or null. |
218 */ | 217 */ |
219 function getIframeFromOrigin(origin) { | 218 function getIframeFromOrigin(origin) { |
220 assert(origin.substr(-1) != '/', 'invalid origin given'); | 219 assert(origin.substr(-1) != '/', 'invalid origin given'); |
221 var query = '.iframe-container > iframe[src^="' + origin + '/"]'; | 220 var query = '.iframe-container > iframe[src^="' + origin + '/"]'; |
222 return document.querySelector(query); | 221 return document.querySelector(query); |
223 } | 222 } |
224 | 223 |
225 /** | 224 /** |
226 * Changes the path past the page title (i.e. chrome://chrome/settings/(.*)). | 225 * Changes the path past the page title (i.e. chrome://chrome/settings/(.*)). |
227 * @param {string} pageId Should match an id of one of the iframe containers. | |
228 * @param {Object} state The page's state object for the navigation. | 226 * @param {Object} state The page's state object for the navigation. |
229 * @param {string} path The new /path/ to be set after the page name. | 227 * @param {string} path The new /path/ to be set after the page name. |
230 * @param {number} historyOption The type of history modification to make. | 228 * @param {number} historyOption The type of history modification to make. |
231 */ | 229 */ |
232 function changePathTo(pageId, state, path, historyOption) { | 230 function changePathTo(state, path, historyOption) { |
233 assert(!path || path.substr(-1) != '/', 'invalid path given'); | 231 assert(!path || path.substr(-1) != '/', 'invalid path given'); |
234 | 232 |
235 var histFunc; | 233 var histFunc; |
236 if (historyOption == HISTORY_STATE_OPTION.PUSH) | 234 if (historyOption == HISTORY_STATE_OPTION.PUSH) |
237 histFunc = window.history.pushState; | 235 histFunc = window.history.pushState; |
238 else if (historyOption == HISTORY_STATE_OPTION.REPLACE) | 236 else if (historyOption == HISTORY_STATE_OPTION.REPLACE) |
239 histFunc = window.history.replaceState; | 237 histFunc = window.history.replaceState; |
240 | 238 |
241 assert(histFunc, 'invalid historyOption given ' + historyOption); | 239 assert(histFunc, 'invalid historyOption given ' + historyOption); |
242 | 240 |
| 241 var pageId = getSelectedIframe().id; |
243 var args = [{pageId: pageId, pageState: state}, | 242 var args = [{pageId: pageId, pageState: state}, |
244 '', | 243 '', |
245 '/' + pageId + '/' + (path || '')]; | 244 '/' + pageId + '/' + (path || '')]; |
246 histFunc.apply(window.history, args); | 245 histFunc.apply(window.history, args); |
247 } | 246 } |
248 | 247 |
249 /** | 248 /** |
250 * Adds or replaces the current history entry based on a navigation from the | 249 * Adds or replaces the current history entry based on a navigation from the |
251 * source iframe. | 250 * source iframe. |
252 * @param {string} origin The origin of the source iframe. | 251 * @param {string} origin The origin of the source iframe. |
253 * @param {Object} state The source iframe's state object. | 252 * @param {Object} state The source iframe's state object. |
254 * @param {string} path The new "path" (e.g. "/createProfile"). | 253 * @param {string} path The new "path" (e.g. "/createProfile"). |
255 * @param {boolean} replace Whether to replace the current history entry. | 254 * @param {boolean} replace Whether to replace the current history entry. |
256 */ | 255 */ |
257 function updateHistory(origin, state, path, replace) { | 256 function updateHistory(origin, state, path, replace) { |
258 assert(!path || path[0] != '/', 'invalid path sent from ' + origin); | 257 assert(!path || path[0] != '/', 'invalid path sent from ' + origin); |
259 var historyOption = | 258 var historyOption = |
260 replace ? HISTORY_STATE_OPTION.REPLACE : HISTORY_STATE_OPTION.PUSH; | 259 replace ? HISTORY_STATE_OPTION.REPLACE : HISTORY_STATE_OPTION.PUSH; |
261 // Only update the currently displayed path if this is the visible frame. | 260 // Only update the currently displayed path if this is the visible frame. |
262 var container = getIframeFromOrigin(origin).parentNode; | 261 var container = getIframeFromOrigin(origin).parentNode; |
263 if (container == getSelectedIframe()) | 262 if (container == getSelectedIframe()) |
264 changePathTo(container.id, state, path, historyOption); | 263 changePathTo(state, path, historyOption); |
265 } | 264 } |
266 | 265 |
267 /** | 266 /** |
268 * Sets the title of the page. | 267 * Sets the title of the page. |
269 * @param {string} origin The origin of the source iframe. | 268 * @param {string} origin The origin of the source iframe. |
270 * @param {string} title The title of the page. | 269 * @param {string} title The title of the page. |
271 */ | 270 */ |
272 function setTitle(origin, title) { | 271 function setTitle(origin, title) { |
273 // 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 |
274 // title. querySelector returns the actual iframe element, so use parentNode | 273 // title. querySelector returns the actual iframe element, so use parentNode |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
334 container.appendChild(frame); | 333 container.appendChild(frame); |
335 frame.src = sourceUrl; | 334 frame.src = sourceUrl; |
336 } else { | 335 } else { |
337 // 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 |
338 // 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 |
339 // location, so just replace every time until necessary to do otherwise. | 338 // location, so just replace every time until necessary to do otherwise. |
340 frame.contentWindow.location.replace(sourceUrl); | 339 frame.contentWindow.location.replace(sourceUrl); |
341 frame.dataset.ready = false; | 340 frame.dataset.ready = false; |
342 } | 341 } |
343 | 342 |
344 // This must be called before selectPage so that the title change applies to | 343 // If the last selected container is already showing, ignore the rest. |
345 // the new history entry. | |
346 if (historyOption != HISTORY_STATE_OPTION.NONE) | |
347 changePathTo(pageId, {}, path, historyOption); | |
348 | |
349 selectPage(pageId); | |
350 } | |
351 | |
352 /** | |
353 * Switches to a subpage. The subpage must already exist. | |
354 * @param {string} pageId Should match an id of one of the iframe containers. | |
355 */ | |
356 function selectPage(pageId) { | |
357 var container = getRequiredElement(pageId); | |
358 var lastSelected = document.querySelector('.iframe-container.selected'); | 344 var lastSelected = document.querySelector('.iframe-container.selected'); |
359 | |
360 // If the last selected container is already showing, ignore the rest. | |
361 if (lastSelected === container) | 345 if (lastSelected === container) |
362 return; | 346 return; |
363 | 347 |
364 if (lastSelected) { | 348 if (lastSelected) { |
365 lastSelected.classList.remove('selected'); | 349 lastSelected.classList.remove('selected'); |
366 // Setting aria-hidden hides the container from assistive technology | 350 // Setting aria-hidden hides the container from assistive technology |
367 // immediately. The 'hidden' attribute is set after the transition | 351 // immediately. The 'hidden' attribute is set after the transition |
368 // finishes - that ensures it's not possible to accidentally focus | 352 // finishes - that ensures it's not possible to accidentally focus |
369 // an element in an unselected container. | 353 // an element in an unselected container. |
370 lastSelected.setAttribute('aria-hidden', 'true'); | 354 lastSelected.setAttribute('aria-hidden', 'true'); |
371 } | 355 } |
372 | 356 |
373 // Containers that aren't selected have to be hidden so that their | 357 // Containers that aren't selected have to be hidden so that their |
374 // content isn't focusable. | 358 // content isn't focusable. |
375 container.hidden = false; | 359 container.hidden = false; |
376 container.setAttribute('aria-hidden', 'false'); | 360 container.setAttribute('aria-hidden', 'false'); |
377 | 361 |
378 // Trigger a layout after making it visible and before setting | 362 // Trigger a layout after making it visible and before setting |
379 // the class to 'selected', so that it animates in. | 363 // the class to 'selected', so that it animates in. |
380 container.offsetTop; | 364 container.offsetTop; |
381 container.classList.add('selected'); | 365 container.classList.add('selected'); |
382 | 366 |
383 setContentChanging(true); | 367 setContentChanging(true); |
384 adjustToScroll(0); | 368 adjustToScroll(0); |
385 | 369 |
386 var selectedFrame = getSelectedIframe().querySelector('iframe'); | 370 var selectedFrame = getSelectedIframe().querySelector('iframe'); |
387 uber.invokeMethodOnWindow(selectedFrame.contentWindow, 'frameSelected'); | 371 uber.invokeMethodOnWindow(selectedFrame.contentWindow, 'frameSelected'); |
388 | 372 |
| 373 if (historyOption != HISTORY_STATE_OPTION.NONE) |
| 374 changePathTo({}, path, historyOption); |
| 375 |
389 if (container.dataset.title) | 376 if (container.dataset.title) |
390 document.title = container.dataset.title; | 377 document.title = container.dataset.title; |
391 $('favicon').href = 'chrome://theme/' + container.dataset.favicon; | 378 $('favicon').href = 'chrome://theme/' + container.dataset.favicon; |
392 $('favicon2x').href = 'chrome://theme/' + container.dataset.favicon + '@2x'; | 379 $('favicon2x').href = 'chrome://theme/' + container.dataset.favicon + '@2x'; |
393 | 380 |
394 updateNavigationControls(); | 381 updateNavigationControls(); |
395 } | 382 } |
396 | 383 |
397 function onNavigationControlsLoaded() { | 384 function onNavigationControlsLoaded() { |
398 updateNavigationControls(); | 385 updateNavigationControls(); |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
463 } | 450 } |
464 | 451 |
465 return { | 452 return { |
466 onLoad: onLoad, | 453 onLoad: onLoad, |
467 onPopHistoryState: onPopHistoryState | 454 onPopHistoryState: onPopHistoryState |
468 }; | 455 }; |
469 }); | 456 }); |
470 | 457 |
471 window.addEventListener('popstate', uber.onPopHistoryState); | 458 window.addEventListener('popstate', uber.onPopHistoryState); |
472 document.addEventListener('DOMContentLoaded', uber.onLoad); | 459 document.addEventListener('DOMContentLoaded', uber.onLoad); |
OLD | NEW |