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

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: More comments (try jobs on previous) 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 = {};
davidben 2014/05/19 22:38:33 This may not be strictly necessary? The only page
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 }
Dan Beam 2014/05/19 23:06:49 nit: no curlies
davidben 2014/05/19 23:32:28 Done.
115
116 // Either way, send the state down to it.
117 //
118 // Note: This assumes that the state and path parameters for every page
119 // under this origin are compatible. All of the downstream pages which
120 // navigate use pushState and replaceState.
121 invokeMethodOnPage(e.state.pageId, 'popState',
122 {state: e.state.pageState, path: params.path});
123 }
97 } 124 }
98 125
99 /** 126 /**
100 * @return {Object} The default iframe container. 127 * @return {Object} The default iframe container.
101 */ 128 */
102 function getDefaultIframe() { 129 function getDefaultIframe() {
103 return $(loadTimeData.getString('helpHost')); 130 return $(loadTimeData.getString('helpHost'));
104 } 131 }
105 132
106 /** 133 /**
(...skipping 16 matching lines...) Expand all
123 * |method| is required, while |params| is optional. Extra parameters required 150 * |method| is required, while |params| is optional. Extra parameters required
124 * by a method must be specified by that method's documentation. 151 * by a method must be specified by that method's documentation.
125 * 152 *
126 * @param {Event} e The posted object. 153 * @param {Event} e The posted object.
127 */ 154 */
128 function handleWindowMessage(e) { 155 function handleWindowMessage(e) {
129 if (e.data.method === 'beginInterceptingEvents') { 156 if (e.data.method === 'beginInterceptingEvents') {
130 backgroundNavigation(); 157 backgroundNavigation();
131 } else if (e.data.method === 'stopInterceptingEvents') { 158 } else if (e.data.method === 'stopInterceptingEvents') {
132 foregroundNavigation(); 159 foregroundNavigation();
133 } else if (e.data.method === 'setPath') { 160 } else if (e.data.method === 'ready') {
134 setPath(e.origin, e.data.params.path); 161 pageReady(e.origin);
162 } else if (e.data.method === 'updateHistory') {
163 updateHistory(e.origin, e.data.params.state, e.data.params.path,
164 e.data.params.replace);
135 } else if (e.data.method === 'setTitle') { 165 } else if (e.data.method === 'setTitle') {
136 setTitle(e.origin, e.data.params.title); 166 setTitle(e.origin, e.data.params.title);
137 } else if (e.data.method === 'showPage') { 167 } else if (e.data.method === 'showPage') {
138 showPage(e.data.params.pageId, 168 showPage(e.data.params.pageId,
139 HISTORY_STATE_OPTION.PUSH, 169 HISTORY_STATE_OPTION.PUSH,
140 e.data.params.path); 170 e.data.params.path);
141 } else if (e.data.method === 'navigationControlsLoaded') { 171 } else if (e.data.method === 'navigationControlsLoaded') {
142 onNavigationControlsLoaded(); 172 onNavigationControlsLoaded();
143 } else if (e.data.method === 'adjustToScroll') { 173 } else if (e.data.method === 'adjustToScroll') {
144 adjustToScroll(e.data.params); 174 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. 218 * @return {!HTMLElement} The frame associated to |origin| or null.
189 */ 219 */
190 function getIframeFromOrigin(origin) { 220 function getIframeFromOrigin(origin) {
191 assert(origin.substr(-1) != '/', 'invalid origin given'); 221 assert(origin.substr(-1) != '/', 'invalid origin given');
192 var query = '.iframe-container > iframe[src^="' + origin + '/"]'; 222 var query = '.iframe-container > iframe[src^="' + origin + '/"]';
193 return document.querySelector(query); 223 return document.querySelector(query);
194 } 224 }
195 225
196 /** 226 /**
197 * Changes the path past the page title (i.e. chrome://chrome/settings/(.*)). 227 * Changes the path past the page title (i.e. chrome://chrome/settings/(.*)).
228 * @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. 229 * @param {string} path The new /path/ to be set after the page name.
199 * @param {number} historyOption The type of history modification to make. 230 * @param {number} historyOption The type of history modification to make.
200 */ 231 */
201 function changePathTo(path, historyOption) { 232 function changePathTo(state, path, historyOption) {
202 assert(!path || path.substr(-1) != '/', 'invalid path given'); 233 assert(!path || path.substr(-1) != '/', 'invalid path given');
203 234
204 var histFunc; 235 var histFunc;
205 if (historyOption == HISTORY_STATE_OPTION.PUSH) 236 if (historyOption == HISTORY_STATE_OPTION.PUSH)
206 histFunc = window.history.pushState; 237 histFunc = window.history.pushState;
207 else if (historyOption == HISTORY_STATE_OPTION.REPLACE) 238 else if (historyOption == HISTORY_STATE_OPTION.REPLACE)
208 histFunc = window.history.replaceState; 239 histFunc = window.history.replaceState;
209 240
210 assert(histFunc, 'invalid historyOption given ' + historyOption); 241 assert(histFunc, 'invalid historyOption given ' + historyOption);
211 242
212 var pageId = getSelectedIframe().id; 243 var pageId = getSelectedIframe().id;
213 var args = [{pageId: pageId}, '', '/' + pageId + '/' + (path || '')]; 244 var args = [{pageId: pageId, pageState: state},
245 '',
246 '/' + pageId + '/' + (path || '')];
214 histFunc.apply(window.history, args); 247 histFunc.apply(window.history, args);
215 } 248 }
216 249
217 /** 250 /**
218 * Sets the "path" of the page (actually the path after the first '/' char). 251 * Adds or replaces the current history entry based on a navigation from the
219 * @param {Object} origin The origin of the source iframe. 252 * source iframe.
220 * @param {string} title The new "path". 253 * @param {string} origin The origin of the source iframe.
254 * @param {Object} state The source iframe's state object.
255 * @param {string} path The new "path".
Dan Beam 2014/05/19 23:06:49 @param {string} path The new "path" (e.g. "/create
davidben 2014/05/19 23:32:28 Done.
256 * @param {boolean} replace Whether to replace the current history entry.
221 */ 257 */
222 function setPath(origin, path) { 258 function updateHistory(origin, state, path, replace) {
223 assert(!path || path[0] != '/', 'invalid path sent from ' + origin); 259 assert(!path || path[0] != '/', 'invalid path sent from ' + origin);
260 var historyOption =
261 replace ? HISTORY_STATE_OPTION.REPLACE : HISTORY_STATE_OPTION.PUSH;
Dan Beam 2014/05/19 23:06:49 nit: 4 \s indent
davidben 2014/05/19 23:32:28 Done.
224 // Only update the currently displayed path if this is the visible frame. 262 // Only update the currently displayed path if this is the visible frame.
225 if (getIframeFromOrigin(origin).parentNode == getSelectedIframe()) 263 if (getIframeFromOrigin(origin).parentNode == getSelectedIframe()) {
226 changePathTo(path, HISTORY_STATE_OPTION.REPLACE); 264 changePathTo(state, path, historyOption);
265 }
Dan Beam 2014/05/19 23:06:49 nit: no curlies
davidben 2014/05/19 23:32:28 Done.
227 } 266 }
228 267
229 /** 268 /**
230 * Sets the title of the page. 269 * Sets the title of the page.
231 * @param {Object} origin The origin of the source iframe. 270 * @param {string} origin The origin of the source iframe.
232 * @param {string} title The title of the page. 271 * @param {string} title The title of the page.
233 */ 272 */
234 function setTitle(origin, title) { 273 function setTitle(origin, title) {
235 // Cache the title for the client iframe, i.e., the iframe setting the 274 // Cache the title for the client iframe, i.e., the iframe setting the
236 // title. querySelector returns the actual iframe element, so use parentNode 275 // title. querySelector returns the actual iframe element, so use parentNode
237 // to get back to the container. 276 // to get back to the container.
238 var container = getIframeFromOrigin(origin).parentNode; 277 var container = getIframeFromOrigin(origin).parentNode;
239 container.dataset.title = title; 278 container.dataset.title = title;
240 279
241 // Only update the currently displayed title if this is the visible frame. 280 // Only update the currently displayed title if this is the visible frame.
242 if (container == getSelectedIframe()) 281 if (container == getSelectedIframe())
243 document.title = title; 282 document.title = title;
244 } 283 }
245 284
246 /** 285 /**
247 * Selects a subpage. This is called from uber-frame. 286 * Invokes a method on a subpage. If the subpage has not signaled readiness,
287 * queue the message for when it does.
288 * @param {string} pageId Should match an id of one of the iframe containers.
289 * @param {string} method The name of the method to invoke.
290 * @param {Object=} opt_params Optional property page of parameters to pass to
291 * the invoked method.
292 */
293 function invokeMethodOnPage(pageId, method, opt_params) {
294 var frame = $(pageId).querySelector('iframe');
295 if (!frame || !frame.dataset.ready) {
296 queuedInvokes[pageId] = (queuedInvokes[pageId] || []);
297 queuedInvokes[pageId].push([method, opt_params]);
298 } else {
299 uber.invokeMethodOnWindow(frame.contentWindow, method, opt_params);
300 }
301 }
302
303 /**
304 * Called in response to a page declaring readiness. Calls any deferred method
305 * invocations from invokeMethodOnPage.
306 * @param {string} origin The origin of the source iframe.
307 */
308 function pageReady(origin) {
309 var frame = getIframeFromOrigin(origin);
310 var container = frame.parentNode;
311 frame.dataset.ready = true;
312 var queue = queuedInvokes[container.id] || [];
313 delete queuedInvokes[container.id];
Dan Beam 2014/05/19 23:06:49 arguably this should be queuedInvokes[container
davidben 2014/05/19 23:32:28 Done.
314 for (var i = 0; i < queue.length; i++) {
315 uber.invokeMethodOnWindow(frame.contentWindow, queue[i][0], queue[i][1]);
316 }
317 }
318
319 /**
320 * 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. 321 * @param {string} pageId Should match an id of one of the iframe containers.
249 * @param {integer} historyOption Indicates whether we should push or replace 322 * @param {integer} historyOption Indicates whether we should push or replace
250 * browser history. 323 * browser history.
251 * @param {string} path A sub-page path. 324 * @param {string} path A sub-page path.
252 */ 325 */
253 function showPage(pageId, historyOption, path) { 326 function showPage(pageId, historyOption, path) {
254 var container = $(pageId); 327 var container = $(pageId);
255 var lastSelected = document.querySelector('.iframe-container.selected');
256 328
257 // Lazy load of iframe contents. 329 // Lazy load of iframe contents.
258 var sourceUrl = container.dataset.url + (path || ''); 330 var sourceUrl = container.dataset.url + (path || '');
259 var frame = container.querySelector('iframe'); 331 var frame = container.querySelector('iframe');
260 if (!frame) { 332 if (!frame) {
261 frame = container.ownerDocument.createElement('iframe'); 333 frame = container.ownerDocument.createElement('iframe');
262 frame.name = pageId; 334 frame.name = pageId;
263 container.appendChild(frame); 335 container.appendChild(frame);
264 frame.src = sourceUrl; 336 frame.src = sourceUrl;
265 } else { 337 } else {
266 // There's no particularly good way to know what the current URL of the 338 // 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 339 // 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. 340 // location, so just replace every time until necessary to do otherwise.
269 frame.contentWindow.location.replace(sourceUrl); 341 frame.contentWindow.location.replace(sourceUrl);
342 frame.dataset.ready = false;
270 } 343 }
271 344
345 selectPage(pageId);
346
347 if (historyOption != HISTORY_STATE_OPTION.NONE)
348 changePathTo({}, path, historyOption);
349 }
350
351 /**
352 * Switches to a subpage. The subpage must already exist.
353 * @param {string} pageId Should match an id of one of the iframe containers.
354 */
355 function selectPage(pageId) {
356 var container = $(pageId);
Dan Beam 2014/05/19 23:06:49 nit: $ -> getRequiredElement
davidben 2014/05/19 23:32:28 Done.
Dan Beam 2014/05/19 23:49:10 I think you did this on L325 instead of L354
davidben 2014/05/20 00:12:55 Oops, so I did. Done.
357 var lastSelected = document.querySelector('.iframe-container.selected');
358
272 // If the last selected container is already showing, ignore the rest. 359 // If the last selected container is already showing, ignore the rest.
273 if (lastSelected === container) 360 if (lastSelected === container)
274 return; 361 return;
275 362
276 if (lastSelected) { 363 if (lastSelected) {
277 lastSelected.classList.remove('selected'); 364 lastSelected.classList.remove('selected');
278 // Setting aria-hidden hides the container from assistive technology 365 // Setting aria-hidden hides the container from assistive technology
279 // immediately. The 'hidden' attribute is set after the transition 366 // immediately. The 'hidden' attribute is set after the transition
280 // finishes - that ensures it's not possible to accidentally focus 367 // finishes - that ensures it's not possible to accidentally focus
281 // an element in an unselected container. 368 // an element in an unselected container.
282 lastSelected.setAttribute('aria-hidden', 'true'); 369 lastSelected.setAttribute('aria-hidden', 'true');
283 } 370 }
284 371
285 // Containers that aren't selected have to be hidden so that their 372 // Containers that aren't selected have to be hidden so that their
286 // content isn't focusable. 373 // content isn't focusable.
287 container.hidden = false; 374 container.hidden = false;
288 container.setAttribute('aria-hidden', 'false'); 375 container.setAttribute('aria-hidden', 'false');
289 376
290 // Trigger a layout after making it visible and before setting 377 // Trigger a layout after making it visible and before setting
291 // the class to 'selected', so that it animates in. 378 // the class to 'selected', so that it animates in.
292 container.offsetTop; 379 container.offsetTop;
293 container.classList.add('selected'); 380 container.classList.add('selected');
294 381
295 setContentChanging(true); 382 setContentChanging(true);
296 adjustToScroll(0); 383 adjustToScroll(0);
297 384
298 var selectedFrame = getSelectedIframe().querySelector('iframe'); 385 var selectedFrame = getSelectedIframe().querySelector('iframe');
299 uber.invokeMethodOnWindow(selectedFrame.contentWindow, 'frameSelected'); 386 uber.invokeMethodOnWindow(selectedFrame.contentWindow, 'frameSelected');
300 387
301 if (historyOption != HISTORY_STATE_OPTION.NONE)
302 changePathTo(path, historyOption);
303
304 if (container.dataset.title) 388 if (container.dataset.title)
305 document.title = container.dataset.title; 389 document.title = container.dataset.title;
306 $('favicon').href = 'chrome://theme/' + container.dataset.favicon; 390 $('favicon').href = 'chrome://theme/' + container.dataset.favicon;
307 $('favicon2x').href = 'chrome://theme/' + container.dataset.favicon + '@2x'; 391 $('favicon2x').href = 'chrome://theme/' + container.dataset.favicon + '@2x';
308 392
309 updateNavigationControls(); 393 updateNavigationControls();
310 } 394 }
311 395
312 function onNavigationControlsLoaded() { 396 function onNavigationControlsLoaded() {
313 updateNavigationControls(); 397 updateNavigationControls();
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
378 } 462 }
379 463
380 return { 464 return {
381 onLoad: onLoad, 465 onLoad: onLoad,
382 onPopHistoryState: onPopHistoryState 466 onPopHistoryState: onPopHistoryState
383 }; 467 };
384 }); 468 });
385 469
386 window.addEventListener('popstate', uber.onPopHistoryState); 470 window.addEventListener('popstate', uber.onPopHistoryState);
387 document.addEventListener('DOMContentLoaded', uber.onLoad); 471 document.addEventListener('DOMContentLoaded', uber.onLoad);
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698