image_loader_client.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. // Copyright 2013 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. import {LoadImageRequest, LoadImageResponse, LoadImageResponseStatus} from './load_image_request.js';
  5. import {LRUCache} from './lru_cache.js';
  6. /**
  7. * Client used to connect to the remote ImageLoader extension. Client class runs
  8. * in the extension, where the client.js is included (eg. Files app).
  9. * It sends remote requests using IPC to the ImageLoader class and forwards
  10. * its responses.
  11. *
  12. * Implements cache, which is stored in the calling extension.
  13. *
  14. * @constructor
  15. */
  16. export function ImageLoaderClient() {
  17. /**
  18. * @type {number}
  19. * @private
  20. */
  21. this.lastTaskId_ = 0;
  22. /**
  23. * LRU cache for images.
  24. * @type {!LRUCache.<{
  25. * timestamp: ?number,
  26. * width: number,
  27. * height: number,
  28. * ifd: ?string,
  29. * data: string
  30. * }>}
  31. * @private
  32. */
  33. this.cache_ = new LRUCache(ImageLoaderClient.CACHE_MEMORY_LIMIT);
  34. }
  35. /**
  36. * Image loader's extension id.
  37. * @const
  38. * @type {string}
  39. */
  40. ImageLoaderClient.EXTENSION_ID = 'pmfjbimdmchhbnneeidfognadeopoehp';
  41. /**
  42. * Returns a singleton instance.
  43. * @return {ImageLoaderClient} Client instance.
  44. */
  45. ImageLoaderClient.getInstance = function() {
  46. if (!ImageLoaderClient.instance_) {
  47. ImageLoaderClient.instance_ = new ImageLoaderClient();
  48. }
  49. return ImageLoaderClient.instance_;
  50. };
  51. /**
  52. * Records binary metrics. Counts for true and false are stored as a histogram.
  53. * @param {string} name Histogram's name.
  54. * @param {boolean} value True or false.
  55. */
  56. ImageLoaderClient.recordBinary = function(name, value) {
  57. chrome.metricsPrivate.recordValue(
  58. { metricName: 'ImageLoader.Client.' + name,
  59. type: chrome.metricsPrivate.MetricTypeType.HISTOGRAM_LINEAR,
  60. min: 1, // According to histogram.h, this should be 1 for enums.
  61. max: 2, // Maximum should be exclusive.
  62. buckets: 3 }, // Number of buckets: 0, 1 and overflowing 2.
  63. value ? 1 : 0);
  64. };
  65. /**
  66. * Records percent metrics, stored as a histogram.
  67. * @param {string} name Histogram's name.
  68. * @param {number} value Value (0..100).
  69. */
  70. ImageLoaderClient.recordPercentage = function(name, value) {
  71. chrome.metricsPrivate.recordPercentage('ImageLoader.Client.' + name,
  72. Math.round(value));
  73. };
  74. /**
  75. * Sends a message to the Image Loader extension.
  76. * @param {!LoadImageRequest} request The image request.
  77. * @param {!function(!LoadImageResponse)=} callback Response handling callback.
  78. * The response is passed as a hash array.
  79. * @private
  80. */
  81. ImageLoaderClient.sendMessage_ = function(request, callback) {
  82. chrome.runtime.sendMessage(ImageLoaderClient.EXTENSION_ID, request, callback);
  83. };
  84. /**
  85. * Image loader client extension request URL matcher.
  86. * @const {!RegExp}
  87. */
  88. ImageLoaderClient.CLIENT_URL_REGEX = /filesystem:chrome-extension:\/\/[a-z]+/;
  89. /**
  90. * Image loader client chrome://file-manager request URL matcher.
  91. * @const {!RegExp}
  92. */
  93. ImageLoaderClient.CLIENT_SWA_REGEX = /filesystem:chrome:\/\/file-manager/;
  94. /**
  95. * All client request URL match ImageLoaderClient.CLIENT_URL_REGEX and all are
  96. * rewritten: the client extension id part of the request URL is replaced with
  97. * the image loader extension id.
  98. * @const {string}
  99. */
  100. ImageLoaderClient.IMAGE_LOADER_URL =
  101. 'filesystem:chrome-extension://' + ImageLoaderClient.EXTENSION_ID;
  102. /**
  103. * Loads and resizes and image.
  104. *
  105. * @param {!LoadImageRequest} request
  106. * @param {!function(!LoadImageResponse)=} callback Response handling callback.
  107. * @return {?number} Remote task id or null if loaded from cache.
  108. */
  109. ImageLoaderClient.prototype.load = function(request, callback) {
  110. // Record cache usage.
  111. ImageLoaderClient.recordPercentage('Cache.Usage',
  112. this.cache_.size() / ImageLoaderClient.CACHE_MEMORY_LIMIT * 100.0);
  113. // Replace the client origin with the image loader extension origin.
  114. request.url = request.url.replace(
  115. ImageLoaderClient.CLIENT_URL_REGEX, ImageLoaderClient.IMAGE_LOADER_URL);
  116. request.url = request.url.replace(
  117. ImageLoaderClient.CLIENT_SWA_REGEX, ImageLoaderClient.IMAGE_LOADER_URL);
  118. // Try to load from cache, if available.
  119. const cacheKey = LoadImageRequest.cacheKey(request);
  120. if (cacheKey) {
  121. if (request.cache) {
  122. // Load from cache.
  123. ImageLoaderClient.recordBinary('Cached', true);
  124. let cachedValue = this.cache_.get(cacheKey);
  125. // Check if the image in cache is up to date. If not, then remove it.
  126. if (cachedValue && cachedValue.timestamp != request.timestamp) {
  127. this.cache_.remove(cacheKey);
  128. cachedValue = null;
  129. }
  130. if (cachedValue && cachedValue.data &&
  131. cachedValue.width && cachedValue.height) {
  132. ImageLoaderClient.recordBinary('Cache.HitMiss', true);
  133. callback(new LoadImageResponse(LoadImageResponseStatus.SUCCESS, null, {
  134. width: cachedValue.width,
  135. height: cachedValue.height,
  136. ifd: cachedValue.ifd,
  137. data: cachedValue.data,
  138. }));
  139. return null;
  140. } else {
  141. ImageLoaderClient.recordBinary('Cache.HitMiss', false);
  142. }
  143. } else {
  144. // Remove from cache.
  145. ImageLoaderClient.recordBinary('Cached', false);
  146. this.cache_.remove(cacheKey);
  147. }
  148. }
  149. // Not available in cache, performing a request to a remote extension.
  150. this.lastTaskId_++;
  151. request.taskId = this.lastTaskId_;
  152. ImageLoaderClient.sendMessage_(request, function(result_data) {
  153. // TODO(tapted): Move to a prototype for better type checking.
  154. const result = /** @type {!LoadImageResponse} */ (result_data);
  155. // Save to cache.
  156. if (cacheKey && request.cache) {
  157. const value = LoadImageResponse.cacheValue(result, request.timestamp);
  158. if (value) {
  159. this.cache_.put(cacheKey, value, value.data.length);
  160. }
  161. }
  162. callback(result);
  163. }.bind(this));
  164. return request.taskId;
  165. };
  166. /**
  167. * Cancels the request. Note the original callback may still be invoked if this
  168. * message doesn't reach the ImageLoader before it starts processing.
  169. * @param {number} taskId Task id returned by ImageLoaderClient.load().
  170. */
  171. ImageLoaderClient.prototype.cancel = function(taskId) {
  172. ImageLoaderClient.sendMessage_(
  173. LoadImageRequest.createCancel(taskId), function(result) {});
  174. };
  175. /**
  176. * Memory limit for images data in bytes.
  177. *
  178. * @const
  179. * @type {number}
  180. */
  181. ImageLoaderClient.CACHE_MEMORY_LIMIT = 20 * 1024 * 1024; // 20 MB.
  182. // Helper functions.
  183. /**
  184. * Loads and resizes and image.
  185. *
  186. * @param {!LoadImageRequest} request
  187. * @param {HTMLImageElement} image Image node to load the requested picture
  188. * into.
  189. * @param {function()} onSuccess Callback for success.
  190. * @param {function()} onError Callback for failure.
  191. * @return {?number} Remote task id or null if loaded from cache.
  192. */
  193. ImageLoaderClient.loadToImage = function(request, image, onSuccess, onError) {
  194. const callback = function(result) {
  195. if (result.status == LoadImageResponseStatus.ERROR) {
  196. onError();
  197. return;
  198. }
  199. image.src = result.data;
  200. onSuccess();
  201. };
  202. return ImageLoaderClient.getInstance().load(request, callback);
  203. };