From 68b2a2aafe3b596b4e52aa40da9465db7d41b86f Mon Sep 17 00:00:00 2001 From: Alex Nicksay <nicksay@google.com> Date: Mon, 2 Mar 2015 01:51:06 -0500 Subject: [PATCH] Pass shared values when processing multipart responses. This change allows values from one part of a multipart response to be handled during processing of subsequent parts. The motivating use case is to allow a response to specify a "name" to use during CSS-based animation once and have it apply to all subsequent parts. For example instead of: ```json [{"name": "my-page", "body": {"id-1": "content-1"}}, {"name": "my-page", "body": {"id-2": "content-2"}}, {"name": "my-page", "body": {"id-3": "content-3"}}] ``` The following would now be processed as if the above were sent: ```json [{"name": "my-page", "body": {"id-1": "content-1"}}, {"body": {"id-2": "content-2"}}, {"body": {"id-3": "content-3"}}] ``` Progress on #299. SAVEPOINT handle shared values revert server revert islated client changes revert parts of spf.nav revert parts of spf.nav.response --- src/client/nav/nav.js | 20 ++++++----- src/client/nav/request.js | 69 ++++++++++++++++++++++++++++---------- src/client/nav/response.js | 10 ++++-- 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/client/nav/nav.js b/src/client/nav/nav.js index 7c2663d4..e1a7a34f 100644 --- a/src/client/nav/nav.js +++ b/src/client/nav/nav.js @@ -663,9 +663,10 @@ spf.nav.handleNavigateError_ = function(options, url, err) { * @param {spf.nav.Info} info The navigation info object. * @param {string} url The requested URL, without the SPF identifier. * @param {spf.SingleResponse} partial The partial response object. + * @param {!Object.<string>} shared A shared values object. * @private */ -spf.nav.handleNavigatePart_ = function(options, info, url, partial) { +spf.nav.handleNavigatePart_ = function(options, info, url, partial, shared) { // Reload if the "part process" event is canceled. if (!spf.nav.dispatchPartProcess_(url, partial, options)) { spf.nav.reload(url, spf.nav.ReloadReason.PART_PROCESS_CANCELED); @@ -685,7 +686,7 @@ spf.nav.handleNavigatePart_ = function(options, info, url, partial) { } try { - spf.nav.response.process(url, partial, info, function() { + spf.nav.response.process(url, partial, info, shared, function() { spf.nav.dispatchPartDone_(url, partial, options); }); } catch (err) { @@ -749,7 +750,7 @@ spf.nav.handleNavigateSuccess_ = function(options, info, url, response) { // so an empty object is used to ensure events/callbacks are properly // queued after existing ones from any ongoing part prcoessing. var r = /** @type {spf.SingleResponse} */ (multipart ? {} : response); - spf.nav.response.process(url, r, info, function() { + spf.nav.response.process(url, r, info, null, function() { // If this navigation was from history, attempt to scroll to the previous // position after all processing is complete. This should not be done // earlier because the prevous position might rely on page width/height @@ -1032,9 +1033,11 @@ spf.nav.handleLoadError_ = function(isPrefetch, options, info, url, err) { * @param {spf.nav.Info} info The navigation info object. * @param {string} url The requested URL, without the SPF identifier. * @param {spf.SingleResponse} partial The partial response object. + * @param {!Object.<string>} shared A shared values object. * @private */ -spf.nav.handleLoadPart_ = function(isPrefetch, options, info, url, partial) { +spf.nav.handleLoadPart_ = function(isPrefetch, options, info, url, partial, + shared) { // Abort the load/prefetch if the "part process" callback is canceled. // Note: pass "true" to only execute callbacks and not dispatch events. if (!spf.nav.dispatchPartProcess_(url, partial, options, true)) { @@ -1080,7 +1083,7 @@ spf.nav.handleLoadPart_ = function(isPrefetch, options, info, url, partial) { var processFn = isPrefetch ? spf.nav.response.preprocess : spf.nav.response.process; - processFn(url, partial, info, function() { + processFn(url, partial, info, shared, function() { // Note: pass "true" to only execute callbacks and not dispatch events. spf.nav.dispatchPartDone_(url, partial, options, true); }); @@ -1164,7 +1167,7 @@ spf.nav.handleLoadSuccess_ = function(isPrefetch, options, info, url, // so an empty object is used to ensure the callback is properly // queued after existing ones from any ongoing part prcoessing. var r = /** @type {spf.SingleResponse} */ (multipart ? {} : response); - processFn(url, r, info, function() { + processFn(url, r, info, null, function() { // Note: pass "true" to only execute callbacks and not dispatch events. spf.nav.dispatchDone_(url, response, options, true); }); @@ -1226,14 +1229,15 @@ spf.nav.process = function(response, opt_callback) { }; if (multipart) { var parts = response['parts']; + var shared = response; // Top-level properties act as shared values. for (var i = 0; i < parts.length; i++) { var fn = spf.bind(done, null, i, parts.length - 1); - spf.nav.response.process(url, parts[i], null, fn); + spf.nav.response.process(url, parts[i], null, shared, fn); } } else { response = /** @type {spf.SingleResponse} */ (response); var fn = spf.bind(done, null, 0, 0); - spf.nav.response.process(url, response, null, fn); + spf.nav.response.process(url, response, null, null, fn); } }; diff --git a/src/client/nav/request.js b/src/client/nav/request.js index f871e812..458e154f 100644 --- a/src/client/nav/request.js +++ b/src/client/nav/request.js @@ -209,8 +209,9 @@ spf.nav.request.handleResponseFromCache_ = function(url, options, timing, } if (options.onPart && response['type'] == 'multipart') { var parts = response['parts']; + var shared = response; // Top-level properties act as shared values. for (var i = 0; i < parts.length; i++) { - options.onPart(url, parts[i]); + options.onPart(url, parts[i], shared); } } spf.nav.request.done_(url, options, timing, response, updateCache); @@ -269,10 +270,11 @@ spf.nav.request.handleChunkFromXHR_ = function(url, options, chunking, } return; } - if (options.onPart) { - for (var i = 0; i < parsed.parts.length; i++) { - spf.debug.debug(' parsed part', parsed.parts[i]); - options.onPart(url, parsed.parts[i]); + for (var i = 0; i < parsed.parts.length; i++) { + spf.debug.debug(' parsed part', parsed.parts[i]); + spf.nav.request.copySharedFrom_(chunking.shared, parsed.parts[i]); + if (options.onPart) { + options.onPart(url, parsed.parts[i], chunking.shared); } } chunking.complete = chunking.complete.concat(parsed.parts); @@ -363,33 +365,27 @@ spf.nav.request.handleCompleteFromXHR_ = function(url, options, timing, } return; } - if (options.onPart && parts.length > 1) { - // Only execute callbacks for parts that have not already been processed. + if (parts.length > 1) { // In case there is an edge case where some parts were parsed on-the-fly // but the entire response needed a full parse here, start iteration where // the chunk processing left off. This is mostly a safety measure and // the number of chunks processed here should be 0. for (var i = chunking.complete.length; i < parts.length; i++) { spf.debug.debug(' parsed part', parts[i]); - options.onPart(url, parts[i]); + spf.nav.request.copySharedFrom_(chunking.shared, parts[i]); + if (options.onPart) { + // Only execute callbacks for parts that have not been processed. + options.onPart(url, parts[i], chunking.shared); + } } } var response; if (parts.length > 1) { - var cacheType; - for (var i = 0, l = parts.length; i < l; i++) { - var part = parts[i]; - if (part['cacheType']) { - cacheType = part['cacheType']; - } - } response = /** @type {spf.MultipartResponse} */ ({ 'parts': parts, 'type': 'multipart' }); - if (cacheType) { - response['cacheType'] = cacheType; - } + spf.nav.request.copySharedTo_(response, chunking.shared); } else if (parts.length == 1) { response = /** @type {spf.SingleResponse} */(parts[0]); } else { @@ -431,6 +427,37 @@ spf.nav.request.done_ = function(url, options, timing, response, cache) { }; +/** + * Copy shared values from a response. + * + * @param {!Object.<string>} shared The shared values. + * @param {spf.SingleResponse} response The response. + * @private + */ +spf.nav.request.copySharedFrom_ = function(shared, response) { + if ('cacheType' in response) { + shared['cacheType'] = response['cacheType']; + } + if ('name' in response) { + shared['name'] = response['name']; + } +}; + + +/** + * Copy shared values to a response. + * + * @param {spf.SingleResponse} response The response. + * @param {!Object.<string>} shared The shared values. + * @private + */ +spf.nav.request.copySharedTo_ = function(response, shared) { + for (var k in shared) { + response[k] = shared[k]; + } +}; + + /** * @param {string} url The requested URL, without the SPF identifier. * @param {string|null|undefined} opt_current The current page's URL. Some @@ -556,6 +583,12 @@ spf.nav.request.Chunking_ = function() { * @type {!Array} */ this.complete = []; + /** + * Values for response fields that are present in one part but meant to be + * shared across all (e.g. cacheType, name). + * @type {!Object.<string>} + */ + this.shared = {}; }; diff --git a/src/client/nav/response.js b/src/client/nav/response.js index 3b6b1a80..aa18cb6f 100644 --- a/src/client/nav/response.js +++ b/src/client/nav/response.js @@ -113,12 +113,14 @@ spf.nav.response.parse = function(text, opt_multipart, opt_lastDitch) { * @param {string} url The URL of the response being processed. * @param {spf.SingleResponse} response The SPF response object to process. * @param {spf.nav.Info=} opt_info The navigation info object. + * @param {Object.<string>=} opt_shared A shared values object. * @param {function(string, spf.SingleResponse)=} opt_callback Function to * execute when processing is done; the first argument is `url`, * the second argument is `response`. */ -spf.nav.response.process = function(url, response, opt_info, opt_callback) { - spf.debug.info('nav.response.process ', response, opt_info); +spf.nav.response.process = function(url, response, opt_info, opt_shared, + opt_callback) { + spf.debug.info('nav.response.process ', response, opt_info, opt_shared); var isNavigate = opt_info && spf.string.startsWith(opt_info.type, 'navigate'); var isReverse = opt_info && opt_info.reverse; @@ -362,11 +364,13 @@ spf.nav.response.process = function(url, response, opt_info, opt_callback) { * @param {string} url The URL of the response being preprocessed. * @param {spf.SingleResponse} response The SPF response object to preprocess. * @param {spf.nav.Info=} opt_info The navigation info object. + * @param {Object.<string>=} opt_shared A shared values object. * @param {function(string, spf.SingleResponse)=} opt_callback Function to * execute when preprocessing is done; the first argument is `url`, * the second argument is `response`. */ -spf.nav.response.preprocess = function(url, response, opt_info, opt_callback) { +spf.nav.response.preprocess = function(url, response, opt_info, opt_shared, + opt_callback) { spf.debug.info('nav.response.preprocess ', response); // Convert the URL to absolute, to be used for finding the task queue. var key = 'preprocess ' + spf.url.absolute(url);