From 71067afe47ad91970fcf1a91569424e7c0e4f995 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sat, 25 Jul 2026 22:18:15 +0200 Subject: [PATCH] Keep FileReader in DONE state after abort --- .../react-native/Libraries/Blob/FileReader.js | 30 +++++++++++-------- .../Blob/__tests__/FileReader-test.js | 27 +++++++++++++++++ 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/react-native/Libraries/Blob/FileReader.js b/packages/react-native/Libraries/Blob/FileReader.js index 3745c00923a8..996e48a9503f 100644 --- a/packages/react-native/Libraries/Blob/FileReader.js +++ b/packages/react-native/Libraries/Blob/FileReader.js @@ -44,6 +44,7 @@ class FileReader extends EventTarget { _error: ?Error; _result: ?ReaderResult; _aborted: boolean = false; + _readId: number = 0; constructor() { super(); @@ -67,7 +68,9 @@ class FileReader extends EventTarget { } else { this.dispatchEvent(new Event('load')); } - this.dispatchEvent(new Event('loadend')); + if (this._readyState !== LOADING) { + this.dispatchEvent(new Event('loadend')); + } } } @@ -80,11 +83,12 @@ class FileReader extends EventTarget { ); } + const readId = ++this._readId; this._setReadyState(LOADING); NativeFileReaderModule.readAsDataURL(blob.data).then( (text: string) => { - if (this._aborted) { + if (readId !== this._readId) { return; } @@ -95,7 +99,7 @@ class FileReader extends EventTarget { this._setReadyState(DONE); }, error => { - if (this._aborted) { + if (readId !== this._readId) { return; } this._error = error; @@ -113,18 +117,19 @@ class FileReader extends EventTarget { ); } + const readId = ++this._readId; this._setReadyState(LOADING); NativeFileReaderModule.readAsDataURL(blob.data).then( (text: string) => { - if (this._aborted) { + if (readId !== this._readId) { return; } this._result = text; this._setReadyState(DONE); }, error => { - if (this._aborted) { + if (readId !== this._readId) { return; } this._error = error; @@ -142,18 +147,19 @@ class FileReader extends EventTarget { ); } + const readId = ++this._readId; this._setReadyState(LOADING); NativeFileReaderModule.readAsText(blob.data, encoding).then( (text: string) => { - if (this._aborted) { + if (readId !== this._readId) { return; } this._result = text; this._setReadyState(DONE); }, error => { - if (this._aborted) { + if (readId !== this._readId) { return; } this._error = error; @@ -163,14 +169,12 @@ class FileReader extends EventTarget { } 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._result = null; + if (this._readyState === LOADING) { + this._aborted = true; + this._readId++; this._setReadyState(DONE); } - // Reset again after, in case modified in handler - this._reset(); } get readyState(): ReadyState { diff --git a/packages/react-native/Libraries/Blob/__tests__/FileReader-test.js b/packages/react-native/Libraries/Blob/__tests__/FileReader-test.js index 36d55849c487..aafb1c659aba 100644 --- a/packages/react-native/Libraries/Blob/__tests__/FileReader-test.js +++ b/packages/react-native/Libraries/Blob/__tests__/FileReader-test.js @@ -69,6 +69,33 @@ describe('FileReader', function () { reader.abort(); expect(aborted).toBe(true); expect(loadended).toBe(true); + expect(reader.readyState).toBe(FileReader.DONE); + expect(reader.result).toBe(null); + }); + + it('should preserve a read started by an abort handler', async () => { + const reader = new FileReader(); + let loadendCount = 0; + const replacementRead = new Promise(resolve => { + reader.onloadend = () => { + loadendCount++; + resolve(); + }; + }); + reader.onabort = () => { + reader.readAsText(new Blob()); + }; + + reader.readAsText(new Blob()); + reader.abort(); + + expect(reader.readyState).toBe(FileReader.LOADING); + expect(loadendCount).toBe(0); + + await replacementRead; + expect(reader.readyState).toBe(FileReader.DONE); + expect(reader.result).toBe(''); + expect(loadendCount).toBe(1); }); it('should read blob as ArrayBuffer', async () => {