2017-03-22 Ryosuke Niwa <rniwa@webkit.org>
+ UploadedFile should support a callback for upload progress
+ https://bugs.webkit.org/show_bug.cgi?id=169977
+
+ Reviewed by Andreas Kling.
+
+ Added a new option dictionary to CommonRemoteAPI.sendHttpRequest with uploadProgressCallback
+
+ Moved request headers and responseHandler callback in NodeRemoteAPI to this dictionary,
+ and updated the tests which relied on this code.
+
+ * public/shared/common-remote.js:
+ (CommonRemoteAPI.prototype.postJSON):
+ (CommonRemoteAPI.prototype.postJSONWithStatus):
+ (CommonRemoteAPI.prototype.postFormData):
+ (CommonRemoteAPI.prototype.postFormDataWithStatus):
+ * public/v3/privileged-api.js:
+ (PrivilegedAPI.prototype.sendRequest):
+ * public/v3/remote.js:
+ (BrowserRemoteAPI.prototype.sendHttpRequest):
+ (BrowserRemoteAPI.prototype.sendHttpRequestWithFormData):
+ (BrowserRemoteAPI):
+ * server-tests/api-uploaded-file.js:
+ * tools/js/remote.js:
+ (NodeRemoteAPI.prototype.sendHttpRequest):
+ (NodeRemoteAPI.prototype.sendHttpRequestWithFormData):
+ (NodeRemoteAPI):
+
+2017-03-22 Ryosuke Niwa <rniwa@webkit.org>
+
ComponentBase should enqueue itself to render when it becomes connected
https://bugs.webkit.org/show_bug.cgi?id=169905
"use strict";
class CommonRemoteAPI {
- postJSON(path, data)
+ postJSON(path, data, options)
{
- return this._asJSON(this.sendHttpRequest(path, 'POST', 'application/json', JSON.stringify(data || {})));
+ return this._asJSON(this.sendHttpRequest(path, 'POST', 'application/json', JSON.stringify(data || {}), options));
}
- postJSONWithStatus(path, data)
+ postJSONWithStatus(path, data, options)
{
- return this._checkStatus(this.postJSON(path, data));
+ return this._checkStatus(this.postJSON(path, data, options));
}
- postFormData(path, data)
+ postFormData(path, data, options)
{
const formData = new FormData();
for (let key in data)
formData.append(key, data[key]);
- return this._asJSON(this.sendHttpRequestWithFormData(path, formData));
+ return this._asJSON(this.sendHttpRequestWithFormData(path, formData, options));
}
- postFormDataWithStatus(path, data)
+ postFormDataWithStatus(path, data, options)
{
- return this._checkStatus(this.postFormData(path, data));
+ return this._checkStatus(this.postFormData(path, data, options));
}
getJSON(path)
return this._checkStatus(this.getJSON(path));
}
- sendHttpRequest(path, method, contentType, content)
+ sendHttpRequest(path, method, contentType, content, options = {})
{
throw 'NotImplemented';
}
- sendHttpRequestWithFormData(path, formData)
+ sendHttpRequestWithFormData(path, formData, options = {})
{
throw 'NotImplemented';
}
this.ensureNamedStaticMap('sha256')[object.sha256] = this;
}
- static uploadFile(file)
+ static uploadFile(file, uploadProgressCallback = null)
{
- return PrivilegedAPI.sendRequest('upload-file', {'newFile': file}, {useFormData: true}).then((rawData) => {
+ return PrivilegedAPI.sendRequest('upload-file', {'newFile': file}, {useFormData: true, uploadProgressCallback}).then((rawData) => {
return UploadedFile.ensureSingleton(rawData['uploadedFile'].id, rawData['uploadedFile']);
});
}
const fullPath = '/privileged-api/' + path;
const post = options.useFormData
- ? () => RemoteAPI.postFormDataWithStatus(fullPath, clonedData)
- : () => RemoteAPI.postJSONWithStatus(fullPath, clonedData);
+ ? () => RemoteAPI.postFormDataWithStatus(fullPath, clonedData, options)
+ : () => RemoteAPI.postJSONWithStatus(fullPath, clonedData, options);
return this.requestCSRFToken().then((token) => {
clonedData['token'] = token;
class BrowserRemoteAPI extends CommonRemoteAPI {
- sendHttpRequest(path, method, contentType, content)
+ sendHttpRequest(path, method, contentType, content, options = {})
{
console.assert(!path.startsWith('http:') && !path.startsWith('https:') && !path.startsWith('file:'));
xhr.onabort = onerror;
xhr.onerror = onerror;
+ if (content && options.uploadProgressCallback) {
+ xhr.upload.onprogress = (event) => {
+ options.uploadProgressCallback(event.lengthComputable ? {total: event.total, loaded: event.loaded} : null);
+ }
+ }
+
xhr.open(method, path, true);
if (contentType)
xhr.setRequestHeader('Content-Type', contentType);
});
}
- sendHttpRequestWithFormData(path, formData)
+ sendHttpRequestWithFormData(path, formData, options)
{
- return this.sendHttpRequest(path, 'POST', null, formData); // Content-type is set by the browser.
+ return this.sendHttpRequest(path, 'POST', null, formData, options); // Content-type is set by the browser.
}
}
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
- return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {'Range': 'bytes=5-'});
+ return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=5-'}});
}).then((response) => {
const headers = response.headers;
assert.equal(response.statusCode, 206);
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
- return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {'Range': 'bytes=4-9'});
+ return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=4-9'}});
}).then((response) => {
const headers = response.headers;
assert.equal(response.statusCode, 206);
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
- return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {'Range': 'bytes=-4'});
+ return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=-4'}});
}).then((response) => {
const headers = response.headers;
assert.equal(response.statusCode, 206);
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
- return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {'Range': 'bytes=-100'});
+ return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=-100'}});
}).then((response) => {
const headers = response.headers;
assert.equal(response.statusCode, 206);
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
- return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {'Range': 'bytes=12-'})
+ return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=12-'}})
.then(() => assert(false, 'should never be reached'), (error) => assert.equal(error, 416));
});
});
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
- return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {'Range': 'bytes=2-1'})
+ return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=2-1'}})
.then(() => assert(false, 'should never be reached'), (error) => assert.equal(error, 416));
});
});
assert.equal(response.statusCode, 200);
assert.equal(response.responseText, 'some content');
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null,
- {'Range': 'bytes = 9-10', 'If-Range': response.headers['last-modified']});
+ {headers: {'Range': 'bytes = 9-10', 'If-Range': response.headers['last-modified']}});
}).then((response) => {
const headers = response.headers;
assert.equal(response.statusCode, 206);
assert.equal(response.statusCode, 200);
assert.equal(response.responseText, 'some content');
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null,
- {'Range': 'bytes = 9-10', 'If-Range': response.headers['etag']});
+ {headers: {'Range': 'bytes = 9-10', 'If-Range': response.headers['etag']}});
}).then((response) => {
const headers = response.headers;
assert.equal(response.statusCode, 206);
}).then((response) => {
id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null,
- {'Range': `bytes = ${startByte}-${endByte}`}, responseHandler);
+ {headers: {'Range': `bytes = ${startByte}-${endByte}`}, responseHandler});
}).then((response) => {
const headers = response.headers;
assert.equal(response.statusCode, 206);
});
}
- sendHttpRequest(path, method, contentType, content, headers = {}, responseHandler = null)
+ sendHttpRequest(path, method, contentType, content, requestOptions = {})
{
let server = this._server;
return new Promise((resolve, reject) => {
let request = (server.scheme == 'http' ? http : https).request(options, (response) => {
let responseText = '';
- if (responseHandler)
- responseHandler(response);
+ if (requestOptions.responseHandler)
+ requestOptions.responseHandler(response);
else {
response.setEncoding('utf8');
response.on('data', (chunk) => { responseText += chunk; });
if (this._cookies.size)
request.setHeader('Cookie', Array.from(this._cookies.keys()).map((key) => `${key}=${this._cookies.get(key)}`).join('; '));
- for (let headerName in headers)
- request.setHeader(headerName, headers[headerName]);
+ if (requestOptions.headers) {
+ const requestHeaders = requestOptions.headers;
+ for (let headerName in requestHeaders)
+ request.setHeader(headerName, requestHeaders[headerName]);
+ }
if (content instanceof Function)
content(request);
});
}
- sendHttpRequestWithFormData(path, formData)
+ sendHttpRequestWithFormData(path, formData, options)
{
return this.sendHttpRequest(path, 'POST', `multipart/form-data; boundary=${formData.getBoundary()}`, (request) => {
formData.pipe(request);
- });
+ }, options);
}
};