Skip to content

Commit

Permalink
Added progress events for Fetch plugin.
Browse files Browse the repository at this point in the history
Also adds support for the relevant methods to the fetch mocks.

Closes #1504

Change-Id: I1a94148810262ecab675aea7dca7e1a3aaf8800a
  • Loading branch information
theodab committed Sep 25, 2018
1 parent 283f9ca commit cef4ae9
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 10 deletions.
65 changes: 58 additions & 7 deletions lib/net/http_fetch_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

goog.provide('shaka.net.HttpFetchPlugin');

goog.require('goog.asserts');
goog.require('shaka.net.HttpPluginUtils');
goog.require('shaka.net.NetworkingEngine');
goog.require('shaka.util.AbortableOperation');
Expand All @@ -30,10 +31,13 @@ goog.require('shaka.util.MapUtils');
* @param {string} uri
* @param {shaka.extern.Request} request
* @param {shaka.net.NetworkingEngine.RequestType} requestType
* @param {function(number, number)=} progressUpdated Called when a progress
* event happened.
* @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
* @export
*/
shaka.net.HttpFetchPlugin = function(uri, request, requestType) {
shaka.net.HttpFetchPlugin = function(
uri, request, requestType, progressUpdated) {
const headers = new shaka.net.HttpFetchPlugin.Headers_();
shaka.util.MapUtils.asMap(request.headers).forEach((value, key) => {
headers.append(key, value);
Expand Down Expand Up @@ -69,7 +73,7 @@ shaka.net.HttpFetchPlugin = function(uri, request, requestType) {
}

const promise = shaka.net.HttpFetchPlugin.request_(uri, requestType, init,
abortStatus, timeout);
abortStatus, timeout, progressUpdated);

return new shaka.util.AbortableOperation(
promise,
Expand All @@ -86,17 +90,66 @@ shaka.net.HttpFetchPlugin = function(uri, request, requestType) {
* @param {!RequestInit} init
* @param {shaka.net.HttpFetchPlugin.AbortStatus} abortStatus
* @param {number|undefined} timeoutId
* @param {function(number, number)=} progressUpdated
* @return {!Promise<!shaka.extern.Response>}
* @private
*/
shaka.net.HttpFetchPlugin.request_ = async function(uri, requestType, init,
abortStatus, timeoutId) {
abortStatus, timeoutId, progressUpdated) {
const fetch = shaka.net.HttpFetchPlugin.fetch_;
let response;
let arrayBuffer;
let loaded = 0;
let lastLoaded = 0;

// Last time stamp when we got a progress event.
let lastTime = Date.now();

try {
// The promise returned by fetch resolves as soon as the HTTP response
// headers are available. The download itself isn't done until the promise
// for retrieving the data (arrayBuffer, blob, etc) has resolved.
response = await fetch(uri, init);
// Getting the reader in this way allows us to observe the process of
// downloading the body, instead of just waiting for an opaque promise to
// resolve.
// We first clone the response because calling getReader locks the body
// stream; if we didn't clone it here, we would be unable to get the
// response's arrayBuffer later.
const reader = response.clone().body.getReader();
let start = (controller) => {
let push = async () => {
const readObj = await reader.read();

if (!readObj.done) {
loaded += readObj.value.byteLength;
}

let currentTime = Date.now();
// If the time between last time and this time we got progress event
// is long enough, or if a whole segment is downloaded, call
// progressUpdated().
if (currentTime - lastTime > 100 || readObj.done) {
progressUpdated(currentTime - lastTime, loaded - lastLoaded);
lastLoaded = loaded;
lastTime = currentTime;
}

if (readObj.done) {
goog.asserts.assert(!readObj.value,
'readObj should be unset when "done" is true.');
controller.close();
} else {
controller.enqueue(readObj.value);
push();
}
};
push();
};
// Create a ReadableStream to use the reader. We don't need to use the
// actual stream for anything, though, as we are using the response's
// arrayBuffer method to get the body, so we don't store the ReadableStream.
new ReadableStream({start}); // eslint-disable-line no-new
arrayBuffer = await response.arrayBuffer();
} catch (error) {
if (abortStatus.canceled) {
Expand Down Expand Up @@ -188,10 +241,8 @@ shaka.net.HttpFetchPlugin.Headers_ = window.Headers;


if (shaka.net.HttpFetchPlugin.isSupported()) {
// TODO: Update Fetch plugin to use progress events so we can use Fetch as
// the default.
shaka.net.NetworkingEngine.registerScheme('http', shaka.net.HttpFetchPlugin,
shaka.net.NetworkingEngine.PluginPriority.FALLBACK);
shaka.net.NetworkingEngine.PluginPriority.PREFERRED);
shaka.net.NetworkingEngine.registerScheme('https', shaka.net.HttpFetchPlugin,
shaka.net.NetworkingEngine.PluginPriority.FALLBACK);
shaka.net.NetworkingEngine.PluginPriority.PREFERRED);
}
4 changes: 2 additions & 2 deletions lib/net/http_xhr_plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ shaka.net.HttpXHRPlugin.Xhr_ = window.XMLHttpRequest;


shaka.net.NetworkingEngine.registerScheme('http', shaka.net.HttpXHRPlugin,
shaka.net.NetworkingEngine.PluginPriority.PREFERRED);
shaka.net.NetworkingEngine.PluginPriority.FALLBACK);
shaka.net.NetworkingEngine.registerScheme('https', shaka.net.HttpXHRPlugin,
shaka.net.NetworkingEngine.PluginPriority.PREFERRED);
shaka.net.NetworkingEngine.PluginPriority.FALLBACK);

42 changes: 41 additions & 1 deletion test/test/util/jasmine_fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ jasmine.Fetch.install = function() {
jasmine.Fetch.container_.oldFetch = window.fetch;
jasmine.Fetch.container_.oldHeaders = window.Headers;
jasmine.Fetch.container_.oldAbortController = window.AbortController;
jasmine.Fetch.container_.oldResponse = window.Response;
/** @type {!Response} */
jasmine.Fetch.container_.latestResponse;

window.Headers = /** @type {function (new:Headers,
(!Array<!Array<string>>|Headers|Object<string,string>)=)} */(
Expand All @@ -56,6 +59,9 @@ jasmine.Fetch.install = function() {
window.AbortController = /** @type {function (new:AbortController)} */
(jasmine.Fetch.AbortController);

window.Response = /** @type {function (new:Response)} */
(jasmine.Fetch.Response);

window.fetch = function(input, init) {
// TODO: this does not support input in Request form
let url = /** @type {string} */ (input);
Expand Down Expand Up @@ -121,18 +127,35 @@ jasmine.Fetch.impl_ = function(url, init) {
responseHeaders.append(key, stubbed.response.responseHeaders[key]);
}

let body = {
getReader: () => {
let sequence = [{
done: false, value: {byteLength: 100},
}, {
done: true, value: null,
}];
let read = () => {
return Promise.resolve(sequence.shift());
};
return {read: read};
},
};

// This creates an anonymous object instead of using the
// built-in response constructor, because the fetch API
// does not include a very good constructor for Response.
let response = /** @type {!Response} */ ({
status: stubbed.response.status,
body: body,
headers: responseHeaders,
url: stubbed.response.responseURL || url,
arrayBuffer: function() {
return Promise.resolve(stubbed.response.response);
},
clone: () => response,
});
return Promise.resolve(response);
jasmine.Fetch.container_.latestResponse = response;
return Promise.resolve(jasmine.Fetch.container_.latestResponse);
} else if (stubbed.error) {
return Promise.reject({message: 'fake error'});
} else if (stubbed.timeout) {
Expand Down Expand Up @@ -162,6 +185,7 @@ jasmine.Fetch.uninstall = function() {
window.fetch = jasmine.Fetch.container_.oldFetch;
window.Headers = jasmine.Fetch.container_.oldHeaders;
window.AbortController = jasmine.Fetch.container_.oldAbortController;
window.Response = jasmine.Fetch.container_.oldResponse;
jasmine.Fetch.container_.installed_ = false;
}
};
Expand All @@ -179,6 +203,22 @@ jasmine.Fetch.AbortController = function() {
};


/**
* @constructor
* @struct
*/
jasmine.Fetch.Response = function() {
// Returns a copy of the most recent fake response.
const latest = jasmine.Fetch.container_.latestResponse;
this.status = latest.status;
this.body = latest.body;
this.headers = latest.headers;
this.url = latest.url;
let arrayBuffer = latest.arrayBuffer;
this.arrayBuffer = () => arrayBuffer;
};


/**
* Aborts any request that has been supplied the AbortController's signal.
*/
Expand Down

0 comments on commit cef4ae9

Please sign in to comment.