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

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

Issue 14623021: Introduce a priority queue to the image loader. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Cleaned up. Created 7 years, 7 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
« no previous file with comments | « chrome/browser/resources/image_loader/client.js ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * Loads and resizes an image. 6 * Loads and resizes an image.
7 * @constructor 7 * @constructor
8 */ 8 */
9 var ImageLoader = function() { 9 var ImageLoader = function() {
10 /** 10 /**
11 * Hash array of active requests. 11 * Hash array of active requests.
12 * @type {Object} 12 * @type {Object}
13 * @private 13 * @private
14 */ 14 */
15 this.requests_ = {}; 15 this.requests_ = {};
16 16
17 /** 17 /**
18 * Persistent cache object. 18 * Persistent cache object.
19 * @type {ImageLoader.Cache} 19 * @type {ImageLoader.Cache}
20 * @private 20 * @private
21 */ 21 */
22 this.cache_ = new ImageLoader.Cache(); 22 this.cache_ = new ImageLoader.Cache();
23 23
24 /**
25 * Manages pending requests and runs them in order of priorities.
26 * @type {ImageLoader.Worker}
27 * @private
28 */
29 this.worker_ = new ImageLoader.Worker();
30
24 chrome.fileBrowserPrivate.requestLocalFileSystem(function(filesystem) { 31 chrome.fileBrowserPrivate.requestLocalFileSystem(function(filesystem) {
25 // TODO(mtomasz): Handle. 32 // TODO(mtomasz): Handle.
26 }); 33 });
27 34
28 chrome.extension.onMessageExternal.addListener(function(request, 35 chrome.extension.onMessageExternal.addListener(function(request,
29 sender, 36 sender,
30 sendResponse) { 37 sendResponse) {
31 if (ImageLoader.ALLOWED_CLIENTS.indexOf(sender.id) !== -1) 38 if (ImageLoader.ALLOWED_CLIENTS.indexOf(sender.id) !== -1)
32 return this.onMessage_(sender.id, request, sendResponse); 39 return this.onMessage_(sender.id, request, sendResponse);
33 }.bind(this)); 40 }.bind(this));
(...skipping 22 matching lines...) Expand all
56 ImageLoader.prototype.onMessage_ = function(senderId, request, callback) { 63 ImageLoader.prototype.onMessage_ = function(senderId, request, callback) {
57 var requestId = senderId + ':' + request.taskId; 64 var requestId = senderId + ':' + request.taskId;
58 if (request.cancel) { 65 if (request.cancel) {
59 // Cancel a task. 66 // Cancel a task.
60 if (requestId in this.requests_) { 67 if (requestId in this.requests_) {
61 this.requests_[requestId].cancel(); 68 this.requests_[requestId].cancel();
62 delete this.requests_[requestId]; 69 delete this.requests_[requestId];
63 } 70 }
64 return false; // No callback calls. 71 return false; // No callback calls.
65 } else { 72 } else {
66 // Start a task. 73 // Create a request task and add it to the worker (queue).
67 this.requests_[requestId] = 74 var requestTask = new ImageLoader.Request(this.cache_, request, callback);
68 new ImageLoader.Request(this.cache_, request, callback); 75 this.requests_[requestId] = requestTask;
76 this.worker_.add(requestTask);
69 return true; // Request will call the callback. 77 return true; // Request will call the callback.
70 } 78 }
71 }; 79 };
72 80
73 /** 81 /**
74 * Returns the singleton instance. 82 * Returns the singleton instance.
75 * @return {ImageLoader} ImageLoader object. 83 * @return {ImageLoader} ImageLoader object.
76 */ 84 */
77 ImageLoader.getInstance = function() { 85 ImageLoader.getInstance = function() {
78 if (!ImageLoader.instance_) 86 if (!ImageLoader.instance_)
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after
223 * @private 231 * @private
224 */ 232 */
225 this.canvas_ = document.createElement('canvas'); 233 this.canvas_ = document.createElement('canvas');
226 234
227 /** 235 /**
228 * @type {CanvasRenderingContext2D} 236 * @type {CanvasRenderingContext2D}
229 * @private 237 * @private
230 */ 238 */
231 this.context_ = this.canvas_.getContext('2d'); 239 this.context_ = this.canvas_.getContext('2d');
232 240
233 // Process the request. Try to load from cache. If it fails, then download. 241 /**
242 * Callback to be called once downloading is finished.
243 * @type {function()}
244 * @private
245 */
246 this.downloadCallback_ = null;
247 };
248
249 /**
250 * Returns priority of the request. The higher priority, the faster it will
251 * be handled. The highest priority is 0. The default one is 2.
252 *
253 * @return {number} Priority.
254 */
255 ImageLoader.Request.prototype.getPriority = function() {
256 return (this.request_.priority !== undefined) ? this.request_.priority : 2;
257 };
258
259 /**
260 * Tries to load the image from cache if exists and sends the response.
261 *
James Hawkins 2013/05/15 17:37:18 nit: Remove blank line.
mtomasz 2013/05/16 02:18:01 Why? We put an empty line after the description fo
262 * @param {function()} onSuccess Success callback.
263 * @param {function()} onFailure Failure callback.
264 */
265 ImageLoader.Request.prototype.loadFromCacheAndProcess = function(
266 onSuccess, onFailure) {
234 this.loadFromCache_( 267 this.loadFromCache_(
235 this.sendImageData_.bind(this), 268 function(data) { // Found in cache.
236 function() { // Failure, not in cache. 269 this.sendImageData_(data);
237 this.downloadOriginal_(this.onImageLoad_.bind(this), 270 onSuccess();
238 this.onImageError_.bind(this)); 271 }.bind(this),
239 }.bind(this)); 272 onFailure); // Not found in cache.
273 };
274
275 /**
276 * Tries to download the image, resizes and sends the response.
277 * @param {function()} callback Completion callback.
278 */
279 ImageLoader.Request.prototype.downloadAndProcess = function(callback) {
280 if (this.downloadCallback_)
281 throw new Error('Downloading already started.');
282
283 this.downloadCallback_ = callback;
284 this.downloadOriginal_(this.onImageLoad_.bind(this),
285 this.onImageError_.bind(this));
240 }; 286 };
241 287
242 /** 288 /**
243 * Fetches the image from the persistent cache. 289 * Fetches the image from the persistent cache.
290 *
James Hawkins 2013/05/15 17:37:18 nit: Please remove all of these added blank lines.
244 * @param {function()} onSuccess Success callback. 291 * @param {function()} onSuccess Success callback.
245 * @param {function()} onFailure Failure callback. 292 * @param {function()} onFailure Failure callback.
246 * @private 293 * @private
247 */ 294 */
248 ImageLoader.Request.prototype.loadFromCache_ = function(onSuccess, onFailure) { 295 ImageLoader.Request.prototype.loadFromCache_ = function(onSuccess, onFailure) {
249 var cacheKey = ImageLoader.Cache.createKey(this.request_); 296 var cacheKey = ImageLoader.Cache.createKey(this.request_);
250 297
251 if (!this.request_.cache) { 298 if (!this.request_.cache) {
252 // Cache is disabled for this request; therefore, remove it from cache 299 // Cache is disabled for this request; therefore, remove it from cache
253 // if existed. 300 // if existed.
254 this.cache_.removeImage(cacheKey); 301 this.cache_.removeImage(cacheKey);
255 onFailure(); 302 onFailure();
256 return; 303 return;
257 } 304 }
258 305
259 if (!this.request_.timestamp) { 306 if (!this.request_.timestamp) {
260 // Persistent cache is available only when a timestamp is provided. 307 // Persistent cache is available only when a timestamp is provided.
261 onFailure(); 308 onFailure();
262 return; 309 return;
263 } 310 }
264 311
265 this.cache_.loadImage(cacheKey, 312 this.cache_.loadImage(cacheKey,
266 this.request_.timestamp, 313 this.request_.timestamp,
267 onSuccess, 314 onSuccess,
268 onFailure); 315 onFailure);
269 }; 316 };
270 317
271 /** 318 /**
272 * Saves the image to the persistent cache. 319 * Saves the image to the persistent cache.
320 *
273 * @param {string} data The image's data. 321 * @param {string} data The image's data.
274 * @private 322 * @private
275 */ 323 */
276 ImageLoader.Request.prototype.saveToCache_ = function(data) { 324 ImageLoader.Request.prototype.saveToCache_ = function(data) {
277 if (!this.request_.cache || !this.request_.timestamp) { 325 if (!this.request_.cache || !this.request_.timestamp) {
278 // Persistent cache is available only when a timestamp is provided. 326 // Persistent cache is available only when a timestamp is provided.
279 return; 327 return;
280 } 328 }
281 329
282 var cacheKey = ImageLoader.Cache.createKey(this.request_); 330 var cacheKey = ImageLoader.Cache.createKey(this.request_);
283 this.cache_.saveImage(cacheKey, 331 this.cache_.saveImage(cacheKey,
284 data, 332 data,
285 this.request_.timestamp); 333 this.request_.timestamp);
286 }; 334 };
287 335
288 /** 336 /**
289 * Downloads an image directly or for remote resources using the XmlHttpRequest. 337 * Downloads an image directly or for remote resources using the XmlHttpRequest.
338 *
290 * @param {function()} onSuccess Success callback. 339 * @param {function()} onSuccess Success callback.
291 * @param {function()} onFailure Failure callback. 340 * @param {function()} onFailure Failure callback.
292 * @private 341 * @private
293 */ 342 */
294 ImageLoader.Request.prototype.downloadOriginal_ = function( 343 ImageLoader.Request.prototype.downloadOriginal_ = function(
295 onSuccess, onFailure) { 344 onSuccess, onFailure) {
296 this.image_.onload = onSuccess; 345 this.image_.onload = onSuccess;
297 this.image_.onerror = onFailure; 346 this.image_.onerror = onFailure;
298 347
299 if (window.harness || !this.request_.url.match(/^https?:/)) { 348 if (!this.request_.url.match(/^https?:/)) {
300 // Download directly. 349 // Download directly.
301 this.image_.src = this.request_.url; 350 this.image_.src = this.request_.url;
302 return; 351 return;
303 } 352 }
304 353
305 // Download using an xhr request. 354 // Download using an xhr request.
306 this.xhr_.responseType = 'blob'; 355 this.xhr_.responseType = 'blob';
307 356
308 this.xhr_.onerror = this.image_.onerror; 357 this.xhr_.onerror = this.image_.onerror;
309 this.xhr_.onload = function() { 358 this.xhr_.onload = function() {
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
356 ImageLoader.Request.prototype.sendImageData_ = function(data) { 405 ImageLoader.Request.prototype.sendImageData_ = function(data) {
357 this.sendResponse_({status: 'success', 406 this.sendResponse_({status: 'success',
358 data: data, 407 data: data,
359 taskId: this.request_.taskId}); 408 taskId: this.request_.taskId});
360 }; 409 };
361 410
362 /** 411 /**
363 * Handler, when contents are loaded into the image element. Performs resizing 412 * Handler, when contents are loaded into the image element. Performs resizing
364 * and finalizes the request process. 413 * and finalizes the request process.
365 * 414 *
415 * @param {function()} callback Completion callback.
366 * @private 416 * @private
367 */ 417 */
368 ImageLoader.Request.prototype.onImageLoad_ = function() { 418 ImageLoader.Request.prototype.onImageLoad_ = function(callback) {
369 ImageLoader.resize(this.image_, this.canvas_, this.request_); 419 ImageLoader.resize(this.image_, this.canvas_, this.request_);
370 this.sendImage_(); 420 this.sendImage_();
371 this.cleanup_(); 421 this.cleanup_();
422 this.downloadCallback_();
372 }; 423 };
373 424
374 /** 425 /**
375 * Handler, when loading of the image fails. Sends a failure response and 426 * Handler, when loading of the image fails. Sends a failure response and
376 * finalizes the request process. 427 * finalizes the request process.
377 * 428 *
429 * @param {function()} callback Completion callback.
378 * @private 430 * @private
379 */ 431 */
380 ImageLoader.Request.prototype.onImageError_ = function() { 432 ImageLoader.Request.prototype.onImageError_ = function(callback) {
381 this.sendResponse_({status: 'error', 433 this.sendResponse_({status: 'error',
382 taskId: this.request_.taskId}); 434 taskId: this.request_.taskId});
383 this.cleanup_(); 435 this.cleanup_();
436 this.downloadCallback_();
384 }; 437 };
385 438
386 /** 439 /**
387 * Cancels the request. 440 * Cancels the request.
388 */ 441 */
389 ImageLoader.Request.prototype.cancel = function() { 442 ImageLoader.Request.prototype.cancel = function() {
390 this.cleanup_(); 443 this.cleanup_();
444
445 // If downloading has started, then call the callback.
446 if (this.downloadCallback_)
447 this.downloadCallback_();
391 }; 448 };
392 449
393 /** 450 /**
394 * Cleans up memory used by this request. 451 * Cleans up memory used by this request.
395 * @private 452 * @private
396 */ 453 */
397 ImageLoader.Request.prototype.cleanup_ = function() { 454 ImageLoader.Request.prototype.cleanup_ = function() {
398 this.image_.onerror = function() {}; 455 this.image_.onerror = function() {};
399 this.image_.onload = function() {}; 456 this.image_.onload = function() {};
400 457
401 // Transparent 1x1 pixel gif, to force garbage collecting. 458 // Transparent 1x1 pixel gif, to force garbage collecting.
402 this.image_.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAA' + 459 this.image_.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAA' +
403 'ABAAEAAAICTAEAOw=='; 460 'ABAAEAAAICTAEAOw==';
404 461
405 this.xhr_.onerror = function() {}; 462 this.xhr_.onerror = function() {};
406 this.xhr_.onload = function() {}; 463 this.xhr_.onload = function() {};
407 this.xhr_.abort(); 464 this.xhr_.abort();
408 465
409 // Dispose memory allocated by Canvas. 466 // Dispose memory allocated by Canvas.
410 this.canvas_.width = 0; 467 this.canvas_.width = 0;
411 this.canvas_.height = 0; 468 this.canvas_.height = 0;
412 }; 469 };
413 470
414 /** 471 /**
472 * Worker for requests. Fetches requests from a queue and processes them
473 * synchronously, taking into account priorities. The highest priority is 0.
474 */
475 ImageLoader.Worker = function() {
476 /**
477 * List of pending requests.
478 * @type {ImageLoader.Request}
479 * @private
480 */
481 this.pendingRequests_ = [];
482
483 /**
484 * List of requests being processed.
485 * @type {ImageLoader.Request}
486 * @private
487 */
488 this.activeRequests_ = [];
489 };
490
491 /**
492 * Maximum requests to be run in parallel.
493 * @type {number}
494 * @const
495 */
496 ImageLoader.Worker.MAXIMUM_IN_PARALLEL = 5;
497
498 /**
499 * Adds a request to the internal priority queue and executes it when requests
500 * with higher priorities are finished. If the result is cached, then it is
501 * processed immediately.
502 *
503 * @param {ImageLoader.Request} request Request object.
504 */
505 ImageLoader.Worker.prototype.add = function(request) {
506 request.loadFromCacheAndProcess(function() { }, function() {
507 // Not found in cache, then add to pending requests.
508 this.pendingRequests_.push(request);
509
510 // Sort requests by priorities.
511 this.pendingRequests_.sort(function(a, b) {
512 return a.getPriority() - b.getPriority();
513 });
514
515 // Handle the most important requests (if possible).
516 this.continue_();
517 }.bind(this));
518 };
519
520 /**
521 * Processes pending requests from the queue. There is no guarantee that
522 * all of the tasks will be processed at once.
523 *
524 * @private
525 */
526 ImageLoader.Worker.prototype.continue_ = function() {
527 for (var index = 0; index < this.pendingRequests_.length; index++) {
528 var request = this.pendingRequests_[index];
529
530 // Run only up to MAXIMUM_IN_PARALLEL in the same time.
531 if (Object.keys(this.activeRequests_).length ==
532 ImageLoader.Worker.MAXIMUM_IN_PARALLEL) {
533 return;
534 }
535
536 delete this.pendingRequests_.splice(index, 1);
537 this.activeRequests_.push(request);
538
539 request.downloadAndProcess(this.finish_.bind(this, request));
540 }
541 };
542
543 /**
544 * Handles finished requests.
545 *
546 * @param {ImageLoader.Request} request Finished request.
547 * @private
548 */
549 ImageLoader.Worker.prototype.finish_ = function(request) {
550 var index = this.activeRequests_.indexOf(request);
551 if (index < 0)
552 console.warn('Request not found.');
553 delete this.activeRequests_.splice(index, 1);
554
555 // Handle the most important requests (if possible).
556 this.continue_();
557 };
558
559 /**
415 * Persistent cache storing images in an indexed database on the hard disk. 560 * Persistent cache storing images in an indexed database on the hard disk.
416 * @constructor 561 * @constructor
417 */ 562 */
418 ImageLoader.Cache = function() { 563 ImageLoader.Cache = function() {
419 /** 564 /**
420 * IndexedDB database handle. 565 * IndexedDB database handle.
421 * @type {IDBDatabase} 566 * @type {IDBDatabase}
422 * @private 567 * @private
423 */ 568 */
424 this.db_ = null; 569 this.db_ = null;
(...skipping 28 matching lines...) Expand all
453 * @type {string} 598 * @type {string}
454 * @const 599 * @const
455 */ 600 */
456 ImageLoader.Cache.DB_NAME = 'image-loader'; 601 ImageLoader.Cache.DB_NAME = 'image-loader';
457 602
458 /** 603 /**
459 * Cache database version. 604 * Cache database version.
460 * @type {number} 605 * @type {number}
461 * @const 606 * @const
462 */ 607 */
463 ImageLoader.Cache.DB_VERSION = 8; 608 ImageLoader.Cache.DB_VERSION = 11;
464 609
465 /** 610 /**
466 * Memory limit for images data in bytes. 611 * Memory limit for images data in bytes.
467 * 612 *
468 * @const 613 * @const
469 * @type {number} 614 * @type {number}
470 */ 615 */
471 ImageLoader.Cache.MEMORY_LIMIT = 250 * 1024 * 1024; // 250 MB. 616 ImageLoader.Cache.MEMORY_LIMIT = 250 * 1024 * 1024; // 250 MB.
472 617
473 /** 618 /**
(...skipping 329 matching lines...) Expand 10 before | Expand all | Expand 10 after
803 948
804 metadataRequest.onerror = function() { 949 metadataRequest.onerror = function() {
805 console.error('Failed to remove an image.'); 950 console.error('Failed to remove an image.');
806 metadataReceived = true; 951 metadataReceived = true;
807 onPartialSuccess(); 952 onPartialSuccess();
808 }; 953 };
809 }; 954 };
810 955
811 // Load the extension. 956 // Load the extension.
812 ImageLoader.getInstance(); 957 ImageLoader.getInstance();
OLDNEW
« no previous file with comments | « chrome/browser/resources/image_loader/client.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698