mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Summary: This PR prevents blob data from being prematurely de-allocated in native code when using slice to create views into an existing blob. Currently, whenever a new blob is created via createFromOptions, BlobManager.js creates a new blobCollector object since options.__collector is never provided. https://github.com/facebook/react-native/blob/dc80b2dcb52fadec6a573a9dd1824393f8c29fdc/Libraries/Blob/BlobManager.js#L115-L123 When the reference to a blobCollector is garbage collected, the corresponding native memory for the blob data is de-allocated. https://github.com/facebook/react-native/blob/27651720b40cab564a0cbd41be56a02584e0c73a/Libraries/Blob/RCTBlobCollector.mm#L19-L25 Since, `blob.slice()` is supposed to create a new view onto the same binary data as the original blob, we need to re-use the same collector object when slicing so that it is not GC'd until the last reference to the binary data is no longer reachable. Currently, since each blob slice gets a new blobCollector object, the memory is de-allocated when the first blob is GC'd. Fixes https://github.com/facebook/react-native/issues/29970 Fixes https://github.com/facebook/react-native/issues/27857 ## Changelog <!-- Help reviewers and the release process by writing your own changelog entry. For an example, see: https://github.com/facebook/react-native/wiki/Changelog --> [iOS] [Fixed] - Blob data is no longer prematurely deallocated when using blob.slice Pull Request resolved: https://github.com/facebook/react-native/pull/31392 Test Plan: I could use help coming up with a test plan here. I could add a referential equality check for the blob.data.__collector in `Blob-test` but it doesn't seem quite right to be testing the implementation detail there. Reviewed By: javache Differential Revision: D41730782 Pulled By: cortinico fbshipit-source-id: 5671ae2c69908f4c9acb5d203ba198b41b421294
154 lines
4.3 KiB
JavaScript
154 lines
4.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow strict-local
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import type {BlobData, BlobOptions} from './BlobTypes';
|
|
|
|
/**
|
|
* Opaque JS representation of some binary data in native.
|
|
*
|
|
* The API is modeled after the W3C Blob API, with one caveat
|
|
* regarding explicit deallocation. Refer to the `close()`
|
|
* method for further details.
|
|
*
|
|
* Example usage in a React component:
|
|
*
|
|
* class WebSocketImage extends React.Component {
|
|
* state = {blob: null};
|
|
* componentDidMount() {
|
|
* let ws = this.ws = new WebSocket(...);
|
|
* ws.binaryType = 'blob';
|
|
* ws.onmessage = (event) => {
|
|
* if (this.state.blob) {
|
|
* this.state.blob.close();
|
|
* }
|
|
* this.setState({blob: event.data});
|
|
* };
|
|
* }
|
|
* componentUnmount() {
|
|
* if (this.state.blob) {
|
|
* this.state.blob.close();
|
|
* }
|
|
* this.ws.close();
|
|
* }
|
|
* render() {
|
|
* if (!this.state.blob) {
|
|
* return <View />;
|
|
* }
|
|
* return <Image source={{uri: URL.createObjectURL(this.state.blob)}} />;
|
|
* }
|
|
* }
|
|
*
|
|
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob
|
|
*/
|
|
class Blob {
|
|
_data: ?BlobData;
|
|
|
|
/**
|
|
* Constructor for JS consumers.
|
|
* Currently we only support creating Blobs from other Blobs.
|
|
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob
|
|
*/
|
|
constructor(parts: Array<Blob | string> = [], options?: BlobOptions) {
|
|
const BlobManager = require('./BlobManager');
|
|
this.data = BlobManager.createFromParts(parts, options).data;
|
|
}
|
|
|
|
/*
|
|
* This method is used to create a new Blob object containing
|
|
* the data in the specified range of bytes of the source Blob.
|
|
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice
|
|
*/
|
|
// $FlowFixMe[unsafe-getters-setters]
|
|
set data(data: ?BlobData) {
|
|
this._data = data;
|
|
}
|
|
|
|
// $FlowFixMe[unsafe-getters-setters]
|
|
get data(): BlobData {
|
|
if (!this._data) {
|
|
throw new Error('Blob has been closed and is no longer available');
|
|
}
|
|
|
|
return this._data;
|
|
}
|
|
|
|
slice(start?: number, end?: number): Blob {
|
|
const BlobManager = require('./BlobManager');
|
|
let {offset, size} = this.data;
|
|
|
|
if (typeof start === 'number') {
|
|
if (start > size) {
|
|
// $FlowFixMe[reassign-const]
|
|
start = size;
|
|
}
|
|
offset += start;
|
|
size -= start;
|
|
|
|
if (typeof end === 'number') {
|
|
if (end < 0) {
|
|
// $FlowFixMe[reassign-const]
|
|
end = this.size + end;
|
|
}
|
|
size = end - start;
|
|
}
|
|
}
|
|
return BlobManager.createFromOptions({
|
|
blobId: this.data.blobId,
|
|
offset,
|
|
size,
|
|
/* Since `blob.slice()` creates a new view onto the same binary
|
|
* data as the original blob, we should re-use the same collector
|
|
* object so that the underlying resource gets deallocated when
|
|
* the last view into the data is released, not the first.
|
|
*/
|
|
__collector: this.data.__collector,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This method is in the standard, but not actually implemented by
|
|
* any browsers at this point. It's important for how Blobs work in
|
|
* React Native, however, since we cannot de-allocate resources automatically,
|
|
* so consumers need to explicitly de-allocate them.
|
|
*
|
|
* Note that the semantics around Blobs created via `blob.slice()`
|
|
* and `new Blob([blob])` are different. `blob.slice()` creates a
|
|
* new *view* onto the same binary data, so calling `close()` on any
|
|
* of those views is enough to deallocate the data, whereas
|
|
* `new Blob([blob, ...])` actually copies the data in memory.
|
|
*/
|
|
close() {
|
|
const BlobManager = require('./BlobManager');
|
|
BlobManager.release(this.data.blobId);
|
|
this.data = null;
|
|
}
|
|
|
|
/**
|
|
* Size of the data contained in the Blob object, in bytes.
|
|
*/
|
|
// $FlowFixMe[unsafe-getters-setters]
|
|
get size(): number {
|
|
return this.data.size;
|
|
}
|
|
|
|
/*
|
|
* String indicating the MIME type of the data contained in the Blob.
|
|
* If the type is unknown, this string is empty.
|
|
*/
|
|
// $FlowFixMe[unsafe-getters-setters]
|
|
get type(): string {
|
|
return this.data.type || '';
|
|
}
|
|
}
|
|
|
|
module.exports = Blob;
|