Chromium Code Reviews| Index: chrome/browser/resources/image_loader/image_loader.js |
| diff --git a/chrome/browser/resources/image_loader/image_loader.js b/chrome/browser/resources/image_loader/image_loader.js |
| index 1502d92febd0f77c285e9d11c1481cbfa5b96dbb..0758d24e6813505e232d4e7cbbdd094ec7c5d640 100644 |
| --- a/chrome/browser/resources/image_loader/image_loader.js |
| +++ b/chrome/browser/resources/image_loader/image_loader.js |
| @@ -21,6 +21,13 @@ var ImageLoader = function() { |
| */ |
| this.cache_ = new ImageLoader.Cache(); |
| + /** |
| + * Manages pending requests and runs them in order of priorities. |
| + * @type {ImageLoader.Worker} |
| + * @private |
| + */ |
| + this.worker_ = new ImageLoader.Worker(); |
| + |
| chrome.fileBrowserPrivate.requestLocalFileSystem(function(filesystem) { |
| // TODO(mtomasz): Handle. |
| }); |
| @@ -63,9 +70,10 @@ ImageLoader.prototype.onMessage_ = function(senderId, request, callback) { |
| } |
| return false; // No callback calls. |
| } else { |
| - // Start a task. |
| - this.requests_[requestId] = |
| - new ImageLoader.Request(this.cache_, request, callback); |
| + // Create a request task and add it to the worker (queue). |
| + var requestTask = new ImageLoader.Request(this.cache_, request, callback); |
| + this.requests_[requestId] = requestTask; |
| + this.worker_.add(requestTask); |
| return true; // Request will call the callback. |
| } |
| }; |
| @@ -230,17 +238,56 @@ ImageLoader.Request = function(cache, request, callback) { |
| */ |
| this.context_ = this.canvas_.getContext('2d'); |
| - // Process the request. Try to load from cache. If it fails, then download. |
| + /** |
| + * Callback to be called once downloading is finished. |
| + * @type {function()} |
| + * @private |
| + */ |
| + this.downloadCallback_ = null; |
| +}; |
| + |
| +/** |
| + * Returns priority of the request. The higher priority, the faster it will |
| + * be handled. The highest priority is 0. The default one is 2. |
| + * |
| + * @return {number} Priority. |
| + */ |
| +ImageLoader.Request.prototype.getPriority = function() { |
| + return (this.request_.priority !== undefined) ? this.request_.priority : 2; |
| +}; |
| + |
| +/** |
| + * Tries to load the image from cache if exists and sends the response. |
| + * |
|
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
|
| + * @param {function()} onSuccess Success callback. |
| + * @param {function()} onFailure Failure callback. |
| + */ |
| +ImageLoader.Request.prototype.loadFromCacheAndProcess = function( |
| + onSuccess, onFailure) { |
| this.loadFromCache_( |
| - this.sendImageData_.bind(this), |
| - function() { // Failure, not in cache. |
| - this.downloadOriginal_(this.onImageLoad_.bind(this), |
| - this.onImageError_.bind(this)); |
| - }.bind(this)); |
| + function(data) { // Found in cache. |
| + this.sendImageData_(data); |
| + onSuccess(); |
| + }.bind(this), |
| + onFailure); // Not found in cache. |
| +}; |
| + |
| +/** |
| + * Tries to download the image, resizes and sends the response. |
| + * @param {function()} callback Completion callback. |
| + */ |
| +ImageLoader.Request.prototype.downloadAndProcess = function(callback) { |
| + if (this.downloadCallback_) |
| + throw new Error('Downloading already started.'); |
| + |
| + this.downloadCallback_ = callback; |
| + this.downloadOriginal_(this.onImageLoad_.bind(this), |
| + this.onImageError_.bind(this)); |
| }; |
| /** |
| * Fetches the image from the persistent cache. |
| + * |
|
James Hawkins
2013/05/15 17:37:18
nit: Please remove all of these added blank lines.
|
| * @param {function()} onSuccess Success callback. |
| * @param {function()} onFailure Failure callback. |
| * @private |
| @@ -270,6 +317,7 @@ ImageLoader.Request.prototype.loadFromCache_ = function(onSuccess, onFailure) { |
| /** |
| * Saves the image to the persistent cache. |
| + * |
| * @param {string} data The image's data. |
| * @private |
| */ |
| @@ -287,6 +335,7 @@ ImageLoader.Request.prototype.saveToCache_ = function(data) { |
| /** |
| * Downloads an image directly or for remote resources using the XmlHttpRequest. |
| + * |
| * @param {function()} onSuccess Success callback. |
| * @param {function()} onFailure Failure callback. |
| * @private |
| @@ -296,7 +345,7 @@ ImageLoader.Request.prototype.downloadOriginal_ = function( |
| this.image_.onload = onSuccess; |
| this.image_.onerror = onFailure; |
| - if (window.harness || !this.request_.url.match(/^https?:/)) { |
| + if (!this.request_.url.match(/^https?:/)) { |
| // Download directly. |
| this.image_.src = this.request_.url; |
| return; |
| @@ -363,24 +412,28 @@ ImageLoader.Request.prototype.sendImageData_ = function(data) { |
| * Handler, when contents are loaded into the image element. Performs resizing |
| * and finalizes the request process. |
| * |
| + * @param {function()} callback Completion callback. |
| * @private |
| */ |
| -ImageLoader.Request.prototype.onImageLoad_ = function() { |
| +ImageLoader.Request.prototype.onImageLoad_ = function(callback) { |
| ImageLoader.resize(this.image_, this.canvas_, this.request_); |
| this.sendImage_(); |
| this.cleanup_(); |
| + this.downloadCallback_(); |
| }; |
| /** |
| * Handler, when loading of the image fails. Sends a failure response and |
| * finalizes the request process. |
| * |
| + * @param {function()} callback Completion callback. |
| * @private |
| */ |
| -ImageLoader.Request.prototype.onImageError_ = function() { |
| +ImageLoader.Request.prototype.onImageError_ = function(callback) { |
| this.sendResponse_({status: 'error', |
| taskId: this.request_.taskId}); |
| this.cleanup_(); |
| + this.downloadCallback_(); |
| }; |
| /** |
| @@ -388,6 +441,10 @@ ImageLoader.Request.prototype.onImageError_ = function() { |
| */ |
| ImageLoader.Request.prototype.cancel = function() { |
| this.cleanup_(); |
| + |
| + // If downloading has started, then call the callback. |
| + if (this.downloadCallback_) |
| + this.downloadCallback_(); |
| }; |
| /** |
| @@ -412,6 +469,94 @@ ImageLoader.Request.prototype.cleanup_ = function() { |
| }; |
| /** |
| + * Worker for requests. Fetches requests from a queue and processes them |
| + * synchronously, taking into account priorities. The highest priority is 0. |
| + */ |
| +ImageLoader.Worker = function() { |
| + /** |
| + * List of pending requests. |
| + * @type {ImageLoader.Request} |
| + * @private |
| + */ |
| + this.pendingRequests_ = []; |
| + |
| + /** |
| + * List of requests being processed. |
| + * @type {ImageLoader.Request} |
| + * @private |
| + */ |
| + this.activeRequests_ = []; |
| +}; |
| + |
| +/** |
| + * Maximum requests to be run in parallel. |
| + * @type {number} |
| + * @const |
| + */ |
| +ImageLoader.Worker.MAXIMUM_IN_PARALLEL = 5; |
| + |
| +/** |
| + * Adds a request to the internal priority queue and executes it when requests |
| + * with higher priorities are finished. If the result is cached, then it is |
| + * processed immediately. |
| + * |
| + * @param {ImageLoader.Request} request Request object. |
| + */ |
| +ImageLoader.Worker.prototype.add = function(request) { |
| + request.loadFromCacheAndProcess(function() { }, function() { |
| + // Not found in cache, then add to pending requests. |
| + this.pendingRequests_.push(request); |
| + |
| + // Sort requests by priorities. |
| + this.pendingRequests_.sort(function(a, b) { |
| + return a.getPriority() - b.getPriority(); |
| + }); |
| + |
| + // Handle the most important requests (if possible). |
| + this.continue_(); |
| + }.bind(this)); |
| +}; |
| + |
| +/** |
| + * Processes pending requests from the queue. There is no guarantee that |
| + * all of the tasks will be processed at once. |
| + * |
| + * @private |
| + */ |
| +ImageLoader.Worker.prototype.continue_ = function() { |
| + for (var index = 0; index < this.pendingRequests_.length; index++) { |
| + var request = this.pendingRequests_[index]; |
| + |
| + // Run only up to MAXIMUM_IN_PARALLEL in the same time. |
| + if (Object.keys(this.activeRequests_).length == |
| + ImageLoader.Worker.MAXIMUM_IN_PARALLEL) { |
| + return; |
| + } |
| + |
| + delete this.pendingRequests_.splice(index, 1); |
| + this.activeRequests_.push(request); |
| + |
| + request.downloadAndProcess(this.finish_.bind(this, request)); |
| + } |
| +}; |
| + |
| +/** |
| + * Handles finished requests. |
| + * |
| + * @param {ImageLoader.Request} request Finished request. |
| + * @private |
| + */ |
| +ImageLoader.Worker.prototype.finish_ = function(request) { |
| + var index = this.activeRequests_.indexOf(request); |
| + if (index < 0) |
| + console.warn('Request not found.'); |
| + delete this.activeRequests_.splice(index, 1); |
| + |
| + // Handle the most important requests (if possible). |
| + this.continue_(); |
| +}; |
| + |
| +/** |
| * Persistent cache storing images in an indexed database on the hard disk. |
| * @constructor |
| */ |
| @@ -460,7 +605,7 @@ ImageLoader.Cache.DB_NAME = 'image-loader'; |
| * @type {number} |
| * @const |
| */ |
| -ImageLoader.Cache.DB_VERSION = 8; |
| +ImageLoader.Cache.DB_VERSION = 11; |
| /** |
| * Memory limit for images data in bytes. |