Chromium Code Reviews| 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. https://crbug.com/37738 6 |
|
Dan Beam
2014/05/27 18:01:16
https://crbug -> http://crbug to fix 80 col (and s
davidben
2014/05/27 19:18:35
Done. (https://crbug.com works now, actually.)
| |
| 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 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 317 | 316 |
| 318 /** | 317 /** |
| 319 * Selects and navigates a subpage. This is called from uber-frame. | 318 * Selects and navigates a subpage. This is called from uber-frame. |
| 320 * @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. |
| 321 * @param {integer} historyOption Indicates whether we should push or replace | 320 * @param {integer} historyOption Indicates whether we should push or replace |
| 322 * browser history. | 321 * browser history. |
| 323 * @param {string} path A sub-page path. | 322 * @param {string} path A sub-page path. |
| 324 */ | 323 */ |
| 325 function showPage(pageId, historyOption, path) { | 324 function showPage(pageId, historyOption, path) { |
| 326 var container = $(pageId); | 325 var container = $(pageId); |
| 326 var lastSelected = document.querySelector('.iframe-container.selected'); | |
|
Dan Beam
2014/05/27 18:01:16
lower to right before use
davidben
2014/05/27 19:18:35
Done.
| |
| 327 | 327 |
| 328 // Lazy load of iframe contents. | 328 // Lazy load of iframe contents. |
| 329 var sourceUrl = container.dataset.url + (path || ''); | 329 var sourceUrl = container.dataset.url + (path || ''); |
| 330 var frame = container.querySelector('iframe'); | 330 var frame = container.querySelector('iframe'); |
| 331 if (!frame) { | 331 if (!frame) { |
| 332 frame = container.ownerDocument.createElement('iframe'); | 332 frame = container.ownerDocument.createElement('iframe'); |
| 333 frame.name = pageId; | 333 frame.name = pageId; |
| 334 container.appendChild(frame); | 334 container.appendChild(frame); |
| 335 frame.src = sourceUrl; | 335 frame.src = sourceUrl; |
| 336 } else { | 336 } else { |
| 337 // There's no particularly good way to know what the current URL of the | 337 // 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 | 338 // 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. | 339 // location, so just replace every time until necessary to do otherwise. |
| 340 frame.contentWindow.location.replace(sourceUrl); | 340 frame.contentWindow.location.replace(sourceUrl); |
| 341 frame.dataset.ready = false; | 341 frame.dataset.ready = false; |
| 342 } | 342 } |
| 343 | 343 |
| 344 // This must be called before selectPage so that the title change applies to | |
| 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'); | |
| 359 | |
| 360 // If the last selected container is already showing, ignore the rest. | 344 // 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 |