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
);