@atlaskit/media-file-preview 0.16.3 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/media-file-preview
2
2
 
3
+ ## 0.16.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`be94d5f05b8ae`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/be94d5f05b8ae) -
8
+ Fix media image blob cache to only evict blobs when they are no longer referenced
9
+ - Updated dependencies
10
+
3
11
  ## 0.16.3
4
12
 
5
13
  ### Patch Changes
@@ -34,6 +34,14 @@ var CardPreviewCacheImpl = exports.CardPreviewCacheImpl = /*#__PURE__*/(0, _crea
34
34
  (0, _defineProperty2.default)(this, "clear", function () {
35
35
  _this.previewCache.clear();
36
36
  });
37
+ (0, _defineProperty2.default)(this, "acquire", function (id, mode) {
38
+ var cacheKey = getCacheKey(id, mode);
39
+ _this.previewCache.acquire(cacheKey);
40
+ });
41
+ (0, _defineProperty2.default)(this, "release", function (id, mode) {
42
+ var cacheKey = getCacheKey(id, mode);
43
+ _this.previewCache.release(cacheKey);
44
+ });
37
45
  this.previewCache = previewCache;
38
46
  });
39
47
  var mediaFilePreviewCache = exports.mediaFilePreviewCache = new CardPreviewCacheImpl((0, _objectURLCache.createObjectURLCache)());
@@ -5,52 +5,103 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.createObjectURLCache = exports.PREVIEW_CACHE_LRU_SIZE = exports.ObjectURLCache = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
8
9
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
9
10
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
11
  var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
11
12
  var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
12
13
  var _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get"));
13
14
  var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
14
- var _eventemitter = require("eventemitter2");
15
15
  var _lru_map = require("lru_map");
16
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
16
17
  function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
