Skip to content

Commit

Permalink
Set up test to validate refactor of XHR, FileReader and WebSocket cla…
Browse files Browse the repository at this point in the history
…sses to use the built-in EventTarget implementation (facebook#48930)

Summary:
Pull Request resolved: facebook#48930

Changelog: [internal]

This creates new versions of `XMLHttpRequest`, `FileReader` and `WebSocket` that extend the new built-in `EventTarget` implementation, instead of the implementation from the `event-target-shim` package.

It also sets up a test to choose between the 2 implementations at runtime to verify correctness and performance. This doesn't use the RN feature flags infra because we use this flag very early on startup, before we have a chance to set overrides. We could use a native feature flag instead but it'd slow down the rollout of the test.

Reviewed By: yungsters

Differential Revision: D68625226

fbshipit-source-id: bff715c43a237b65d5a02a3fdb56f3275689ea46
  • Loading branch information
rubennorte authored and facebook-github-bot committed Jan 29, 2025
1 parent d80284c commit ea1260a
Show file tree
Hide file tree
Showing 15 changed files with 3,261 additions and 1,397 deletions.
182 changes: 8 additions & 174 deletions packages/react-native/Libraries/Blob/FileReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,179 +8,13 @@
* @format
*/

import type Blob from './Blob';
import typeof FileReader from './FileReader_old';

import NativeFileReaderModule from './NativeFileReaderModule';
import {toByteArray} from 'base64-js';
import EventTarget from 'event-target-shim';
// Use a global instead of a flag from ReactNativeFeatureFlags because this will
// be read before apps have a chance to set overrides.
const useBuiltInEventTarget = global.RN$useBuiltInEventTarget?.();

type ReadyState =
| 0 // EMPTY
| 1 // LOADING
| 2; // DONE

type ReaderResult = string | ArrayBuffer;

const READER_EVENTS = [
'abort',
'error',
'load',
'loadstart',
'loadend',
'progress',
];

const EMPTY = 0;
const LOADING = 1;
const DONE = 2;

class FileReader extends (EventTarget(...READER_EVENTS): typeof EventTarget) {
static EMPTY: number = EMPTY;
static LOADING: number = LOADING;
static DONE: number = DONE;

EMPTY: number = EMPTY;
LOADING: number = LOADING;
DONE: number = DONE;

_readyState: ReadyState;
_error: ?Error;
_result: ?ReaderResult;
_aborted: boolean = false;

constructor() {
super();
this._reset();
}

_reset(): void {
this._readyState = EMPTY;
this._error = null;
this._result = null;
}

_setReadyState(newState: ReadyState) {
this._readyState = newState;
this.dispatchEvent({type: 'readystatechange'});
if (newState === DONE) {
if (this._aborted) {
this.dispatchEvent({type: 'abort'});
} else if (this._error) {
this.dispatchEvent({type: 'error'});
} else {
this.dispatchEvent({type: 'load'});
}
this.dispatchEvent({type: 'loadend'});
}
}

readAsArrayBuffer(blob: ?Blob): void {
this._aborted = false;

if (blob == null) {
throw new TypeError(
"Failed to execute 'readAsArrayBuffer' on 'FileReader': parameter 1 is not of type 'Blob'",
);
}

NativeFileReaderModule.readAsDataURL(blob.data).then(
(text: string) => {
if (this._aborted) {
return;
}

const base64 = text.split(',')[1];
const typedArray = toByteArray(base64);

this._result = typedArray.buffer;
this._setReadyState(DONE);
},
error => {
if (this._aborted) {
return;
}
this._error = error;
this._setReadyState(DONE);
},
);
}

readAsDataURL(blob: ?Blob): void {
this._aborted = false;

if (blob == null) {
throw new TypeError(
"Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'",
);
}

NativeFileReaderModule.readAsDataURL(blob.data).then(
(text: string) => {
if (this._aborted) {
return;
}
this._result = text;
this._setReadyState(DONE);
},
error => {
if (this._aborted) {
return;
}
this._error = error;
this._setReadyState(DONE);
},
);
}

readAsText(blob: ?Blob, encoding: string = 'UTF-8'): void {
this._aborted = false;

if (blob == null) {
throw new TypeError(
"Failed to execute 'readAsText' on 'FileReader': parameter 1 is not of type 'Blob'",
);
}

NativeFileReaderModule.readAsText(blob.data, encoding).then(
(text: string) => {
if (this._aborted) {
return;
}
this._result = text;
this._setReadyState(DONE);
},
error => {
if (this._aborted) {
return;
}
this._error = error;
this._setReadyState(DONE);
},
);
}

abort() {
this._aborted = true;
// only call onreadystatechange if there is something to abort, as per spec
if (this._readyState !== EMPTY && this._readyState !== DONE) {
this._reset();
this._setReadyState(DONE);
}
// Reset again after, in case modified in handler
this._reset();
}

get readyState(): ReadyState {
return this._readyState;
}

get error(): ?Error {
return this._error;
}

get result(): ?ReaderResult {
return this._result;
}
}

export default FileReader;
export default (useBuiltInEventTarget
? // $FlowExpectedError[incompatible-cast]
require('./FileReader_new').default
: require('./FileReader_old').default) as FileReader;
Loading

0 comments on commit ea1260a

Please sign in to comment.