Correct semantics for XMLHttpRequest.responseText
Summary: Accessing the `responseText` property when `responseType` is not `''` or `'text'` should throw. Also, the property is read-only. **Test Plan:** UIExplorer example, unit tests Closes https://github.com/facebook/react-native/pull/7284 Differential Revision: D3366893 fbshipit-source-id: a4cf5ebabcd1e03d6e2dc9d51230982922746c11
This commit is contained in:
parent
16a97c8027
commit
e29350214a
|
@ -85,7 +85,6 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
|
|
||||||
readyState: number = UNSENT;
|
readyState: number = UNSENT;
|
||||||
responseHeaders: ?Object;
|
responseHeaders: ?Object;
|
||||||
responseText: string = '';
|
|
||||||
status: number = 0;
|
status: number = 0;
|
||||||
timeout: number = 0;
|
timeout: number = 0;
|
||||||
responseURL: ?string;
|
responseURL: ?string;
|
||||||
|
@ -103,6 +102,7 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
_method: ?string = null;
|
_method: ?string = null;
|
||||||
_response: string | ?Object;
|
_response: string | ?Object;
|
||||||
_responseType: ResponseType;
|
_responseType: ResponseType;
|
||||||
|
_responseText: string = '';
|
||||||
_sent: boolean;
|
_sent: boolean;
|
||||||
_url: ?string = null;
|
_url: ?string = null;
|
||||||
_timedOut: boolean = false;
|
_timedOut: boolean = false;
|
||||||
|
@ -116,7 +116,6 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
_reset(): void {
|
_reset(): void {
|
||||||
this.readyState = this.UNSENT;
|
this.readyState = this.UNSENT;
|
||||||
this.responseHeaders = undefined;
|
this.responseHeaders = undefined;
|
||||||
this.responseText = '';
|
|
||||||
this.status = 0;
|
this.status = 0;
|
||||||
delete this.responseURL;
|
delete this.responseURL;
|
||||||
|
|
||||||
|
@ -125,6 +124,7 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
this._cachedResponse = undefined;
|
this._cachedResponse = undefined;
|
||||||
this._hasError = false;
|
this._hasError = false;
|
||||||
this._headers = {};
|
this._headers = {};
|
||||||
|
this._responseText = '';
|
||||||
this._responseType = '';
|
this._responseType = '';
|
||||||
this._sent = false;
|
this._sent = false;
|
||||||
this._lowerCaseResponseHeaders = {};
|
this._lowerCaseResponseHeaders = {};
|
||||||
|
@ -148,7 +148,9 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
}
|
}
|
||||||
if (!SUPPORTED_RESPONSE_TYPES.hasOwnProperty(responseType)) {
|
if (!SUPPORTED_RESPONSE_TYPES.hasOwnProperty(responseType)) {
|
||||||
warning(
|
warning(
|
||||||
`The provided value '${responseType}' is not a valid 'responseType'.`);
|
false,
|
||||||
|
`The provided value '${responseType}' is not a valid 'responseType'.`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,13 +162,27 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
this._responseType = responseType;
|
this._responseType = responseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $FlowIssue #10784535
|
||||||
|
get responseText(): string {
|
||||||
|
if (this._responseType !== '' && this._responseType !== 'text') {
|
||||||
|
throw new Error(
|
||||||
|
`The 'responseText' property is only available if 'responseType' ` +
|
||||||
|
`is set to '' or 'text', but it is '${this._responseType}'.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.readyState < LOADING) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return this._responseText;
|
||||||
|
}
|
||||||
|
|
||||||
// $FlowIssue #10784535
|
// $FlowIssue #10784535
|
||||||
get response(): Response {
|
get response(): Response {
|
||||||
const {responseType} = this;
|
const {responseType} = this;
|
||||||
if (responseType === '' || responseType === 'text') {
|
if (responseType === '' || responseType === 'text') {
|
||||||
return this.readyState < LOADING || this._hasError
|
return this.readyState < LOADING || this._hasError
|
||||||
? ''
|
? ''
|
||||||
: this.responseText;
|
: this._responseText;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.readyState !== DONE) {
|
if (this.readyState !== DONE) {
|
||||||
|
@ -177,26 +193,26 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
return this._cachedResponse;
|
return this._cachedResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.responseType) {
|
switch (this._responseType) {
|
||||||
case 'document':
|
case 'document':
|
||||||
this._cachedResponse = null;
|
this._cachedResponse = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'arraybuffer':
|
case 'arraybuffer':
|
||||||
this._cachedResponse = toArrayBuffer(
|
this._cachedResponse = toArrayBuffer(
|
||||||
this.responseText, this.getResponseHeader('content-type') || '');
|
this._responseText, this.getResponseHeader('content-type') || '');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'blob':
|
case 'blob':
|
||||||
this._cachedResponse = new global.Blob(
|
this._cachedResponse = new global.Blob(
|
||||||
[this.responseText],
|
[this._responseText],
|
||||||
{type: this.getResponseHeader('content-type') || ''}
|
{type: this.getResponseHeader('content-type') || ''}
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'json':
|
case 'json':
|
||||||
try {
|
try {
|
||||||
this._cachedResponse = JSON.parse(this.responseText);
|
this._cachedResponse = JSON.parse(this._responseText);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
this._cachedResponse = null;
|
this._cachedResponse = null;
|
||||||
}
|
}
|
||||||
|
@ -226,7 +242,12 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_didReceiveResponse(requestId: number, status: number, responseHeaders: ?Object, responseURL: ?string): void {
|
__didReceiveResponse(
|
||||||
|
requestId: number,
|
||||||
|
status: number,
|
||||||
|
responseHeaders: ?Object,
|
||||||
|
responseURL: ?string
|
||||||
|
): void {
|
||||||
if (requestId === this._requestId) {
|
if (requestId === this._requestId) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.setResponseHeaders(responseHeaders);
|
this.setResponseHeaders(responseHeaders);
|
||||||
|
@ -239,12 +260,12 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_didReceiveData(requestId: number, responseText: string): void {
|
__didReceiveData(requestId: number, responseText: string): void {
|
||||||
if (requestId === this._requestId) {
|
if (requestId === this._requestId) {
|
||||||
if (!this.responseText) {
|
if (!this._responseText) {
|
||||||
this.responseText = responseText;
|
this._responseText = responseText;
|
||||||
} else {
|
} else {
|
||||||
this.responseText += responseText;
|
this._responseText += responseText;
|
||||||
}
|
}
|
||||||
this._cachedResponse = undefined; // force lazy recomputation
|
this._cachedResponse = undefined; // force lazy recomputation
|
||||||
this.setReadyState(this.LOADING);
|
this.setReadyState(this.LOADING);
|
||||||
|
@ -252,10 +273,14 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// exposed for testing
|
// exposed for testing
|
||||||
__didCompleteResponse(requestId: number, error: string, timeOutError: boolean): void {
|
__didCompleteResponse(
|
||||||
|
requestId: number,
|
||||||
|
error: string,
|
||||||
|
timeOutError: boolean
|
||||||
|
): void {
|
||||||
if (requestId === this._requestId) {
|
if (requestId === this._requestId) {
|
||||||
if (error) {
|
if (error) {
|
||||||
this.responseText = error;
|
this._responseText = error;
|
||||||
this._hasError = true;
|
this._hasError = true;
|
||||||
if (timeOutError) {
|
if (timeOutError) {
|
||||||
this._timedOut = true;
|
this._timedOut = true;
|
||||||
|
@ -330,11 +355,11 @@ class XMLHttpRequest extends EventTarget(...XHR_EVENTS) {
|
||||||
));
|
));
|
||||||
this._subscriptions.push(RCTNetworking.addListener(
|
this._subscriptions.push(RCTNetworking.addListener(
|
||||||
'didReceiveNetworkResponse',
|
'didReceiveNetworkResponse',
|
||||||
(args) => this._didReceiveResponse(...args)
|
(args) => this.__didReceiveResponse(...args)
|
||||||
));
|
));
|
||||||
this._subscriptions.push(RCTNetworking.addListener(
|
this._subscriptions.push(RCTNetworking.addListener(
|
||||||
'didReceiveNetworkData',
|
'didReceiveNetworkData',
|
||||||
(args) => this._didReceiveData(...args)
|
(args) => this.__didReceiveData(...args)
|
||||||
));
|
));
|
||||||
this._subscriptions.push(RCTNetworking.addListener(
|
this._subscriptions.push(RCTNetworking.addListener(
|
||||||
'didCompleteNetworkResponse',
|
'didCompleteNetworkResponse',
|
||||||
|
|
|
@ -57,6 +57,7 @@ describe('XMLHttpRequest', function(){
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should transition readyState correctly', function() {
|
it('should transition readyState correctly', function() {
|
||||||
|
|
||||||
expect(xhr.readyState).toBe(xhr.UNSENT);
|
expect(xhr.readyState).toBe(xhr.UNSENT);
|
||||||
|
|
||||||
xhr.open('GET', 'blabla');
|
xhr.open('GET', 'blabla');
|
||||||
|
@ -66,6 +67,43 @@ describe('XMLHttpRequest', function(){
|
||||||
expect(xhr.readyState).toBe(xhr.OPENED);
|
expect(xhr.readyState).toBe(xhr.OPENED);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should expose responseType correctly', function() {
|
||||||
|
expect(xhr.responseType).toBe('');
|
||||||
|
|
||||||
|
// Setting responseType to an unsupported value has no effect.
|
||||||
|
xhr.responseType = 'arrayblobbuffertextfile';
|
||||||
|
expect(xhr.responseType).toBe('');
|
||||||
|
|
||||||
|
xhr.responseType = 'arraybuffer';
|
||||||
|
expect(xhr.responseType).toBe('arraybuffer');
|
||||||
|
|
||||||
|
// Can't change responseType after first data has been received.
|
||||||
|
xhr.__didReceiveData(1, 'Some data');
|
||||||
|
expect(() => { xhr.responseType = 'text'; }).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should expose responseText correctly', function() {
|
||||||
|
xhr.responseType = '';
|
||||||
|
expect(xhr.responseText).toBe('');
|
||||||
|
expect(xhr.response).toBe('');
|
||||||
|
|
||||||
|
xhr.responseType = 'arraybuffer';
|
||||||
|
expect(() => xhr.responseText).toThrow();
|
||||||
|
expect(xhr.response).toBe(null);
|
||||||
|
|
||||||
|
xhr.responseType = 'text';
|
||||||
|
expect(xhr.responseText).toBe('');
|
||||||
|
expect(xhr.response).toBe('');
|
||||||
|
|
||||||
|
// responseText is read-only.
|
||||||
|
expect(() => { xhr.responseText = 'hi'; }).toThrow();
|
||||||
|
expect(xhr.responseText).toBe('');
|
||||||
|
expect(xhr.response).toBe('');
|
||||||
|
|
||||||
|
xhr.__didReceiveData(1, 'Some data');
|
||||||
|
expect(xhr.responseText).toBe('Some data');
|
||||||
|
});
|
||||||
|
|
||||||
it('should call ontimeout function when the request times out', function(){
|
it('should call ontimeout function when the request times out', function(){
|
||||||
xhr.__didCompleteResponse(1, 'Timeout', true);
|
xhr.__didCompleteResponse(1, 'Timeout', true);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue