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

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: Cleanup. 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 2012 The Chromium Authors. All rights reserved.
James Hawkins 2013/02/20 04:31:06 2013
mtomasz 2013/02/21 05:19:24 Done.
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 class working in a separate
James Hawkins 2013/02/20 04:31:06 What is the 'separate extension'?
mtomasz 2013/02/21 05:19:24 Clarified the comment. Client runs in Files.app ex
16 * extension. This class runs in the extension, when the client.js is included.
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 this.port_ = chrome.extension.connect(ImageLoader.EXTENSION_ID);
26 this.port_.onMessage.addListener(this.handleMessage_.bind(this));
27
28 this.tasks_ = {};
James Hawkins 2013/02/20 04:31:06 nit: Document member variables.
mtomasz 2013/02/21 05:19:24 Done.
29 this.lastTaskId_ = 0;
30 this.cache_ = new ImageLoader.Client.Cache();
31 };
32
33 /**
34 * Returns a singleton instance. Static.
James Hawkins 2013/02/20 04:31:06 nit: No need to specify 'static'. Here and elsewh
mtomasz 2013/02/21 05:19:24 I wanted to use @static, however it is not accepte
35 * @return {ImageLoader.Client} ImageLoader.Client instance.
36 */
37 ImageLoader.Client.getInstance = function() {
38 if (!ImageLoader.Client.instance_)
39 ImageLoader.Client.instance_ = new ImageLoader.Client();
40 return ImageLoader.Client.instance_;
41 };
42
43 /**
44 * Loads and resizes and image. Use opt_isValid to easily cancel requests
James Hawkins 2013/02/20 04:31:06 s/and/an/?
mtomasz 2013/02/21 05:19:24 Done.
45 * which are not valid anymore, which will reduce cpu consumption. Static.
46 *
47 * @param {string} url Url of the requested image.
48 * @param {function} callback Callback used to return response.
49 * @param {Object=} opt_options Loader options, such as: scale, maxHeight,
50 * width, height and/or cache.
51 * @param {function=} opt_isValid Function returning false in case
52 * a request is not valid anymore, eg. parent node has been detached.
53 * @return {?number} Remote task id or null if loaded from cache.
54 */
55 ImageLoader.Client.load = function(url, callback, opt_options, opt_isValid) {
James Hawkins 2013/02/20 04:31:06 Please remove all of these 'singleton' methods and
mtomasz 2013/02/21 05:19:24 Done.
56 return ImageLoader.Client.getInstance().load(
57 url, callback, opt_options, opt_isValid);
58 };
59
60 /**
61 * Cancels the request. Static.
62 * @param {number} taskId Task id returned by ImageLoader.Client.load().
63 */
64 ImageLoader.Client.cancel = function(taskId) {
65 ImageLoader.Client.getInstance().cancel(taskId);
66 };
67
68 /**
69 * Prints the cache usage statistics. Static.
70 */
71 ImageLoader.Client.stat = function() {
72 ImageLoader.Client.getInstance().stat();
73 };
74
75 /**
76 * Handles a message from the remote image loader and calls the registered
77 * callback to pass the response back to the requester.
78 *
79 * @param {Object} message Response message as a hash array.
80 * @private
81 */
82 ImageLoader.Client.prototype.handleMessage_ = function(message) {
83 if (!(message.taskId in this.tasks_)) {
84 // This task has been canceled, but was already fetched, so it's result
85 // should be discarded anyway.
86 return;
87 }
88
89 var task = this.tasks_[message.taskId];
90
91 // Check if the task is still valid.
92 if (task.isValid())
93 task.accept(message);
94
95 delete this.tasks_[message.taskId];
96 };
97
98 /**
99 * Loads and resizes and image. Use opt_isValid to easily cancel requests
100 * which are not valid anymore, which will reduce cpu consumption.
101 *
102 * @param {string} url Url of the requested image.
103 * @param {function} callback Callback used to return response.
104 * @param {Object=} opt_options Loader options, such as: scale, maxHeight,
105 * width, height and/or cache.
106 * @param {function=} opt_isValid Function returning false in case
107 * a request is not valid anymore, eg. parent node has been detached.
108 * @return {?number} Remote task id or null if loaded from cache.
109 */
110 ImageLoader.Client.prototype.load = function(
111 url, callback, opt_options, opt_isValid) {
112 opt_options = opt_options || {};
113 opt_isValid = opt_isValid || function() { return true; };
114
115 // Cancel old, invalid tasks.
116 var taskKeys = Object.keys(this.tasks_);
117 for (var index = 0; index < taskKeys.length; index++) {
118 var taskKey = taskKeys[index];
119 var task = this.tasks_[taskKey];
120 if (!task.isValid()) {
121 // Cancel this task since it is not valid anymore.
122 this.cancel(taskKey);
123 delete this.tasks_[taskKey];
124 }
125 }
126
127 // Replace exchange id.
James Hawkins 2013/02/20 04:31:06 What does 'exchange id' mean?
James Hawkins 2013/02/20 04:31:06 nit: s/id/ID/
mtomasz 2013/02/21 05:19:24 Should be extension id. Fixed.
mtomasz 2013/02/21 05:19:24 Done.
mtomasz 2013/02/21 05:19:24 git grep 'extension id' | wc -l 152 git grep 'ext
James Hawkins 2013/02/21 23:20:36 I'm not going to block the CL on this, but for ref
128 var sourceId = chrome.i18n.getMessage('@@extension_id');
129 var targetId = ImageLoader.EXTENSION_ID;
130
131 url = url.replace('filesystem:chrome-extension://' + sourceId,
132 'filesystem:chrome-extension://' + targetId);
133
134 // Try to load from cache, if available.
135 var cacheKey = ImageLoader.Client.Cache.createKey(url, opt_options);
136 if (opt_options.cache) {
137 // Load from cache.
138 // TODO(mtomasz): Add cache invalidating if the file has changed.
139 var cachedData = this.cache_.loadImage(cacheKey);
140 if (cachedData) {
141 callback({ status: 'success', data: cachedData });
142 return null;
143 }
144 } else {
145 // Remove from cache.
146 this.cache_.removeImage(cacheKey);
147 }
148
149 // Not available in cache, performing a request to a remote extension.
150 request = opt_options;
151 this.lastTaskId_++;
152 var task = { isValid: opt_isValid, accept: function(result) {
153 // Save to cache.
154 if (result.status == 'success' && opt_options.cache)
155 this.cache_.saveImage(cacheKey, result.data);
156 callback(result);
157 }.bind(this) };
158 this.tasks_[this.lastTaskId_] = task;
159
160 request.url = url;
161 request.taskId = this.lastTaskId_;
162
163 this.port_.postMessage(request);
164 return request.taskId;
165 };
166
167 /**
168 * Cancels the request.
169 * @param {number} taskId Task id returned by ImageLoader.Client.load().
170 */
171 ImageLoader.Client.prototype.cancel = function(taskId) {
172 this.port_.postMessage({ taskId: taskId, cancel: true });
173 };
174
175 /**
176 * Prints the cache usage statistics.
177 */
178 ImageLoader.Client.prototype.stat = function() {
179 this.cache_.stat();
180 };
181
182 /**
183 * Least Recently Used (LRU) cache implementation to be used by
184 * ImageLoader.Client class. It has memory constraints, so it will never
185 * exceed specified memory limit defined in MEMORY_LIMIT.
186 *
187 * @constructor
188 */
189 ImageLoader.Client.Cache = function() {
190 this.images_ = [];
191 this.size_ = 0;
192 };
193
194 /**
195 * Memory limit for images data in bytes.
196 *
197 * @const
198 * @type {number}
199 */
200 ImageLoader.Client.Cache.MEMORY_LIMIT = 100 * 1024 * 1024; // 100 MB.
201
202 /**
203 * Creates a cache key. Static.
204 *
205 * @param {string} url Image url.
206 * @param {Object=} opt_options Loader options as a hash array.
207 * @return {string} Cache key.
208 */
209 ImageLoader.Client.Cache.createKey = function(url, opt_options) {
210 var array = opt_options || {};
211 array.url = url;
212 return JSON.stringify(array);
213 };
214
215 /**
216 * Evicts the least used elements in cache to make space for a new image.
217 *
218 * @param {number} size Requested size.
219 * @private
220 */
221 ImageLoader.Client.Cache.prototype.evictCache_ = function(size) {
222 // Sort from the most recent to the oldest.
223 this.images_.sort(function(a, b) {
224 return b.timestamp - a.timestamp;
225 });
226
227 while (this.images_.length > 0 &&
228 (ImageLoader.Client.Cache.MEMORY_LIMIT - this.size_ < size)) {
229 var entry = this.images_.pop();
230 this.size_ -= entry.data.length;
231 }
232 };
233
234 /**
235 * Saves an image in the cache.
236 *
237 * @param {string} key Cache key.
238 * @param {string} data Image data.
239 */
240 ImageLoader.Client.Cache.prototype.saveImage = function(key, data) {
241 this.evictCache_(data.length);
242 if (ImageLoader.Client.Cache.MEMORY_LIMIT - this.size_ >= data.length) {
243 this.images_[key] = { timestamp: Date.now(), data: data };
244 this.size_ += data.length;
245 }
246 };
247
248 /**
249 * Loads an image from the cache (if available) or returns null.
250 *
251 * @param {string} key Cache key.
252 * @return {?string} Data of the loaded image or null.
253 */
254 ImageLoader.Client.Cache.prototype.loadImage = function(key) {
255 if (!(key in this.images_))
256 return null;
257
258 var entry = this.images_[key];
259 entry.timestamp = Date.now();
260 return entry.data;
261 };
262
263 /**
264 * Prints the cache usage stats.
265 */
266 ImageLoader.Client.Cache.prototype.stat = function() {
267 console.log('Cache entries: ' + Object.keys(this.images_).length);
268 console.log('Usage: ' + Math.round(this.size_ /
269 ImageLoader.Client.Cache.MEMORY_LIMIT * 100.0) + '%');
270 };
271
272 /**
273 * Removes the image from the cache.
274 * @param {string} key Cache key.
275 */
276 ImageLoader.Client.Cache.prototype.removeImage = function(key) {
277 if (!(key in this.images_))
278 return;
279
280 var entry = this.images_[key];
281 this.size_ -= entry.data.length;
282 delete this.images_[key];
283 };
284
285 // Helper functions.
286
287 /**
288 * Loads and resizes and image. Use opt_isValid to easily cancel requests
289 * which are not valid anymore, which will reduce cpu consumption.
290 *
291 * @param {string} url Url of the requested image.
292 * @param {Image} image Image node to load the requested picture into.
293 * @param {Object} options Loader options, such as: scale, maxHeight, width,
294 * height and/or cache.
295 * @param {function=} onSuccess Callback for success.
296 * @param {function=} onError Callback for failure.
297 * @param {function=} opt_isValid Function returning false in case
298 * a request is not valid anymore, eg. parent node has been detached.
299 * @return {?number} Remote task id or null if loaded from cache.
300 */
301 ImageLoader.Client.loadToImage = function(url, image, options, onSuccess,
302 onError, opt_isValid) {
303 var callback = function(result) {
304 if (result.status == 'error') {
305 onError();
306 return;
307 }
308 image.src = result.data;
309 onSuccess();
310 };
311
312 return ImageLoader.Client.load(url, callback, options, opt_isValid);
313 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698