From 950bfd039c860070ffb63b62e2c08d946d8bce46 Mon Sep 17 00:00:00 2001 From: Charlie Croom Date: Sat, 11 May 2019 01:06:55 -0700 Subject: [PATCH] [add] Image.queryCache API Image.queryCache is a React Native method that allows the user to see if a given uri is in the cache. It specifies three return options: disk, memory or both. Choosing both seemed most appropriate since we don't really know and can't confirm. The way Image is implemented, if RNW thinks the image might already be loaded, it displays it immediately. Otherwise there can be a flash of a frame. In some scenarios, if the user chooses to preload and then make an Image element, it would still flash. By adding it to the cache, we can prevent that. Close #1344 --- .../src/exports/Image/__tests__/index-test.js | 29 ++++++++++++++++++- .../src/exports/Image/index.js | 17 ++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/react-native-web/src/exports/Image/__tests__/index-test.js b/packages/react-native-web/src/exports/Image/__tests__/index-test.js index 1c45144b..5aa8d334 100644 --- a/packages/react-native-web/src/exports/Image/__tests__/index-test.js +++ b/packages/react-native-web/src/exports/Image/__tests__/index-test.js @@ -14,6 +14,7 @@ const findImageSurfaceStyle = wrapper => StyleSheet.flatten(wrapper.childAt(0).p describe('components/Image', () => { beforeEach(() => { + ImageUriCache._entries = {}; window.Image = jest.fn(() => ({})); }); @@ -111,8 +112,8 @@ describe('components/Image', () => { }); const onLoadStub = jest.fn(); const uri = 'https://test.com/img.jpg'; - shallow(); ImageUriCache.add(uri); + shallow(); jest.runOnlyPendingTimers(); expect(ImageLoader.load).not.toBeCalled(); expect(onLoadStub).toBeCalled(); @@ -164,6 +165,19 @@ describe('components/Image', () => { expect(component.find('img')).toBeUndefined; }); + test('is set immediately if the image was preloaded', () => { + const uri = 'https://yahoo.com/favicon.ico'; + ImageLoader.load = jest.fn().mockImplementationOnce((_, onLoad, onError) => { + onLoad(); + }); + return Image.prefetch(uri).then(() => { + const source = { uri }; + const component = shallow(, { disableLifecycleMethods: true }); + expect(component.find('img').prop('src')).toBe(uri); + ImageUriCache.remove(uri); + }); + }); + test('is set immediately if the image has already been loaded', () => { const uriOne = 'https://google.com/favicon.ico'; const uriTwo = 'https://twitter.com/favicon.ico'; @@ -247,4 +261,17 @@ describe('components/Image', () => { const component = shallow(); expect(component.prop('onResponderGrant')).toBe(fn); }); + + test('queryCache', () => { + const uriOne = 'https://google.com/favicon.ico'; + const uriTwo = 'https://twitter.com/favicon.ico'; + ImageUriCache.add(uriOne); + ImageUriCache.add(uriTwo); + return Image.queryCache([uriOne, uriTwo, 'oops']).then(res => { + expect(res).toEqual({ + [uriOne]: 'disk/memory', + [uriTwo]: 'disk/memory' + }); + }); + }); }); diff --git a/packages/react-native-web/src/exports/Image/index.js b/packages/react-native-web/src/exports/Image/index.js index 25ee74e1..5695ecd9 100644 --- a/packages/react-native-web/src/exports/Image/index.js +++ b/packages/react-native-web/src/exports/Image/index.js @@ -130,7 +130,22 @@ class Image extends Component<*, State> { } static prefetch(uri) { - return ImageLoader.prefetch(uri); + return ImageLoader.prefetch(uri).then(() => { + // Add the uri to the cache so it can be immediately displayed when used + // but also immediately remove it to correctly reflect that it has no active references + ImageUriCache.add(uri); + ImageUriCache.remove(uri); + }); + } + + static queryCache(uris) { + const result = {}; + uris.forEach(u => { + if (ImageUriCache.has(u)) { + result[u] = 'disk/memory'; + } + }); + return Promise.resolve(result); } _filterId = 0;