| Index: chrome/browser/resources/image_loader/client.js
|
| diff --git a/chrome/browser/resources/image_loader/client.js b/chrome/browser/resources/image_loader/client.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c2c714930726dd238da656fde96c0873026e4f5f
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/image_loader/client.js
|
| @@ -0,0 +1,302 @@
|
| +// Copyright 2013 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +var ImageLoader = ImageLoader || {};
|
| +
|
| +/**
|
| + * Image loader's extension id.
|
| + * @const
|
| + * @type {string}
|
| + */
|
| +ImageLoader.EXTENSION_ID = 'pmfjbimdmchhbnneeidfognadeopoehp';
|
| +
|
| +/**
|
| + * Client used to connect to the remote ImageLoader extension. Client class runs
|
| + * in the extension, where the client.js is included (eg. Files.app).
|
| + * It sends remote requests using IPC to the ImageLoader class and forwards
|
| + * its responses.
|
| + *
|
| + * Implements cache, which is stored in the calling extension.
|
| + *
|
| + * @constructor
|
| + */
|
| +ImageLoader.Client = function() {
|
| + /**
|
| + * @type {Port}
|
| + * @private
|
| + */
|
| + this.port_ = chrome.extension.connect(ImageLoader.EXTENSION_ID);
|
| + this.port_.onMessage.addListener(this.handleMessage_.bind(this));
|
| +
|
| + /**
|
| + * Hash array with active tasks.
|
| + * @type {Object}
|
| + * @private
|
| + */
|
| + this.tasks_ = {};
|
| +
|
| + /**
|
| + * @type {number}
|
| + * @private
|
| + */
|
| + this.lastTaskId_ = 0;
|
| +
|
| + /**
|
| + * LRU cache for images.
|
| + * @type {ImageLoader.Client.Cache}
|
| + * @private
|
| + */
|
| + this.cache_ = new ImageLoader.Client.Cache();
|
| +};
|
| +
|
| +/**
|
| + * Returns a singleton instance.
|
| + * @return {ImageLoader.Client} ImageLoader.Client instance.
|
| + */
|
| +ImageLoader.Client.getInstance = function() {
|
| + if (!ImageLoader.Client.instance_)
|
| + ImageLoader.Client.instance_ = new ImageLoader.Client();
|
| + return ImageLoader.Client.instance_;
|
| +};
|
| +
|
| +/**
|
| + * Handles a message from the remote image loader and calls the registered
|
| + * callback to pass the response back to the requester.
|
| + *
|
| + * @param {Object} message Response message as a hash array.
|
| + * @private
|
| + */
|
| +ImageLoader.Client.prototype.handleMessage_ = function(message) {
|
| + if (!(message.taskId in this.tasks_)) {
|
| + // This task has been canceled, but was already fetched, so it's result
|
| + // should be discarded anyway.
|
| + return;
|
| + }
|
| +
|
| + var task = this.tasks_[message.taskId];
|
| +
|
| + // Check if the task is still valid.
|
| + if (task.isValid())
|
| + task.accept(message);
|
| +
|
| + delete this.tasks_[message.taskId];
|
| +};
|
| +
|
| +/**
|
| + * Loads and resizes and image. Use opt_isValid to easily cancel requests
|
| + * which are not valid anymore, which will reduce cpu consumption.
|
| + *
|
| + * @param {string} url Url of the requested image.
|
| + * @param {function} callback Callback used to return response.
|
| + * @param {Object=} opt_options Loader options, such as: scale, maxHeight,
|
| + * width, height and/or cache.
|
| + * @param {function=} opt_isValid Function returning false in case
|
| + * a request is not valid anymore, eg. parent node has been detached.
|
| + * @return {?number} Remote task id or null if loaded from cache.
|
| + */
|
| +ImageLoader.Client.prototype.load = function(
|
| + url, callback, opt_options, opt_isValid) {
|
| + opt_options = opt_options || {};
|
| + opt_isValid = opt_isValid || function() { return true; };
|
| +
|
| + // Cancel old, invalid tasks.
|
| + var taskKeys = Object.keys(this.tasks_);
|
| + for (var index = 0; index < taskKeys.length; index++) {
|
| + var taskKey = taskKeys[index];
|
| + var task = this.tasks_[taskKey];
|
| + if (!task.isValid()) {
|
| + // Cancel this task since it is not valid anymore.
|
| + this.cancel(taskKey);
|
| + delete this.tasks_[taskKey];
|
| + }
|
| + }
|
| +
|
| + // Replace the extension id.
|
| + var sourceId = chrome.i18n.getMessage('@@extension_id');
|
| + var targetId = ImageLoader.EXTENSION_ID;
|
| +
|
| + url = url.replace('filesystem:chrome-extension://' + sourceId,
|
| + 'filesystem:chrome-extension://' + targetId);
|
| +
|
| + // Try to load from cache, if available.
|
| + var cacheKey = ImageLoader.Client.Cache.createKey(url, opt_options);
|
| + if (opt_options.cache) {
|
| + // Load from cache.
|
| + // TODO(mtomasz): Add cache invalidating if the file has changed.
|
| + var cachedData = this.cache_.loadImage(cacheKey);
|
| + if (cachedData) {
|
| + callback({ status: 'success', data: cachedData });
|
| + return null;
|
| + }
|
| + } else {
|
| + // Remove from cache.
|
| + this.cache_.removeImage(cacheKey);
|
| + }
|
| +
|
| + // Not available in cache, performing a request to a remote extension.
|
| + request = opt_options;
|
| + this.lastTaskId_++;
|
| + var task = { isValid: opt_isValid, accept: function(result) {
|
| + // Save to cache.
|
| + if (result.status == 'success' && opt_options.cache)
|
| + this.cache_.saveImage(cacheKey, result.data);
|
| + callback(result);
|
| + }.bind(this) };
|
| + this.tasks_[this.lastTaskId_] = task;
|
| +
|
| + request.url = url;
|
| + request.taskId = this.lastTaskId_;
|
| +
|
| + this.port_.postMessage(request);
|
| + return request.taskId;
|
| +};
|
| +
|
| +/**
|
| + * Cancels the request.
|
| + * @param {number} taskId Task id returned by ImageLoader.Client.load().
|
| + */
|
| +ImageLoader.Client.prototype.cancel = function(taskId) {
|
| + this.port_.postMessage({ taskId: taskId, cancel: true });
|
| +};
|
| +
|
| +/**
|
| + * Prints the cache usage statistics.
|
| + */
|
| +ImageLoader.Client.prototype.stat = function() {
|
| + this.cache_.stat();
|
| +};
|
| +
|
| +/**
|
| + * Least Recently Used (LRU) cache implementation to be used by
|
| + * ImageLoader.Client class. It has memory constraints, so it will never
|
| + * exceed specified memory limit defined in MEMORY_LIMIT.
|
| + *
|
| + * @constructor
|
| + */
|
| +ImageLoader.Client.Cache = function() {
|
| + this.images_ = [];
|
| + this.size_ = 0;
|
| +};
|
| +
|
| +/**
|
| + * Memory limit for images data in bytes.
|
| + *
|
| + * @const
|
| + * @type {number}
|
| + */
|
| +ImageLoader.Client.Cache.MEMORY_LIMIT = 100 * 1024 * 1024; // 100 MB.
|
| +
|
| +/**
|
| + * Creates a cache key.
|
| + *
|
| + * @param {string} url Image url.
|
| + * @param {Object=} opt_options Loader options as a hash array.
|
| + * @return {string} Cache key.
|
| + */
|
| +ImageLoader.Client.Cache.createKey = function(url, opt_options) {
|
| + var array = opt_options || {};
|
| + array.url = url;
|
| + return JSON.stringify(array);
|
| +};
|
| +
|
| +/**
|
| + * Evicts the least used elements in cache to make space for a new image.
|
| + *
|
| + * @param {number} size Requested size.
|
| + * @private
|
| + */
|
| +ImageLoader.Client.Cache.prototype.evictCache_ = function(size) {
|
| + // Sort from the most recent to the oldest.
|
| + this.images_.sort(function(a, b) {
|
| + return b.timestamp - a.timestamp;
|
| + });
|
| +
|
| + while (this.images_.length > 0 &&
|
| + (ImageLoader.Client.Cache.MEMORY_LIMIT - this.size_ < size)) {
|
| + var entry = this.images_.pop();
|
| + this.size_ -= entry.data.length;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Saves an image in the cache.
|
| + *
|
| + * @param {string} key Cache key.
|
| + * @param {string} data Image data.
|
| + */
|
| +ImageLoader.Client.Cache.prototype.saveImage = function(key, data) {
|
| + this.evictCache_(data.length);
|
| + if (ImageLoader.Client.Cache.MEMORY_LIMIT - this.size_ >= data.length) {
|
| + this.images_[key] = { timestamp: Date.now(), data: data };
|
| + this.size_ += data.length;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Loads an image from the cache (if available) or returns null.
|
| + *
|
| + * @param {string} key Cache key.
|
| + * @return {?string} Data of the loaded image or null.
|
| + */
|
| +ImageLoader.Client.Cache.prototype.loadImage = function(key) {
|
| + if (!(key in this.images_))
|
| + return null;
|
| +
|
| + var entry = this.images_[key];
|
| + entry.timestamp = Date.now();
|
| + return entry.data;
|
| +};
|
| +
|
| +/**
|
| + * Prints the cache usage stats.
|
| + */
|
| +ImageLoader.Client.Cache.prototype.stat = function() {
|
| + console.log('Cache entries: ' + Object.keys(this.images_).length);
|
| + console.log('Usage: ' + Math.round(this.size_ /
|
| + ImageLoader.Client.Cache.MEMORY_LIMIT * 100.0) + '%');
|
| +};
|
| +
|
| +/**
|
| + * Removes the image from the cache.
|
| + * @param {string} key Cache key.
|
| + */
|
| +ImageLoader.Client.Cache.prototype.removeImage = function(key) {
|
| + if (!(key in this.images_))
|
| + return;
|
| +
|
| + var entry = this.images_[key];
|
| + this.size_ -= entry.data.length;
|
| + delete this.images_[key];
|
| +};
|
| +
|
| +// Helper functions.
|
| +
|
| +/**
|
| + * Loads and resizes and image. Use opt_isValid to easily cancel requests
|
| + * which are not valid anymore, which will reduce cpu consumption.
|
| + *
|
| + * @param {string} url Url of the requested image.
|
| + * @param {Image} image Image node to load the requested picture into.
|
| + * @param {Object} options Loader options, such as: scale, maxHeight, width,
|
| + * height and/or cache.
|
| + * @param {function=} onSuccess Callback for success.
|
| + * @param {function=} onError Callback for failure.
|
| + * @param {function=} opt_isValid Function returning false in case
|
| + * a request is not valid anymore, eg. parent node has been detached.
|
| + * @return {?number} Remote task id or null if loaded from cache.
|
| + */
|
| +ImageLoader.Client.loadToImage = function(url, image, options, onSuccess,
|
| + onError, opt_isValid) {
|
| + var callback = function(result) {
|
| + if (result.status == 'error') {
|
| + onError();
|
| + return;
|
| + }
|
| + image.src = result.data;
|
| + onSuccess();
|
| + };
|
| +
|
| + return ImageLoader.Client.getInstance().load(
|
| + url, callback, options, opt_isValid);
|
| +};
|
|
|