17
18
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
18
19
  function _superPropGet(t, o, e, r) { var p = (0, _get2.default)((0, _getPrototypeOf2.default)(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; }
19
20
  var PREVIEW_CACHE_LRU_SIZE = exports.PREVIEW_CACHE_LRU_SIZE = 50;
20
- var ExtendedLRUCache = /*#__PURE__*/function (_LRUMap) {
21
- function ExtendedLRUCache(limit) {
21
+ /**
22
+ * LRU cache that checks whether the oldest entry should be evicted
23
+ * this means that the cache can grow past the softLimit if `shouldEvict` returns false.
24
+ *
25
+ * When `platform_media_safe_blob_url_eviction` is ON the oldest
26
+ * entry is kept if `shouldEvict` returns false.
27
+ * When the flag is OFF the oldest entry is always evicted.
28
+ */
29
+ var EvictionLRUCache = /*#__PURE__*/function (_LRUMap) {
30
+ function EvictionLRUCache(softLimit, options) {
22
31
  var _this;
23
- (0, _classCallCheck2.default)(this, ExtendedLRUCache);
24
- _this = _callSuper(this, ExtendedLRUCache, [limit]);
25
- _this.eventEmitter = new _eventemitter.EventEmitter2();
32
+ (0, _classCallCheck2.default)(this, EvictionLRUCache);
33
+ _this = _callSuper(this, EvictionLRUCache, [softLimit]);
34
+ _this.shouldEvict = options === null || options === void 0 ? void 0 : options.shouldEvict;
35
+ _this.onEvict = options === null || options === void 0 ? void 0 : options.onEvict;
26
36
  return _this;
27
37
  }
28
- (0, _inherits2.default)(ExtendedLRUCache, _LRUMap);
29
- return (0, _createClass2.default)(ExtendedLRUCache, [{
38
+ (0, _inherits2.default)(EvictionLRUCache, _LRUMap);
39
+ return (0, _createClass2.default)(EvictionLRUCache, [{
30
40
  key: "shift",
31
41
  value: function shift() {
32
- var entry = _superPropGet(ExtendedLRUCache, "shift", this, 3)([]);
33
- this.eventEmitter.emit('shift', entry);
42
+ if ((0, _platformFeatureFlags.fg)('platform_media_safe_blob_url_eviction')) {
43
+ var oldest = this.oldest;
44
+ if (oldest && this.shouldEvict && !this.shouldEvict([oldest.key, oldest.value])) {
45
+ return undefined;
46
+ }
47
+ }
48
+ var entry = _superPropGet(EvictionLRUCache, "shift", this, 3)([]);
49
+ if (entry && this.onEvict) {
50
+ this.onEvict(entry);
51
+ }
34
52
  return entry;
35
53
  }
36
- }, {
37
- key: "on",
38
- value: function on(event, callback) {
39
- this.eventEmitter.on(event, callback);
40
- }
41
54
  }]);
42
55
  }(_lru_map.LRUMap);
43
56
  var ObjectURLCache = exports.ObjectURLCache = /*#__PURE__*/function () {
44
57
  function ObjectURLCache(size) {
58
+ var _this2 = this;
45
59
  (0, _classCallCheck2.default)(this, ObjectURLCache);
46
- this.cache = new ExtendedLRUCache(size);
47
- this.cache.on('shift', function (entry) {
48
- if (entry && entry[1].dataURI) {
49
- URL.revokeObjectURL(entry[1].dataURI);
60
+ (0, _defineProperty2.default)(this, "activeRefs", new Map());
61
+ this.cache = new EvictionLRUCache(size, {
62
+ shouldEvict: function shouldEvict(entry) {
63
+ return !_this2.isInUse(entry[0]);
64
+ },
65
+ onEvict: function onEvict(entry) {
66
+ if (entry[1].dataURI) {
67
+ var dataURI = (0, _platformFeatureFlags.fg)('platform_media_safe_blob_url_eviction') ? entry[1].dataURI.split('#')[0] : entry[1].dataURI;
68
+ if (dataURI) {
69
+ URL.revokeObjectURL(dataURI);
70
+ }
71
+ }
50
72
  }
51
73
  });
52
74
  }
75
+
76
+ /**
77
+ * Marks a cache key as actively in use by a consumer.
78
+ * Multiple consumers can acquire the same key; eviction is
79
+ * blocked until all consumers have released it.
80
+ */
53
81
  return (0, _createClass2.default)(ObjectURLCache, [{
82
+ key: "acquire",
83
+ value: function acquire(key) {
84
+ var _this$activeRefs$get;
85
+ this.activeRefs.set(key, ((_this$activeRefs$get = this.activeRefs.get(key)) !== null && _this$activeRefs$get !== void 0 ? _this$activeRefs$get : 0) + 1);
86
+ }
87
+ }, {
88
+ key: "release",
89
+ value: function release(key) {
90
+ var _this$activeRefs$get2;
91
+ var count = (_this$activeRefs$get2 = this.activeRefs.get(key)) !== null && _this$activeRefs$get2 !== void 0 ? _this$activeRefs$get2 : 0;
92
+ if (count <= 1) {
93
+ this.activeRefs.delete(key);
94
+ } else {
95
+ this.activeRefs.set(key, count - 1);
96
+ }
97
+ }
98
+ }, {
99
+ key: "isInUse",
100
+ value: function isInUse(key) {
101
+ var _this$activeRefs$get3;
102
+ return ((_this$activeRefs$get3 = this.activeRefs.get(key)) !== null && _this$activeRefs$get3 !== void 0 ? _this$activeRefs$get3 : 0) > 0;
103
+ }
104
+ }, {
54
105
  key: "has",
55
106
  value: function has(key) {
56
107
  return !!this.cache.find(key);
@@ -69,7 +120,9 @@ var ObjectURLCache = exports.ObjectURLCache = /*#__PURE__*/function () {
69
120
  key: "remove",
70
121
  value: function remove(key) {
71
122
  var removed = this.cache.delete(key);
72
- removed && URL.revokeObjectURL(removed.dataURI);
123
+ this.activeRefs.delete(key);
124
+ var dataURI = (0, _platformFeatureFlags.fg)('platform_media_safe_blob_url_eviction') ? removed === null || removed === void 0 ? void 0 : removed.dataURI.split('#')[0] : removed === null || removed === void 0 ? void 0 : removed.dataURI;
125
+ dataURI && URL.revokeObjectURL(dataURI);
73
126
  }
74
127
  }, {
75
128
  key: "clear",
@@ -300,6 +300,18 @@ var useFilePreview = exports.useFilePreview = function useFilePreview(_ref) {
300
300
  }
301
301
  }, [error, nonCriticalError, getAndCacheRemotePreviewRef, identifier.id, identifier.collectionName, resizeMode, isBannedLocalPreview, mediaBlobUrlAttrsRef, mediaClient, preview, requestDimensions, skipRemote, isBackendPreviewReady, localBinary, mediaType, mimeType, upfrontPreviewStatus, dimensions]);
302
302
 
303
+ //----------------------------------------------------------------
304
+ // Cache ref tracking — prevent blob URL eviction while mounted
305
+ //----------------------------------------------------------------
306
+ (0, _react.useEffect)(function () {
307
+ if (preview) {
308
+ _getPreview.mediaFilePreviewCache.acquire(identifier.id, resizeMode);
309
+ return function () {
310
+ _getPreview.mediaFilePreviewCache.release(identifier.id, resizeMode);
311
+ };
312
+ }
313
+ }, [preview, identifier.id, resizeMode]);
314
+
303
315
  //----------------------------------------------------------------
304
316
  // RETURN
305
317
  //----------------------------------------------------------------
@@ -25,6 +25,14 @@ export class CardPreviewCacheImpl {
25
25
  _defineProperty(this, "clear", () => {
26
26
  this.previewCache.clear();
27
27
  });
28
+ _defineProperty(this, "acquire", (id, mode) => {
29
+ const cacheKey = getCacheKey(id, mode);
30
+ this.previewCache.acquire(cacheKey);
31
+ });
32
+ _defineProperty(this, "release", (id, mode) => {
33
+ const cacheKey = getCacheKey(id, mode);
34
+ this.previewCache.release(cacheKey);
35
+ });
28
36
  this.previewCache = previewCache;
29
37
  }
30
38
  }
@@ -1,29 +1,73 @@
1
- import { EventEmitter2 } from 'eventemitter2';
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import { LRUMap } from 'lru_map';
3
+ import { fg } from '@atlaskit/platform-feature-flags';
3
4
  export const PREVIEW_CACHE_LRU_SIZE = 50;
4
- class ExtendedLRUCache extends LRUMap {
5
- constructor(limit) {
6
- super(limit);
7
- this.eventEmitter = new EventEmitter2();
5
+ /**
6
+ * LRU cache that checks whether the oldest entry should be evicted
7
+ * this means that the cache can grow past the softLimit if `shouldEvict` returns false.
8
+ *
9
+ * When `platform_media_safe_blob_url_eviction` is ON the oldest
10
+ * entry is kept if `shouldEvict` returns false.
11
+ * When the flag is OFF the oldest entry is always evicted.
12
+ */
13
+ class EvictionLRUCache extends LRUMap {
14
+ constructor(softLimit, options) {
15
+ super(softLimit);
16
+ this.shouldEvict = options === null || options === void 0 ? void 0 : options.shouldEvict;
17
+ this.onEvict = options === null || options === void 0 ? void 0 : options.onEvict;
8
18
  }
9
19
  shift() {
20
+ if (fg('platform_media_safe_blob_url_eviction')) {
21
+ const oldest = this.oldest;
22
+ if (oldest && this.shouldEvict && !this.shouldEvict([oldest.key, oldest.value])) {
23
+ return undefined;
24
+ }
25
+ }
10
26
  const entry = super.shift();
11
- this.eventEmitter.emit('shift', entry);
27
+ if (entry && this.onEvict) {
28
+ this.onEvict(entry);
29
+ }
12
30
  return entry;
13
31
  }
14
- on(event, callback) {
15
- this.eventEmitter.on(event, callback);
16
- }
17
32
  }
18
33
  export class ObjectURLCache {
19
34
  constructor(size) {
20
- this.cache = new ExtendedLRUCache(size);
21
- this.cache.on('shift', entry => {
22
- if (entry && entry[1].dataURI) {
23
- URL.revokeObjectURL(entry[1].dataURI);
35
+ _defineProperty(this, "activeRefs", new Map());
36
+ this.cache = new EvictionLRUCache(size, {
37
+ shouldEvict: entry => !this.isInUse(entry[0]),
38
+ onEvict: entry => {
39
+ if (entry[1].dataURI) {
40
+ const dataURI = fg('platform_media_safe_blob_url_eviction') ? entry[1].dataURI.split('#')[0] : entry[1].dataURI;
41
+ if (dataURI) {
42
+ URL.revokeObjectURL(dataURI);
43
+ }
44
+ }
24
45
  }
25
46
  });
26
47
  }
48
+
49
+ /**
50
+ * Marks a cache key as actively in use by a consumer.
51
+ * Multiple consumers can acquire the same key; eviction is
52
+ * blocked until all consumers have released it.
53
+ */
54
+ acquire(key) {
55
+ var _this$activeRefs$get;
56
+ this.activeRefs.set(key, ((_this$activeRefs$get = this.activeRefs.get(key)) !== null && _this$activeRefs$get !== void 0 ? _this$activeRefs$get : 0) + 1);
57
+ }
58
+ release(key) {
59
+ var _this$activeRefs$get2;
60
+ const count = (_this$activeRefs$get2 = this.activeRefs.get(key)) !== null && _this$activeRefs$get2 !== void 0 ? _this$activeRefs$get2 : 0;
61
+ if (count <= 1) {
62
+ this.activeRefs.delete(key);
63
+ } else {
64
+ this.activeRefs.set(key, count - 1);
65
+ }
66
+ }
67
+ isInUse(key) {
68
+ var _this$activeRefs$get3;
69
+ return ((_this$activeRefs$get3 = this.activeRefs.get(key)) !== null && _this$activeRefs$get3 !== void 0 ? _this$activeRefs$get3 : 0) > 0;
70
+ }
27
71
  has(key) {
28
72
  return !!this.cache.find(key);
29
73
  }
@@ -35,7 +79,9 @@ export class ObjectURLCache {
35
79
  }
36
80
  remove(key) {
37
81
  const removed = this.cache.delete(key);
38
- removed && URL.revokeObjectURL(removed.dataURI);
82
+ this.activeRefs.delete(key);
83
+ const dataURI = fg('platform_media_safe_blob_url_eviction') ? removed === null || removed === void 0 ? void 0 : removed.dataURI.split('#')[0] : removed === null || removed === void 0 ? void 0 : removed.dataURI;
84
+ dataURI && URL.revokeObjectURL(dataURI);
39
85
  }
40
86
  clear() {
41
87
  this.cache.clear();
@@ -275,6 +275,18 @@ export const useFilePreview = ({
275
275
  }
276
276
  }, [error, nonCriticalError, getAndCacheRemotePreviewRef, identifier.id, identifier.collectionName, resizeMode, isBannedLocalPreview, mediaBlobUrlAttrsRef, mediaClient, preview, requestDimensions, skipRemote, isBackendPreviewReady, localBinary, mediaType, mimeType, upfrontPreviewStatus, dimensions]);
277
277
 
278
+ //----------------------------------------------------------------
279
+ // Cache ref tracking — prevent blob URL eviction while mounted
280
+ //----------------------------------------------------------------
281
+ useEffect(() => {
282
+ if (preview) {
283
+ mediaFilePreviewCache.acquire(identifier.id, resizeMode);
284
+ return () => {
285
+ mediaFilePreviewCache.release(identifier.id, resizeMode);
286
+ };
287
+ }
288
+ }, [preview, identifier.id, resizeMode]);
289
+
278
290
  //----------------------------------------------------------------
279
291
  // RETURN
280
292
  //----------------------------------------------------------------
@@ -28,6 +28,14 @@ export var CardPreviewCacheImpl = /*#__PURE__*/_createClass(function CardPreview
28
28
  _defineProperty(this, "clear", function () {
29
29
  _this.previewCache.clear();
30
30
  });
31
+ _defineProperty(this, "acquire", function (id, mode) {
32
+ var cacheKey = getCacheKey(id, mode);
33
+ _this.previewCache.acquire(cacheKey);
34
+ });
35
+ _defineProperty(this, "release", function (id, mode) {
36
+ var cacheKey = getCacheKey(id, mode);
37
+ _this.previewCache.release(cacheKey);
38
+ });
31
39
  this.previewCache = previewCache;
32
40
  });
33
41
  export var mediaFilePreviewCache = new CardPreviewCacheImpl(createObjectURLCache());
@@ -1,3 +1,4 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
2
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
2
3
  import _createClass from "@babel/runtime/helpers/createClass";
3
4
  import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
@@ -7,43 +8,93 @@ import _inherits from "@babel/runtime/helpers/inherits";
7
8
  function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
8
9
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
9
10
  function _superPropGet(t, o, e, r) { var p = _get(_getPrototypeOf(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; }
10
- import { EventEmitter2 } from 'eventemitter2';
11
11
  import { LRUMap } from 'lru_map';
12
+ import { fg } from '@atlaskit/platform-feature-flags';
12
13
  export var PREVIEW_CACHE_LRU_SIZE = 50;
13
- var ExtendedLRUCache = /*#__PURE__*/function (_LRUMap) {
14
- function ExtendedLRUCache(limit) {
14
+ /**
15
+ * LRU cache that checks whether the oldest entry should be evicted
16
+ * this means that the cache can grow past the softLimit if `shouldEvict` returns false.
17
+ *
18
+ * When `platform_media_safe_blob_url_eviction` is ON the oldest
19
+ * entry is kept if `shouldEvict` returns false.
20
+ * When the flag is OFF the oldest entry is always evicted.
21
+ */
22
+ var EvictionLRUCache = /*#__PURE__*/function (_LRUMap) {
23
+ function EvictionLRUCache(softLimit, options) {
15
24
  var _this;
16
- _classCallCheck(this, ExtendedLRUCache);
17
- _this = _callSuper(this, ExtendedLRUCache, [limit]);
18
- _this.eventEmitter = new EventEmitter2();
25
+ _classCallCheck(this, EvictionLRUCache);
26
+ _this = _callSuper(this, EvictionLRUCache, [softLimit]);
27
+ _this.shouldEvict = options === null || options === void 0 ? void 0 : options.shouldEvict;
28
+ _this.onEvict = options === null || options === void 0 ? void 0 : options.onEvict;
19
29
  return _this;
20
30
  }
21
- _inherits(ExtendedLRUCache, _LRUMap);
22
- return _createClass(ExtendedLRUCache, [{
31
+ _inherits(EvictionLRUCache, _LRUMap);
32
+ return _createClass(EvictionLRUCache, [{
23
33
  key: "shift",
24
34
  value: function shift() {
25
- var entry = _superPropGet(ExtendedLRUCache, "shift", this, 3)([]);
26
- this.eventEmitter.emit('shift', entry);
35
+ if (fg('platform_media_safe_blob_url_eviction')) {
36
+ var oldest = this.oldest;
37
+ if (oldest && this.shouldEvict && !this.shouldEvict([oldest.key, oldest.value])) {
38
+ return undefined;
39
+ }
40
+ }
41
+ var entry = _superPropGet(EvictionLRUCache, "shift", this, 3)([]);
42
+ if (entry && this.onEvict) {
43
+ this.onEvict(entry);
44
+ }
27
45
  return entry;
28
46
  }
29
- }, {
30
- key: "on",
31
- value: function on(event, callback) {
32
- this.eventEmitter.on(event, callback);
33
- }
34
47
  }]);
35
48
  }(LRUMap);
36
49
  export var ObjectURLCache = /*#__PURE__*/function () {
37
50
  function ObjectURLCache(size) {
51
+ var _this2 = this;
38
52
  _classCallCheck(this, ObjectURLCache);
39
- this.cache = new ExtendedLRUCache(size);
40
- this.cache.on('shift', function (entry) {
41
- if (entry && entry[1].dataURI) {
42
- URL.revokeObjectURL(entry[1].dataURI);
53
+ _defineProperty(this, "activeRefs", new Map());
54
+ this.cache = new EvictionLRUCache(size, {
55
+ shouldEvict: function shouldEvict(entry) {
56
+ return !_this2.isInUse(entry[0]);
57
+ },
58
+ onEvict: function onEvict(entry) {
59
+ if (entry[1].dataURI) {
60
+ var dataURI = fg('platform_media_safe_blob_url_eviction') ? entry[1].dataURI.split('#')[0] : entry[1].dataURI;
61
+ if (dataURI) {
62
+ URL.revokeObjectURL(dataURI);
63
+ }
64
+ }
43
65
  }
44
66
  });
45
67
  }
68
+
69
+ /**
70
+ * Marks a cache key as actively in use by a consumer.
71
+ * Multiple consumers can acquire the same key; eviction is
72
+ * blocked until all consumers have released it.
73
+ */
46
74
  return _createClass(ObjectURLCache, [{
75
+ key: "acquire",
76
+ value: function acquire(key) {
77
+ var _this$activeRefs$get;
78
+ this.activeRefs.set(key, ((_this$activeRefs$get = this.activeRefs.get(key)) !== null && _this$activeRefs$get !== void 0 ? _this$activeRefs$get : 0) + 1);
79
+ }
80
+ }, {
81
+ key: "release",
82
+ value: function release(key) {
83
+ var _this$activeRefs$get2;
84
+ var count = (_this$activeRefs$get2 = this.activeRefs.get(key)) !== null && _this$activeRefs$get2 !== void 0 ? _this$activeRefs$get2 : 0;
85
+ if (count <= 1) {
86
+ this.activeRefs.delete(key);
87
+ } else {
88
+ this.activeRefs.set(key, count - 1);
89
+ }
90
+ }
91
+ }, {
92
+ key: "isInUse",
93
+ value: function isInUse(key) {
94
+ var _this$activeRefs$get3;
95
+ return ((_this$activeRefs$get3 = this.activeRefs.get(key)) !== null && _this$activeRefs$get3 !== void 0 ? _this$activeRefs$get3 : 0) > 0;
96
+ }
97
+ }, {
47
98
  key: "has",
48
99
  value: function has(key) {
49
100
  return !!this.cache.find(key);
@@ -62,7 +113,9 @@ export var ObjectURLCache = /*#__PURE__*/function () {
62
113
  key: "remove",
63
114
  value: function remove(key) {
64
115
  var removed = this.cache.delete(key);
65
- removed && URL.revokeObjectURL(removed.dataURI);
116
+ this.activeRefs.delete(key);
117
+ var dataURI = fg('platform_media_safe_blob_url_eviction') ? removed === null || removed === void 0 ? void 0 : removed.dataURI.split('#')[0] : removed === null || removed === void 0 ? void 0 : removed.dataURI;
118
+ dataURI && URL.revokeObjectURL(dataURI);
66
119
  }
67
120
  }, {
68
121
  key: "clear",
@@ -293,6 +293,18 @@ export var useFilePreview = function useFilePreview(_ref) {
293
293
  }
294
294
  }, [error, nonCriticalError, getAndCacheRemotePreviewRef, identifier.id, identifier.collectionName, resizeMode, isBannedLocalPreview, mediaBlobUrlAttrsRef, mediaClient, preview, requestDimensions, skipRemote, isBackendPreviewReady, localBinary, mediaType, mimeType, upfrontPreviewStatus, dimensions]);
295
295
 
296
+ //----------------------------------------------------------------
297
+ // Cache ref tracking — prevent blob URL eviction while mounted
298
+ //----------------------------------------------------------------
299
+ useEffect(function () {
300
+ if (preview) {
301
+ mediaFilePreviewCache.acquire(identifier.id, resizeMode);
302
+ return function () {
303
+ mediaFilePreviewCache.release(identifier.id, resizeMode);
304
+ };
305
+ }
306
+ }, [preview, identifier.id, resizeMode]);
307
+
296
308
  //----------------------------------------------------------------
297
309
  // RETURN
298
310
  //----------------------------------------------------------------
@@ -8,6 +8,8 @@ export interface MediaFilePreviewCache {
8
8
  set(id: string, mode: Mode, cardPreview: MediaFilePreview): void;
9
9
  remove(id: string, mode: Mode): void;
10
10
  clear(): void;
11
+ acquire(id: string, mode: Mode): void;
12
+ release(id: string, mode: Mode): void;
11
13
  }
12
14
  export declare class CardPreviewCacheImpl implements MediaFilePreviewCache {
13
15
  private previewCache;
@@ -16,6 +18,8 @@ export declare class CardPreviewCacheImpl implements MediaFilePreviewCache {
16
18
  set: (id: string, mode: Mode, cardPreview: MediaFilePreview) => void;
17
19
  remove: (id: string, mode: Mode) => void;
18
20
  clear: () => void;
21
+ acquire: (id: string, mode: Mode) => void;
22
+ release: (id: string, mode: Mode) => void;
19
23
  }
20
24
  export declare const mediaFilePreviewCache: CardPreviewCacheImpl;
21
25
  export {};
@@ -2,7 +2,16 @@ import { type MediaFilePreview } from '../types';
2
2
  export declare const PREVIEW_CACHE_LRU_SIZE = 50;
3
3
  export declare class ObjectURLCache {
4
4
  private readonly cache;
5
+ private readonly activeRefs;
5
6
  constructor(size: number);
7
+ /**
8
+ * Marks a cache key as actively in use by a consumer.
9
+ * Multiple consumers can acquire the same key; eviction is
10
+ * blocked until all consumers have released it.
11
+ */
12
+ acquire(key: string): void;
13
+ release(key: string): void;
14
+ private isInUse;
6
15
  has(key: string): boolean;
7
16
  get(key: string): MediaFilePreview | undefined;
8
17
  set(key: string, value: MediaFilePreview): void;
@@ -8,6 +8,8 @@ export interface MediaFilePreviewCache {
8
8
  set(id: string, mode: Mode, cardPreview: MediaFilePreview): void;
9
9
  remove(id: string, mode: Mode): void;
10
10
  clear(): void;
11
+ acquire(id: string, mode: Mode): void;
12
+ release(id: string, mode: Mode): void;
11
13
  }
12
14
  export declare class CardPreviewCacheImpl implements MediaFilePreviewCache {
13
15
  private previewCache;
@@ -16,6 +18,8 @@ export declare class CardPreviewCacheImpl implements MediaFilePreviewCache {
16
18
  set: (id: string, mode: Mode, cardPreview: MediaFilePreview) => void;
17
19
  remove: (id: string, mode: Mode) => void;
18
20
  clear: () => void;
21
+ acquire: (id: string, mode: Mode) => void;
22
+ release: (id: string, mode: Mode) => void;
19
23
  }
20
24
  export declare const mediaFilePreviewCache: CardPreviewCacheImpl;
21
25
  export {};
@@ -2,7 +2,16 @@ import { type MediaFilePreview } from '../types';
2
2
  export declare const PREVIEW_CACHE_LRU_SIZE = 50;
3
3
  export declare class ObjectURLCache {
4
4
  private readonly cache;
5
+ private readonly activeRefs;
5
6
  constructor(size: number);
7
+ /**
8
+ * Marks a cache key as actively in use by a consumer.
9
+ * Multiple consumers can acquire the same key; eviction is
10
+ * blocked until all consumers have released it.
11
+ */
12
+ acquire(key: string): void;
13
+ release(key: string): void;
14
+ private isInUse;
6
15
  has(key: string): boolean;
7
16
  get(key: string): MediaFilePreview | undefined;
8
17
  set(key: string, value: MediaFilePreview): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/media-file-preview",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "description": "A React Hook to fetch and render file previews. It's overloaded with fancy features like SSR, lazy loading, memory cache and local preview.",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -37,7 +37,6 @@
37
37
  "@atlaskit/platform-feature-flags": "^1.1.0",
38
38
  "@atlaskit/react-ufo": "^5.4.0",
39
39
  "@babel/runtime": "^7.0.0",
40
- "eventemitter2": "^4.1.0",
41
40
  "lru_map": "^0.4.1"
42
41
  },
43
42
  "peerDependencies": {
@@ -104,6 +103,9 @@
104
103
  },
105
104
  "platform_media_cross_client_copy_with_auth": {
106
105
  "type": "boolean"
106
+ },
107
+ "platform_media_safe_blob_url_eviction": {
108
+ "type": "boolean"
107
109
  }
108
110
  }
109
111
  }