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

Unified 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: Simplified. 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 side-by-side diff with in-line comments
Download patch
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);
+};
« no previous file with comments | « chrome/browser/resources/file_manager/photo_import.html ('k') | chrome/browser/resources/image_loader/image_loader.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698