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.
6 * Client used to connect to the remote ImageLoader extension. Client class runs
7 * in the extension, where the client.js is included (eg. Files.app).
8 * It sends remote requests using IPC to the ImageLoader class and forwards
11 * Implements cache, which is stored in the calling extension.
15 function ImageLoaderClient() {
17 * Hash array with active tasks.
30 * LRU cache for images.
32 * data: string, width:number, height:number, timestamp: ?number}>}
35 this.cache_ = new LRUCache(ImageLoaderClient.CACHE_MEMORY_LIMIT);
39 * Image loader's extension id.
43 ImageLoaderClient.EXTENSION_ID = 'pmfjbimdmchhbnneeidfognadeopoehp';
46 * Returns a singleton instance.
47 * @return {ImageLoaderClient} Client instance.
49 ImageLoaderClient.getInstance = function() {
50 if (!ImageLoaderClient.instance_)
51 ImageLoaderClient.instance_ = new ImageLoaderClient();
52 return ImageLoaderClient.instance_;
56 * Records binary metrics. Counts for true and false are stored as a histogram.
57 * @param {string} name Histogram's name.
58 * @param {boolean} value True or false.
60 ImageLoaderClient.recordBinary = function(name, value) {
61 chrome.metricsPrivate.recordValue(
62 { metricName: 'ImageLoader.Client.' + name,
63 type: 'histogram-linear',
64 min: 1, // According to histogram.h, this should be 1 for enums.
65 max: 2, // Maximum should be exclusive.
66 buckets: 3 }, // Number of buckets: 0, 1 and overflowing 2.
71 * Records percent metrics, stored as a histogram.
72 * @param {string} name Histogram's name.
73 * @param {number} value Value (0..100).
75 ImageLoaderClient.recordPercentage = function(name, value) {
76 chrome.metricsPrivate.recordPercentage('ImageLoader.Client.' + name,
81 * Sends a message to the Image Loader extension.
82 * @param {Object} request Hash array with request data.
83 * @param {function(Object)=} opt_callback Response handling callback.
84 * The response is passed as a hash array.
87 ImageLoaderClient.sendMessage_ = function(request, opt_callback) {
88 opt_callback = opt_callback || function(response) {};
89 chrome.runtime.sendMessage(
90 ImageLoaderClient.EXTENSION_ID, request, opt_callback);
94 * Handles a message from the remote image loader and calls the registered
95 * callback to pass the response back to the requester.
97 * @param {Object} message Response message as a hash array.
100 ImageLoaderClient.prototype.handleMessage_ = function(message) {
101 if (!(message.taskId in this.tasks_)) {
102 // This task has been canceled, but was already fetched, so it's result
103 // should be discarded anyway.
107 var task = this.tasks_[message.taskId];
109 // Check if the task is still valid.
111 task.accept(message);
113 delete this.tasks_[message.taskId];
117 * Loads and resizes and image. Use opt_isValid to easily cancel requests
118 * which are not valid anymore, which will reduce cpu consumption.
120 * @param {string} url Url of the requested image.
121 * @param {function({status: string, data:string, width:number, height:number})}
122 * callback Callback used to return response. Width and height in the
123 * response is the size of image (data), i.e. When the image is resized,
124 * these values are resized width and height.
125 * @param {Object=} opt_options Loader options, such as: scale, maxHeight,
126 * width, height and/or cache.
127 * @param {function(): boolean=} opt_isValid Function returning false in case
128 * a request is not valid anymore, eg. parent node has been detached.
129 * @return {?number} Remote task id or null if loaded from cache.
131 ImageLoaderClient.prototype.load = function(
132 url, callback, opt_options, opt_isValid) {
133 opt_options = /** @type {{cache: (boolean|undefined)}} */(opt_options || {});
134 opt_isValid = opt_isValid || function() { return true; };
136 // Record cache usage.
137 ImageLoaderClient.recordPercentage('Cache.Usage',
138 this.cache_.size() / ImageLoaderClient.CACHE_MEMORY_LIMIT * 100.0);
140 // Cancel old, invalid tasks.
141 var taskKeys = Object.keys(this.tasks_);
142 for (var index = 0; index < taskKeys.length; index++) {
143 var taskKey = taskKeys[index];
144 var task = this.tasks_[taskKey];
145 if (!task.isValid()) {
146 // Cancel this task since it is not valid anymore.
147 this.cancel(parseInt(taskKey, 10));
148 delete this.tasks_[taskKey];
152 // Replace the extension id.
153 var sourceId = chrome.i18n.getMessage('@@extension_id');
154 var targetId = ImageLoaderClient.EXTENSION_ID;
156 url = url.replace('filesystem:chrome-extension://' + sourceId,
157 'filesystem:chrome-extension://' + targetId);
159 // Try to load from cache, if available.
160 var cacheKey = ImageLoaderClient.createKey(url, opt_options);
162 if (opt_options.cache) {
164 ImageLoaderClient.recordBinary('Cached', true);
165 var cachedValue = this.cache_.get(cacheKey);
166 // Check if the image in cache is up to date. If not, then remove it.
167 if (cachedValue && cachedValue.timestamp != opt_options.timestamp) {
168 this.cache_.remove(cacheKey);
171 if (cachedValue && cachedValue.data &&
172 cachedValue.width && cachedValue.height) {
173 ImageLoaderClient.recordBinary('Cache.HitMiss', true);
175 status: 'success', data: cachedValue.data,
176 width: cachedValue.width, height: cachedValue.height
180 ImageLoaderClient.recordBinary('Cache.HitMiss', false);
183 // Remove from cache.
184 ImageLoaderClient.recordBinary('Cached', false);
185 this.cache_.remove(cacheKey);
189 // Not available in cache, performing a request to a remote extension.
190 var request = opt_options;
192 var task = {isValid: opt_isValid};
193 this.tasks_[this.lastTaskId_] = task;
196 request.taskId = this.lastTaskId_;
197 request.timestamp = opt_options.timestamp;
199 ImageLoaderClient.sendMessage_(
203 if (cacheKey && result.status == 'success' && opt_options.cache) {
205 timestamp: opt_options.timestamp ? opt_options.timestamp : null,
206 data: result.data, width: result.width, height: result.height
208 this.cache_.put(cacheKey, value, result.data.length);
212 return request.taskId;
216 * Cancels the request.
217 * @param {number} taskId Task id returned by ImageLoaderClient.load().
219 ImageLoaderClient.prototype.cancel = function(taskId) {
220 ImageLoaderClient.sendMessage_({taskId: taskId, cancel: true});
224 * Memory limit for images data in bytes.
229 ImageLoaderClient.CACHE_MEMORY_LIMIT = 20 * 1024 * 1024; // 20 MB.
232 * Creates a cache key.
234 * @param {string} url Image url.
235 * @param {Object=} opt_options Loader options as a hash array.
236 * @return {?string} Cache key. It may return null if the class does not provide
237 * caches for the URL. (e.g. Data URL)
239 ImageLoaderClient.createKey = function(url, opt_options) {
240 if (/^data:/i.test(url))
242 opt_options = opt_options || {};
243 return JSON.stringify({
245 orientation: opt_options.orientation,
246 scale: opt_options.scale,
247 width: opt_options.width,
248 height: opt_options.height,
249 maxWidth: opt_options.maxWidth,
250 maxHeight: opt_options.maxHeight});
256 * Loads and resizes and image. Use opt_isValid to easily cancel requests
257 * which are not valid anymore, which will reduce cpu consumption.
259 * @param {string} url Url of the requested image.
260 * @param {HTMLImageElement} image Image node to load the requested picture
262 * @param {Object} options Loader options, such as: orientation, scale,
263 * maxHeight, width, height and/or cache.
264 * @param {function()} onSuccess Callback for success.
265 * @param {function()} onError Callback for failure.
266 * @param {function(): boolean=} opt_isValid Function returning false in case
267 * a request is not valid anymore, eg. parent node has been detached.
268 * @return {?number} Remote task id or null if loaded from cache.
270 ImageLoaderClient.loadToImage = function(
271 url, image, options, onSuccess, onError, opt_isValid) {
272 var callback = function(result) {
273 if (result.status == 'error') {
277 image.src = result.data;
281 return ImageLoaderClient.getInstance().load(
282 url, callback, options, opt_isValid);