Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(289)

Side by Side Diff: chrome/browser/resources/image_loader/client.js

Issue 12304013: Introduce Image loader extension. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Simplified. Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
5 var ImageLoader = ImageLoader || {};
6
7 /**
8 * Image loader's extension id.
9 * @const
10 * @type {string}
11 */
12 ImageLoader.EXTENSION_ID = 'pmfjbimdmchhbnneeidfognadeopoehp';
13
14 /**
15 * Client used to connect to the remote ImageLoader extension. Client class runs
16 * in the extension, where the client.js is included (eg. Files.app).
17 * It sends remote requests using IPC to the ImageLoader class and forwards
18 * its responses.
19 *
20 * Implements cache, which is stored in the calling extension.
21 *
22 * @constructor
23 */
24 ImageLoader.Client = function() {
25 /**
26 * @type {Port}
27 * @private
28 */
29 this.port_ = chrome.extension.connect(ImageLoader.EXTENSION_ID);
30 this.port_.onMessage.addListener(this.handleMessage_.bind(this));
31
32 /**
33 * Hash array with active tasks.
34 * @type {Object}
35 * @private
36 */
37 this.tasks_ = {};
38
39 /**
40 * @type {number}
41 * @private
42 */
43 this.lastTaskId_ = 0;
44
45 /**
46 * LRU cache for images.
47 * @type {ImageLoader.Client.Cache}
48 * @private
49 */
50 this.cache_ = new ImageLoader.Client.Cache();
51 };
52
53 /**
54 * Returns a singleton instance.
55 * @return {ImageLoader.Client} ImageLoader.Client instance.
56 */
57 ImageLoader.Client.getInstance = function() {
58 if (!ImageLoader.Client.instance_)
59 ImageLoader.Client.instance_ = new ImageLoader.Client();
60 return ImageLoader.Client.instance_;
61 };
62
63 /**
64 * Handles a message from the remote image loader and calls the registered
65 * callback to pass the response back to the requester.
66 *
67 * @param {Object} message Response message as a hash array.
68 * @private
69 */
70 ImageLoader.Client.prototype.handleMessage_ = function(message) {
71 if (!(message.taskId in this.tasks_)) {
72 // This task has been canceled, but was already fetched, so it's result
73 // should be discarded anyway.
74 return;
75 }
76
77 var task = this.tasks_[message.taskId];
78
79 // Check if the task is still valid.
80 if (task.isValid())
81 task.accept(message);
82
83 delete this.tasks_[message.taskId];
84 };
85
86 /**
87 * Loads and resizes and image. Use opt_isValid to easily cancel requests
88 * which are not valid anymore, which will reduce cpu consumption.
89 *
90 * @param {string} url Url of the requested image.
91 * @param {function} callback Callback used to return response.
92 * @param {Object=} opt_options Loader options, such as: scale, maxHeight,
93 * width, height and/or cache.
94 * @param {function=} opt_isValid Function returning false in case
95 * a request is not valid anymore, eg. parent node has been detached.
96 * @return {?number} Remote task id or null if loaded from cache.
97 */
98 ImageLoader.Client.prototype.load = function(
99 url, callback, opt_options, opt_isValid) {
100 opt_options = opt_options || {};
101 opt_isValid = opt_isValid || function() { return true; };
102
103 // Cancel old, invalid tasks.
104 var taskKeys = Object.keys(this.tasks_);
105 for (var index = 0; index < taskKeys.length; index++) {
106 var taskKey = taskKeys[index];
107 var task = this.tasks_[taskKey];
108 if (!task.isValid()) {
109 // Cancel this task since it is not valid anymore.
110 this.cancel(taskKey);
111 delete this.tasks_[taskKey];
112 }
113 }
114
115 // Replace the extension id.
116 var sourceId = chrome.i18n.getMessage('@@extension_id');
117 var targetId = ImageLoader.EXTENSION_ID;
118
119 url = url.replace('filesystem:chrome-extension://' + sourceId,
120 'filesystem:chrome-extension://' + targetId);
121
122 // Try to load from cache, if available.
123 var cacheKey = ImageLoader.Client.Cache.createKey(url, opt_options);
124 if (opt_options.cache) {
125 // Load from cache.
126 // TODO(mtomasz): Add cache invalidating if the file has changed.
127 var cachedData = this.cache_.loadImage(cacheKey);
128 if (cachedData) {
129 callback({ status: 'success', data: cachedData });
130 return null;
131 }
132 } else {
133 // Remove from cache.
134 this.cache_.removeImage(cacheKey);
135 }
136
137 // Not available in cache, performing a request to a remote extension.
138 request = opt_options;
139 this.lastTaskId_++;
140 var task = { isValid: opt_isValid, accept: function(result) {
141 // Save to cache.
142 if (result.status == 'success' && opt_options.cache)
143 this.cache_.saveImage(cacheKey, result.data);
144 callback(result);
145 }.bind(this) };
146 this.tasks_[this.lastTaskId_] = task;
147
148 request.url = url;
149 request.taskId = this.lastTaskId_;
150
151 this.port_.postMessage(request);
152 return request.taskId;
153 };
154
155 /**
156 * Cancels the request.
157 * @param {number} taskId Task id returned by ImageLoader.Client.load().
158 */
159 ImageLoader.Client.prototype.cancel = function(taskId) {
160 this.port_.postMessage({ taskId: taskId, cancel: true });
161 };
162
163 /**
164 * Prints the cache usage statistics.
165 */
166 ImageLoader.Client.prototype.stat = function() {
167 this.cache_.stat();
168 };
169
170 /**
171 * Least Recently Used (LRU) cache implementation to be used by
172 * ImageLoader.Client class. It has memory constraints, so it will never
173 * exceed specified memory limit defined in MEMORY_LIMIT.
174 *
175 * @constructor
176 */
177 ImageLoader.Client.Cache = function() {
178 this.images_ = [];
179 this.size_ = 0;
180 };
181
182 /**
183 * Memory limit for images data in bytes.
184 *
185 * @const
186 * @type {number}
187 */
188 ImageLoader.Client.Cache.MEMORY_LIMIT = 100 * 1024 * 1024; // 100 MB.
189
190 /**
191 * Creates a cache key.
192 *
193 * @param {string} url Image url.
194 * @param {Object=} opt_options Loader options as a hash array.
195 * @return {string} Cache key.
196 */
197 ImageLoader.Client.Cache.createKey = function(url, opt_options) {
198 var array = opt_options || {};
199 array.url = url;
200 return JSON.stringify(array);
201 };
202
203 /**
204 * Evicts the least used elements in cache to make space for a new image.
205 *
206 * @param {number} size Requested size.
207 * @private
208 */
209 ImageLoader.Client.Cache.prototype.evictCache_ = function(size) {
210 // Sort from the most recent to the oldest.
211 this.images_.sort(function(a, b) {
212 return b.timestamp - a.timestamp;
213 });
214
215 while (this.images_.length > 0 &&
216 (ImageLoader.Client.Cache.MEMORY_LIMIT - this.size_ < size)) {
217 var entry = this.images_.pop();
218 this.size_ -= entry.data.length;
219 }
220 };
221
222 /**
223 * Saves an image in the cache.
224 *
225 * @param {string} key Cache key.
226 * @param {string} data Image data.
227 */
228 ImageLoader.Client.Cache.prototype.saveImage = function(key, data) {
229 this.evictCache_(data.length);
230 if (ImageLoader.Client.Cache.MEMORY_LIMIT - this.size_ >= data.length) {
231 this.images_[key] = { timestamp: Date.now(), data: data };
232 this.size_ += data.length;
233 }
234 };
235
236 /**
237 * Loads an image from the cache (if available) or returns null.
238 *
239 * @param {string} key Cache key.
240 * @return {?string} Data of the loaded image or null.
241 */
242 ImageLoader.Client.Cache.prototype.loadImage = function(key) {
243 if (!(key in this.images_))
244 return null;
245
246 var entry = this.images_[key];
247 entry.timestamp = Date.now();
248 return entry.data;
249 };
250
251 /**
252 * Prints the cache usage stats.
253 */
254 ImageLoader.Client.Cache.prototype.stat = function() {
255 console.log('Cache entries: ' + Object.keys(this.images_).length);
256 console.log('Usage: ' + Math.round(this.size_ /
257 ImageLoader.Client.Cache.MEMORY_LIMIT * 100.0) + '%');
258 };
259
260 /**
261 * Removes the image from the cache.
262 * @param {string} key Cache key.
263 */
264 ImageLoader.Client.Cache.prototype.removeImage = function(key) {
265 if (!(key in this.images_))
266 return;
267
268 var entry = this.images_[key];
269 this.size_ -= entry.data.length;
270 delete this.images_[key];
271 };
272
273 // Helper functions.
274
275 /**
276 * Loads and resizes and image. Use opt_isValid to easily cancel requests
277 * which are not valid anymore, which will reduce cpu consumption.
278 *
279 * @param {string} url Url of the requested image.
280 * @param {Image} image Image node to load the requested picture into.
281 * @param {Object} options Loader options, such as: scale, maxHeight, width,
282 * height and/or cache.
283 * @param {function=} onSuccess Callback for success.
284 * @param {function=} onError Callback for failure.
285 * @param {function=} opt_isValid Function returning false in case
286 * a request is not valid anymore, eg. parent node has been detached.
287 * @return {?number} Remote task id or null if loaded from cache.
288 */
289 ImageLoader.Client.loadToImage = function(url, image, options, onSuccess,
290 onError, opt_isValid) {
291 var callback = function(result) {
292 if (result.status == 'error') {
293 onError();
294 return;
295 }
296 image.src = result.data;
297 onSuccess();
298 };
299
300 return ImageLoader.Client.getInstance().load(
301 url, callback, options, opt_isValid);
302 };
OLDNEW
« no previous file with comments | « chrome/browser/resources/file_manager/photo_import.html ('k') | chrome/browser/resources/image_loader/image_loader.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698