Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 }; | |
| OLD | NEW |