Skip to content

Commit

Permalink
Merge pull request #2789 from erwinmombay/add-fetchhtml
Browse files Browse the repository at this point in the history
add fetchDocument
  • Loading branch information
erwinmombay committed Apr 4, 2016
2 parents 4b54eff + 1ce7635 commit 2d223c6
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 133 deletions.
83 changes: 70 additions & 13 deletions src/xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,18 @@ class Xhr {
* We want to call `fetch_` unbound from any context since it could
* be either the native fetch or our polyfill.
*
* @private
* @param {string} input
* @param {?FetchInitDef=} opt_init
* @return {!Promise<!FetchResponse>}
* @private
*/
fetch_() {
fetch_(input, opt_init) {
// Fallback to xhr polyfill since `fetch` api does not support
// responseType = 'document'. We do this so we dont have to do any parsing
// and document construction on the UI thread which would be expensive.
if (opt_init && opt_init.responseType == 'document') {
return fetchPolyfill.apply(null, arguments);
}
return (this.win.fetch || fetchPolyfill).apply(null, arguments);
}

Expand Down Expand Up @@ -144,6 +152,26 @@ class Xhr {
});
}

/**
* Creates an XHR request with responseType=document
* and returns the `FetchResponse` object.
*
* @param {string} input
* @param {?FetchInitDef=} opt_init
* @return {!Promise<!HTMLDocument>}
*/
fetchDocument(input, opt_init) {
const init = opt_init || {};
init.responseType = 'document';
init.method = normalizeMethod_(init.method);
init.headers = init.headers || {};
init.headers['Accept'] = 'text/html';

return this.fetchAmpCors_(input, init).then(response => {
return assertSuccess(response).document_();
});
}

/**
* Sends the request, awaits result and confirms that it was successful.
*
Expand Down Expand Up @@ -229,12 +257,22 @@ export function fetchPolyfill(input, opt_init) {
'Only credentials=include support: %s', init.credentials);

return new Promise(function(resolve, reject) {
const xhr = createXhrRequest(init.method || 'GET', input, init);
const xhr = createXhrRequest(init.method || 'GET', input);

if (init.credentials == 'include') {
xhr.withCredentials = true;
}

if (init.responseType == 'document') {
xhr.responseType = 'document';
}

if (init.headers) {
Object.keys(init.headers).forEach(function(header) {
xhr.setRequestHeader(header, init.headers[header]);
});
}

xhr.onreadystatechange = () => {
if (xhr.readyState < /* STATUS_RECEIVED */ 2) {
return;
Expand Down Expand Up @@ -271,11 +309,10 @@ export function fetchPolyfill(input, opt_init) {
/**
* @param {string} method
* @param {string} url
* @param {!FetchInitDef} init
* @return {!XMLHttpRequest}
* @private
*/
function createXhrRequest(method, url, init) {
function createXhrRequest(method, url) {
let xhr = new XMLHttpRequest();
if ('withCredentials' in xhr) {
xhr.open(method, url, true);
Expand All @@ -286,24 +323,31 @@ function createXhrRequest(method, url, init) {
} else {
throw new Error('CORS is not supported');
}

if (init.headers) {
Object.keys(init.headers).forEach(function(header) {
xhr.setRequestHeader(header, init.headers[header]);
});
}
return xhr;
}

/**
* If 415 or in the 5xx range.
* @param {string} status
*/
function isRetriable(status) {
return status == 415 || (status >= 500 && status < 600);
}


/**
* Returns the response if successful or otherwise throws an error.
* @paran {!FetchResponse} response
* @return {!FetchResponse}
*/
function assertSuccess(response) {
user.assert(response.status >= 200 && response.status < 300,
'HTTP error %s', response.status);
if (!(response.status >= 200 && response.status < 300)) {
const err = user.createError(`HTTP error ${response.status}`);
if (isRetriable(response.status)) {
err.retriable = true;
}
throw err;
}
return response;
}

Expand Down Expand Up @@ -349,6 +393,19 @@ class FetchResponse {
json() {
return this.drainText_().then(JSON.parse);
}

/**
* Reads the xhr responseXML.
* @return {!Promise<!HTMLDocument>}
* @private
*/
document_() {
dev.assert(!this.bodyUsed, 'Body already used');
this.bodyUsed = true;
user.assert(this.xhr_.responseXML instanceof Document,
'responseXML should be a Document instance.');
return Promise.resolve(this.xhr_.responseXML);
}
}


Expand Down
Loading

0 comments on commit 2d223c6

Please sign in to comment.