summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/browser_resources.grd3
-rw-r--r--chrome/browser/extensions/component_loader.cc17
-rw-r--r--chrome/browser/extensions/component_loader.h1
-rw-r--r--chrome/browser/resources/component_extension_resources.grd4
-rw-r--r--chrome/browser/resources/file_manager/css/photo_import.css15
-rw-r--r--chrome/browser/resources/file_manager/gallery.html2
-rw-r--r--chrome/browser/resources/file_manager/js/action_choice.js7
-rw-r--r--chrome/browser/resources/file_manager/js/file_selection.js1
-rw-r--r--chrome/browser/resources/file_manager/js/image_editor/image_util.js8
-rw-r--r--chrome/browser/resources/file_manager/js/main_scripts.js2
-rw-r--r--chrome/browser/resources/file_manager/js/media/media_util.js96
-rw-r--r--chrome/browser/resources/file_manager/js/photo/gallery_scripts.js2
-rw-r--r--chrome/browser/resources/file_manager/js/photo/photo_import.js3
-rw-r--r--chrome/browser/resources/file_manager/js/photo/photo_import_scripts.js2
-rw-r--r--chrome/browser/resources/file_manager/js/photo/ribbon.js1
-rw-r--r--chrome/browser/resources/file_manager/js/util.js82
-rw-r--r--chrome/browser/resources/file_manager/main.html2
-rw-r--r--chrome/browser/resources/file_manager/photo_import.html2
-rw-r--r--chrome/browser/resources/image_loader/client.js302
-rw-r--r--chrome/browser/resources/image_loader/image_loader.js305
-rw-r--r--chrome/browser/resources/image_loader/manifest.json19
-rw-r--r--chrome/common/chrome_switches.cc3
-rw-r--r--chrome/common/chrome_switches.h1
23 files changed, 777 insertions, 103 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 464f8ef..cf41df8 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -294,6 +294,9 @@
<include name="IDR_FILEMANAGER_MANIFEST" file="resources\file_manager\manifest.json" type="BINDATA" />
<include name="IDR_FILEMANAGER_MANIFEST_V1" file="resources\file_manager\manifest_v1.json" type="BINDATA" />
</if>
+ <if expr="pp_ifdef('image_loader_extension')">
+ <include name="IDR_IMAGE_LOADER_MANIFEST" file="resources\image_loader\manifest.json" type="BINDATA" />
+ </if>
<if expr="pp_ifdef('chromeos')">
<include name="IDR_WALLPAPERMANAGER_MANIFEST" file="resources\chromeos\wallpaper_manager\manifest.json" type="BINDATA" />
</if>
diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc
index 247eecc..cf1f4da 100644
--- a/chrome/browser/extensions/component_loader.cc
+++ b/chrome/browser/extensions/component_loader.cc
@@ -254,6 +254,22 @@ void ComponentLoader::AddFileManagerExtension() {
#endif // defined(FILE_MANAGER_EXTENSION)
}
+void ComponentLoader::AddImageLoaderExtension() {
+#if defined(IMAGE_LOADER_EXTENSION)
+#ifndef NDEBUG
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(switches::kImageLoaderExtensionPath)) {
+ base::FilePath image_loader_extension_path(
+ command_line->GetSwitchValuePath(switches::kImageLoaderExtensionPath));
+ Add(IDR_IMAGE_LOADER_MANIFEST, image_loader_extension_path);
+ return;
+ }
+#endif // NDEBUG
+ Add(IDR_IMAGE_LOADER_MANIFEST,
+ base::FilePath(FILE_PATH_LITERAL("image_loader")));
+#endif // defined(IMAGE_LOADER_EXTENSION)
+}
+
#if defined(OS_CHROMEOS)
void ComponentLoader::AddGaiaAuthExtension() {
const CommandLine* command_line = CommandLine::ForCurrentProcess();
@@ -390,6 +406,7 @@ void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages(
}
AddFileManagerExtension();
+ AddImageLoaderExtension();
#if defined(ENABLE_SETTINGS_APP)
Add(IDR_SETTINGS_APP_MANIFEST,
diff --git a/chrome/browser/extensions/component_loader.h b/chrome/browser/extensions/component_loader.h
index 8a02e31..58b998f 100644
--- a/chrome/browser/extensions/component_loader.h
+++ b/chrome/browser/extensions/component_loader.h
@@ -117,6 +117,7 @@ class ComponentLoader {
void AddDefaultComponentExtensionsWithBackgroundPages(
bool skip_session_components);
void AddFileManagerExtension();
+ void AddImageLoaderExtension();
#if defined(OS_CHROMEOS)
void AddGaiaAuthExtension();
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index 0b9e0b0..1e74a7d 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -104,6 +104,10 @@
<include name="IDR_FILE_MANAGER_IMG_GALLERY_2X_CURSOR_UPDOWN" file="file_manager/images/gallery/2x/cursor_updown.png" type="BINDATA" />
</if>
+ <if expr="pp_ifdef('image_loader_extension')">
+ <include name="IDR_IMAGE_LOADER_MAIN_JS" file="image_loader/image_loader.js" type="BINDATA" />
+ <include name="IDR_IMAGE_LOADER_CLIENT_JS" file="image_loader/client.js" type="BINDATA" />
+ </if>
<if expr="pp_ifdef('enable_google_now')">
<include name="IDR_GOOGLE_NOW_BACKGROUND_JS" file="google_now/background.js" type="BINDATA" />
</if>
diff --git a/chrome/browser/resources/file_manager/css/photo_import.css b/chrome/browser/resources/file_manager/css/photo_import.css
index 565419f..41a1254 100644
--- a/chrome/browser/resources/file_manager/css/photo_import.css
+++ b/chrome/browser/resources/file_manager/css/photo_import.css
@@ -151,6 +151,21 @@ button.import {
position: absolute;
}
+.img-container > img:not(.cached) {
+ -webkit-animation: fadeIn ease-in 1;
+ -webkit-animation-duration: 100ms;
+ -webkit-animation-fill-mode: forwards;
+}
+
+@-webkit-keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity :1;
+ }
+}
+
.grid-item[lead] {
border: 2px solid transparent !important;
}
diff --git a/chrome/browser/resources/file_manager/gallery.html b/chrome/browser/resources/file_manager/gallery.html
index 40f08ec..ae3542f 100644
--- a/chrome/browser/resources/file_manager/gallery.html
+++ b/chrome/browser/resources/file_manager/gallery.html
@@ -19,6 +19,8 @@
Keep the list in sync with gallery_scripts.js. -->
<script src="js/metrics.js"></script>
+ <script src="../image_loader/client.js"></script>
+
<script src="../../../../ui/webui/resources/js/cr.js"></script>
<script src="../../../../ui/webui/resources/js/event_tracker.js"></script>
<script src="../../../../ui/webui/resources/js/load_time_data.js"></script>
diff --git a/chrome/browser/resources/file_manager/js/action_choice.js b/chrome/browser/resources/file_manager/js/action_choice.js
index d14d52b..39ce80d 100644
--- a/chrome/browser/resources/file_manager/js/action_choice.js
+++ b/chrome/browser/resources/file_manager/js/action_choice.js
@@ -220,7 +220,12 @@ ActionChoice.prototype.renderPreview_ = function(entries, count) {
new ThumbnailLoader(entry.toURL(),
ThumbnailLoader.LoaderType.IMAGE,
metadata).load(
- box, ThumbnailLoader.FillMode.FILL, onSuccess, onError, onError);
+ box,
+ ThumbnailLoader.OptimizationMode.DISCARD_DETACHED,
+ ThumbnailLoader.FillMode.FILL,
+ onSuccess,
+ onError,
+ onError);
});
};
diff --git a/chrome/browser/resources/file_manager/js/file_selection.js b/chrome/browser/resources/file_manager/js/file_selection.js
index deffef8..e030cb6 100644
--- a/chrome/browser/resources/file_manager/js/file_selection.js
+++ b/chrome/browser/resources/file_manager/js/file_selection.js
@@ -597,6 +597,7 @@ FileSelectionHandler.prototype.renderThumbnail_ = function(entry, callback) {
entry,
this.fileManager_.metadataCache_,
ThumbnailLoader.FillMode.FILL,
+ ThumbnailLoader.OptimizationMode.NEVER_DISCARD,
callback);
return thumbnail;
};
diff --git a/chrome/browser/resources/file_manager/js/image_editor/image_util.js b/chrome/browser/resources/file_manager/js/image_editor/image_util.js
index 39e375b..acdbb81 100644
--- a/chrome/browser/resources/file_manager/js/image_editor/image_util.js
+++ b/chrome/browser/resources/file_manager/js/image_editor/image_util.js
@@ -487,7 +487,7 @@ ImageUtil.ImageLoader.prototype.load = function(
// errorCallback has an optional error argument, which in case of general
// error should not be specified
this.image_.onerror = errorCallback.bind(this, 'IMAGE_ERROR');
- this.remoteLoader_ = util.loadImage(this.image_, url);
+ this.taskId_ = util.loadImage(this.image_, url);
}.bind(this);
if (opt_delay) {
this.timeout_ = setTimeout(startLoad, opt_delay);
@@ -533,10 +533,8 @@ ImageUtil.ImageLoader.prototype.cancel = function() {
this.image_.onerror = function() {};
this.image_.src = '';
}
- if (this.remoteLoader_) {
- this.remoteLoader_.cancel();
- this.remoteLoader_ = null;
- }
+ if (this.taskId_)
+ util.cancelLoadImage(this.taskId_);
this.generation_++; // Silence the transform fetcher if it is in progress.
};
diff --git a/chrome/browser/resources/file_manager/js/main_scripts.js b/chrome/browser/resources/file_manager/js/main_scripts.js
index cdbfe70..2570550 100644
--- a/chrome/browser/resources/file_manager/js/main_scripts.js
+++ b/chrome/browser/resources/file_manager/js/main_scripts.js
@@ -12,6 +12,8 @@
// //so we want to parse it as early as possible.
//<include src="metrics.js"/>
//
+//<include src="../../image_loader/client.js"/>
+//
//<include src="../../../../../ui/webui/resources/js/load_time_data.js"/>
//<include src="../../../../../ui/webui/resources/js/cr.js"/>
//<include src="../../../../../ui/webui/resources/js/util.js"/>
diff --git a/chrome/browser/resources/file_manager/js/media/media_util.js b/chrome/browser/resources/file_manager/js/media/media_util.js
index b9934bc..126b6b2 100644
--- a/chrome/browser/resources/file_manager/js/media/media_util.js
+++ b/chrome/browser/resources/file_manager/js/media/media_util.js
@@ -38,8 +38,7 @@ function ThumbnailLoader(url, opt_loaderType, opt_metadata, opt_mediaType) {
if (opt_metadata.thumbnail && opt_metadata.thumbnail.url) {
this.thumbnailUrl_ = opt_metadata.thumbnail.url;
this.transform_ = opt_metadata.thumbnail.transform;
- } else if (FileType.isImage(url) &&
- ThumbnailLoader.canUseImageUrl_(opt_metadata)) {
+ } else if (FileType.isImage(url)) {
this.thumbnailUrl_ = url;
this.transform_ = opt_metadata.media && opt_metadata.media.imageTransform;
} else if (this.fallbackUrl_) {
@@ -50,16 +49,6 @@ function ThumbnailLoader(url, opt_loaderType, opt_metadata, opt_mediaType) {
}
/**
- * Files with more pixels won't have thumbnails.
- */
-ThumbnailLoader.MAX_PIXEL_COUNT = 1 << 21; // 2 MPix
-
-/**
- * Files of bigger size won't have thumbnails.
- */
-ThumbnailLoader.MAX_FILE_SIZE = 1 << 20; // 1 Mb
-
-/**
* In percents (0.0 - 1.0), how much area can be cropped to fill an image
* in a container, when loading a thumbnail in FillMode.AUTO mode.
* The specified 30% value allows to fill 16:9, 3:2 pictures in 4:3 element.
@@ -78,6 +67,15 @@ ThumbnailLoader.FillMode = {
};
/**
+ * Optimization mode for downloading thumbnails.
+ * @enum
+ */
+ThumbnailLoader.OptimizationMode = {
+ NEVER_DISCARD: 0, // Never discards downloading. No optimization.
+ DISCARD_DETACHED: 1 // Canceled if the container is not attached anymore.
+};
+
+/**
* Type of element to store the image.
* @enum
*/
@@ -87,33 +85,36 @@ ThumbnailLoader.LoaderType = {
};
/**
- * If an image file does not have an embedded thumbnail we might want to use
- * the image itself as a thumbnail. If the image is too large it hurts
- * the performance a lot so we allow it only for moderately sized files.
- *
- * @param {Object} metadata Metadata object.
- * @return {boolean} Whether it is OK to use the image url for a preview.
- * @private
+ * Maximum thumbnail's width when generating from the full resolution image.
+ * @const
+ * @type {number}
*/
-ThumbnailLoader.canUseImageUrl_ = function(metadata) {
- return (metadata.filesystem && metadata.filesystem.size &&
- metadata.filesystem.size <= ThumbnailLoader.MAX_FILE_SIZE) ||
- (metadata.media && metadata.media.width && metadata.media.height &&
- metadata.media.width * metadata.media.height <=
- ThumbnailLoader.MAX_PIXEL_COUNT);
-};
+ThumbnailLoader.THUMBNAIL_MAX_WIDTH = 500;
/**
+ * Maximum thumbnail's height when generating from the full resolution image.
+ * @const
+ * @type {number}
+ */
+ThumbnailLoader.THUMBNAIL_MAX_HEIGHT = 500;
+
+/**
+ * Loads and attaches an image.
*
* @param {HTMLElement} box Container element.
* @param {ThumbnailLoader.FillMode} fillMode Fill mode.
+ * @param {ThumbnailLoader.OptimizationMode=} opt_optimizationMode Optimization
+ * for downloading thumbnails. By default optimizations are disabled.
* @param {function(Image, object} opt_onSuccess Success callback,
- * accepts the image and the transform.
+ * accepts the image and the transform.
* @param {function} opt_onError Error callback.
* @param {function} opt_onGeneric Callback for generic image used.
*/
-ThumbnailLoader.prototype.load = function(
- box, fillMode, opt_onSuccess, opt_onError, opt_onGeneric) {
+ThumbnailLoader.prototype.load = function(box, fillMode, opt_optimizationMode,
+ opt_onSuccess, opt_onError, opt_onGeneric) {
+ opt_optimizationMode = opt_optimizationMode ||
+ ThumbnailLoader.OptimizationMode.NEVER_DISCARD;
+
if (!this.thumbnailUrl_) {
// Relevant CSS rules are in file_types.css.
box.setAttribute('generic-thumbnail', this.mediaType_);
@@ -142,12 +143,31 @@ ThumbnailLoader.prototype.load = function(
}
}.bind(this);
- if (this.image_.src == this.thumbnailUrl_) {
- console.warn('Thumnbnail already loaded: ' + this.thumbnailUrl_);
+ if (this.image_.src) {
+ console.warn('Thumbnail already loaded: ' + this.thumbnailUrl_);
return;
}
- util.loadImage(this.image_, this.thumbnailUrl_);
+ // TODO(mtomasz): Smarter calculation of the requested size.
+ var wasAttached = box.ownerDocument.contains(box);
+ var taskId = util.loadImage(
+ this.image_,
+ this.thumbnailUrl_,
+ { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH,
+ maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT,
+ cache: true },
+ function() {
+ if (opt_optimizationMode ==
+ ThumbnailLoader.OptimizationMode.DISCARD_DETACHED &&
+ !box.ownerDocument.contains(box)) {
+ // If the container is not attached, then invalidate the download.
+ return false;
+ }
+ return true;
+ });
+
+ if (!taskId)
+ this.image_.classList.add('cached');
};
/**
@@ -195,7 +215,17 @@ ThumbnailLoader.prototype.loadDetachedImage = function(callback) {
this.image_ = new Image();
this.image_.onload = callback.bind(null, true);
this.image_.onerror = callback.bind(null, false);
- util.loadImage(this.image_, this.thumbnailUrl_);
+
+ // TODO(mtomasz): Smarter calculation of the requested size.
+ var taskId = util.loadImage(
+ this.image_,
+ this.thumbnailUrl_,
+ { maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH,
+ maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT,
+ cache: true });
+
+ if (!taskId)
+ this.image_.classList.add('cached');
};
/**
diff --git a/chrome/browser/resources/file_manager/js/photo/gallery_scripts.js b/chrome/browser/resources/file_manager/js/photo/gallery_scripts.js
index 8dc7f0c..e369001 100644
--- a/chrome/browser/resources/file_manager/js/photo/gallery_scripts.js
+++ b/chrome/browser/resources/file_manager/js/photo/gallery_scripts.js
@@ -10,6 +10,8 @@
//<include src="../metrics.js">
+//<include src="../../../image_loader/client.js"/>
+
//<include src="../../../../../../ui/webui/resources/js/cr.js">
//<include src="../../../../../../ui/webui/resources/js/event_tracker.js">
//<include src="../../../../../../ui/webui/resources/js/load_time_data.js">
diff --git a/chrome/browser/resources/file_manager/js/photo/photo_import.js b/chrome/browser/resources/file_manager/js/photo/photo_import.js
index c0c795e..54f0667 100644
--- a/chrome/browser/resources/file_manager/js/photo/photo_import.js
+++ b/chrome/browser/resources/file_manager/js/photo/photo_import.js
@@ -336,7 +336,8 @@ PhotoImport.prototype.decorateGridItem_ = function(li, entry) {
new ThumbnailLoader(entry.toURL(),
ThumbnailLoader.LoaderType.IMAGE,
metadata).
- load(box, ThumbnailLoader.FillMode.FIT);
+ load(box, ThumbnailLoader.FillMode.FIT,
+ ThumbnailLoader.OptimizationMode.DISCARD_DETACHED);
});
frame.appendChild(box);
diff --git a/chrome/browser/resources/file_manager/js/photo/photo_import_scripts.js b/chrome/browser/resources/file_manager/js/photo/photo_import_scripts.js
index 00db271..cd7d591 100644
--- a/chrome/browser/resources/file_manager/js/photo/photo_import_scripts.js
+++ b/chrome/browser/resources/file_manager/js/photo/photo_import_scripts.js
@@ -8,6 +8,8 @@
// included file but that's all right since any javascript file should start
// with a copyright comment anyway.
+//<include src="../../../image_loader/client.js"/>
+
//<include src="../../../../../../ui/webui/resources/js/load_time_data.js"/>
//<include src="../../../../../../ui/webui/resources/js/util.js"/>
//<include src="../../../../../../ui/webui/resources/js/i18n_template_no_process.js"/>
diff --git a/chrome/browser/resources/file_manager/js/photo/ribbon.js b/chrome/browser/resources/file_manager/js/photo/ribbon.js
index 66c3689..9f595c0 100644
--- a/chrome/browser/resources/file_manager/js/photo/ribbon.js
+++ b/chrome/browser/resources/file_manager/js/photo/ribbon.js
@@ -333,6 +333,7 @@ Ribbon.prototype.setThumbnailImage_ = function(thumbnail, url, metadata) {
new ThumbnailLoader(url, ThumbnailLoader.LoaderType.IMAGE, metadata).load(
thumbnail.querySelector('.image-wrapper'),
ThumbnailLoader.FillMode.FILL /* fill */,
+ ThumbnailLoader.OptimizationMode.NEVER_DISCARD,
null /* success callback */,
this.onThumbnailError_.bind(null, url));
};
diff --git a/chrome/browser/resources/file_manager/js/util.js b/chrome/browser/resources/file_manager/js/util.js
index 0d600db..ecda8931 100644
--- a/chrome/browser/resources/file_manager/js/util.js
+++ b/chrome/browser/resources/file_manager/js/util.js
@@ -1173,76 +1173,32 @@ util.AppCache.cleanup_ = function(map) {
};
/**
- * RemoteImageLoader loads an image from a remote url.
+ * Load an image.
*
- * Fetches a blob via XHR, converts it to a data: url and assigns to img.src.
- * @constructor
- */
-util.RemoteImageLoader = function() {};
-
-/**
* @param {Image} image Image element.
- * @param {string} url Remote url to load into the image.
- */
-util.RemoteImageLoader.prototype.load = function(image, url) {
- this.onSuccess_ = function(dataURL) { image.src = dataURL };
- this.onError_ = function() { image.onerror() };
-
- var xhr = new XMLHttpRequest();
- xhr.responseType = 'blob';
- xhr.onload = function() {
- if (xhr.status == 200) {
- var reader = new FileReader;
- reader.onload = function(e) {
- this.onSuccess_(e.target.result);
- }.bind(this);
- reader.onerror = this.onError_;
- reader.readAsDataURL(xhr.response);
- } else {
- this.onError_();
- }
- }.bind(this);
- xhr.onerror = this.onError_;
-
- try {
- xhr.open('GET', url, true);
- xhr.send();
- } catch (e) {
- console.log(e);
- this.onError_();
- }
-};
-
-/**
- * Cancels the loading.
+ * @param {string} url Source url.
+ * @param {Object=} opt_options Hash array of options, eg. width, height,
+ * maxWidth, maxHeight, scale, cache.
+ * @param {function()=} opt_isValid Function returning false iff the task
+ * is not valid and should be aborted.
+ * @return {?number} Task identifier or null if fetched immediately from
+ * cache.
*/
-util.RemoteImageLoader.prototype.cancel = function() {
- // We cannot really cancel the XHR.send and FileReader.readAsDataURL,
- // silencing the callbacks instead.
- this.onSuccess_ = this.onError_ = function() {};
+util.loadImage = function(image, url, opt_options, opt_isValid) {
+ return ImageLoader.Client.loadToImage(url,
+ image,
+ opt_options || {},
+ function() { },
+ function() { image.onerror(); },
+ opt_isValid);
};
/**
- * Load an image.
- *
- * In packaged apps img.src is not allowed to point to http(s)://.
- * For such urls util.RemoteImageLoader is used.
- *
- * @param {Image} image Image element.
- * @param {string} url Source url.
- * @return {util.RemoteImageLoader?} RemoteImageLoader object reference, use it
- * to cancel the loading.
+ * Cancels loading an image.
+ * @param {number} taskId Task identifier returned by util.loadImage().
*/
-util.loadImage = function(image, url) {
- if (util.platform.v2() && url.match(/^http(s):/)) {
- var imageLoader = new util.RemoteImageLoader();
- imageLoader.load(image, url);
- return imageLoader;
- }
-
- // OK to load directly.
- image.src = url;
- return null;
+util.cancelLoadImage = function(taskId) {
+ ImageLoader.Client.getInstance().cancel(taskId);
};
/**
diff --git a/chrome/browser/resources/file_manager/main.html b/chrome/browser/resources/file_manager/main.html
index 82f5050..d2f455d 100644
--- a/chrome/browser/resources/file_manager/main.html
+++ b/chrome/browser/resources/file_manager/main.html
@@ -34,6 +34,8 @@
so we want to parse it as early as possible -->
<script src="js/metrics.js"></script>
+ <script src="../image_loader/client.js"></script>
+
<script src="../../../../ui/webui/resources/js/load_time_data.js"></script>
<script src="../../../../ui/webui/resources/js/cr.js"></script>
<script src="../../../../ui/webui/resources/js/util.js"></script>
diff --git a/chrome/browser/resources/file_manager/photo_import.html b/chrome/browser/resources/file_manager/photo_import.html
index ba5a146..2d500b6 100644
--- a/chrome/browser/resources/file_manager/photo_import.html
+++ b/chrome/browser/resources/file_manager/photo_import.html
@@ -21,6 +21,8 @@
<if expr="0">
<!-- This file has not been flattened, load individual scripts.
Keep the list in sync with photo_import_scripts.js. -->
+ <script src="../image_loader/client.js"></script>
+
<script src="../../../../ui/webui/resources/js/load_time_data.js"></script>
<script src="../../../../ui/webui/resources/js/util.js"></script>
<script src="../../../../ui/webui/resources/js/i18n_template_no_process.js"></script>
diff --git a/chrome/browser/resources/image_loader/client.js b/chrome/browser/resources/image_loader/client.js
new file mode 100644
index 0000000..c2c7149
--- /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);
+};
diff --git a/chrome/browser/resources/image_loader/image_loader.js b/chrome/browser/resources/image_loader/image_loader.js
new file mode 100644
index 0000000..f3a5e97
--- /dev/null
+++ b/chrome/browser/resources/image_loader/image_loader.js
@@ -0,0 +1,305 @@
+// 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.
+
+/**
+ * Loads and resizes an image.
+ * @constructor
+ */
+var ImageLoader = function() {
+ /**
+ * Hash array of active requests.
+ * @type {Object}
+ * @private
+ */
+ this.requests_ = {};
+
+ chrome.fileBrowserPrivate.requestLocalFileSystem(function(filesystem) {
+ // TODO(mtomasz): Handle.
+ });
+
+ chrome.extension.onConnectExternal.addListener(function(port) {
+ if (ImageLoader.ALLOWED_CLIENTS.indexOf(port.sender.id) !== -1)
+ port.onMessage.addListener(function(request) {
+ this.onMessage_(port, request, port.postMessage.bind(port));
+ }.bind(this));
+ }.bind(this));
+};
+
+/**
+ * List of extensions allowed to perform image requests.
+ *
+ * @const
+ * @type {Array.<string>}
+ */
+ImageLoader.ALLOWED_CLIENTS =
+ ['hhaomjibdihmijegdhdafkllkbggdgoj']; // File Manager's extension id.
+
+/**
+ * Handles a request. Depending on type of the request, starts or stops
+ * an image task.
+ *
+ * @param {Port} port Connection port.
+ * @param {Object} request Request message as a hash array.
+ * @param {function} callback Callback to be called to return response.
+ * @private
+ */
+ImageLoader.prototype.onMessage_ = function(port, request, callback) {
+ var requestId = port.sender.id + ':' + request.taskId;
+ if (request.cancel) {
+ // Cancel a task.
+ if (requestId in this.requests_) {
+ this.requests_[requestId].cancel();
+ delete this.requests_[requestId];
+ }
+ } else {
+ // Start a task.
+ this.requests_[requestId] =
+ new ImageLoader.Request(request, callback);
+ }
+};
+
+/**
+ * Returns the singleton instance.
+ * @return {ImageLoader} ImageLoader object.
+ */
+ImageLoader.getInstance = function() {
+ if (!ImageLoader.instance_)
+ ImageLoader.instance_ = new ImageLoader();
+ return ImageLoader.instance_;
+};
+
+/**
+ * Calculates dimensions taking into account resize options, such as:
+ * - scale: for scaling,
+ * - maxWidth, maxHeight: for maximum dimensions,
+ * - width, height: for exact requested size.
+ * Returns the target size as hash array with width, height properties.
+ *
+ * @param {number} width Source width.
+ * @param {number} height Source height.
+ * @param {Object} options Resizing options as a hash array.
+ * @return {Object} Dimensions, eg. { width: 100, height: 50 }.
+ */
+ImageLoader.resizeDimensions = function(width, height, options) {
+ var sourceWidth = width;
+ var sourceHeight = height;
+
+ var targetWidth = sourceWidth;
+ var targetHeight = sourceHeight;
+
+ if ('scale' in options) {
+ targetWidth = sourceWidth * options.scale;
+ targetHeight = sourceHeight * options.scale;
+ }
+
+ if (options.maxWidth &&
+ targetWidth > options.maxWidth) {
+ var scale = options.maxWidth / targetWidth;
+ targetWidth *= scale;
+ targetHeight *= scale;
+ }
+
+ if (options.maxHeight &&
+ targetHeight > options.maxHeight) {
+ var scale = options.maxHeight / targetHeight;
+ targetWidth *= scale;
+ targetHeight *= scale;
+ }
+
+ if (options.width)
+ targetWidth = options.width;
+
+ if (options.height)
+ targetHeight = options.height;
+
+ targetWidth = Math.round(targetWidth);
+ targetHeight = Math.round(targetHeight);
+
+ return { width: targetWidth, height: targetHeight };
+};
+
+/**
+ * Performs resizing of the source image into the target canvas.
+ *
+ * @param {HTMLCanvasElement|Image} source Source image or canvas.
+ * @param {HTMLCanvasElement} target Target canvas.
+ * @param {Object} options Resizing options as a hash array.
+ */
+ImageLoader.resize = function(source, target, options) {
+ var targetDimensions = ImageLoader.resizeDimensions(
+ source.width, source.height, options);
+
+ target.width = targetDimensions.width;
+ target.height = targetDimensions.height;
+
+ var targetContext = target.getContext('2d');
+ targetContext.drawImage(source,
+ 0, 0, source.width, source.height,
+ 0, 0, target.width, target.height);
+};
+
+/**
+ * Creates and starts downloading and then resizing of the image. Finally,
+ * returns the image using the callback.
+ *
+ * @param {Object} request Request message as a hash array.
+ * @param {function} callback Callback used to send the response.
+ * @constructor
+ */
+ImageLoader.Request = function(request, callback) {
+ /**
+ * @type {Object}
+ * @private
+ */
+ this.request_ = request;
+
+ /**
+ * @type {function}
+ * @private
+ */
+ this.sendResponse_ = callback;
+
+ /**
+ * Temporary image used to download images.
+ * @type {Image}
+ * @private
+ */
+ this.image_ = new Image();
+
+ /**
+ * Used to download remote images using http:// or https:// protocols.
+ * @type {XMLHttpRequest}
+ * @private
+ */
+ this.xhr_ = new XMLHttpRequest();
+
+ /**
+ * Temporary canvas used to resize and compress the image.
+ * @type {HTMLCanvasElement}
+ * @private
+ */
+ this.canvas_ = document.createElement('canvas');
+
+ /**
+ * @type {CanvasRenderingContext2D}
+ * @private
+ */
+ this.context_ = this.canvas_.getContext('2d');
+
+ this.downloadOriginal_();
+};
+
+/**
+ * Downloads an image directly or for remote resources using the XmlHttpRequest.
+ * @private
+ */
+ImageLoader.Request.prototype.downloadOriginal_ = function() {
+ this.image_.onload = this.onImageLoad_.bind(this);
+ this.image_.onerror = this.onImageError_.bind(this);
+
+ if (window.harness || !this.request_.url.match(/^https?:/)) {
+ // Download directly.
+ this.image_.src = this.request_.url;
+ return;
+ }
+
+ // Download using an xhr request.
+ this.xhr_.responseType = 'blob';
+
+ this.xhr_.onerror = this.image_.onerror;
+ this.xhr_.onload = function() {
+ if (this.xhr_.status != 200) {
+ this.image_.onerror();
+ return;
+ }
+
+ // Process returnes data.
+ var reader = new FileReader();
+ reader.onerror = this.image_.onerror;
+ reader.onload = function(e) {
+ this.image_.src = e.target.result;
+ }.bind(this);
+
+ // Load the data to the image as a data url.
+ reader.readAsDataURL(this.xhr_.response);
+ }.bind(this);
+
+ // Perform a xhr request.
+ try {
+ this.xhr_.open('GET', this.request_.url, true);
+ this.xhr_.send();
+ } catch (e) {
+ this.image_.onerror();
+ }
+};
+
+/**
+ * Sends the resized image via the callback.
+ * @private
+ */
+ImageLoader.Request.prototype.sendImage_ = function() {
+ // TODO(mtomasz): Keep format. Never compress using jpeg codec for lossless
+ // images such as png, gif.
+ var pngData = this.canvas_.toDataURL('image/png');
+ var jpegData = this.canvas_.toDataURL('image/jpeg', 0.9);
+ var imageData = pngData.length < jpegData.length * 2 ? pngData : jpegData;
+ this.sendResponse_({ status: 'success',
+ data: imageData,
+ taskId: this.request_.taskId });
+};
+
+/**
+ * Handler, when contents are loaded into the image element. Performs resizing
+ * and finalizes the request process.
+ *
+ * @private
+ */
+ImageLoader.Request.prototype.onImageLoad_ = function() {
+ ImageLoader.resize(this.image_, this.canvas_, this.request_);
+ this.sendImage_();
+ this.cleanup_();
+};
+
+/**
+ * Handler, when loading of the image fails. Sends a failure response and
+ * finalizes the request process.
+ *
+ * @private
+ */
+ImageLoader.Request.prototype.onImageError_ = function() {
+ this.sendResponse_({ status: 'error',
+ taskId: this.request_.taskId });
+ this.cleanup_();
+};
+
+/**
+ * Cancels the request.
+ */
+ImageLoader.Request.prototype.cancel = function() {
+ this.cleanup_();
+};
+
+/**
+ * Cleans up memory used by this request.
+ * @private
+ */
+ImageLoader.Request.prototype.cleanup_ = function() {
+ this.image_.onerror = function() {};
+ this.image_.onload = function() {};
+
+ // Transparent 1x1 pixel gif, to force garbage collecting.
+ this.image_.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAA' +
+ 'ABAAEAAAICTAEAOw==';
+
+ this.xhr_.onerror = function() {};
+ this.xhr_.onload = function() {};
+ this.xhr_.abort();
+
+ // Dispose memory allocated by Canvas.
+ this.canvas_.width = 0;
+ this.canvas_.height = 0;
+};
+
+// Load the extension.
+ImageLoader.getInstance();
diff --git a/chrome/browser/resources/image_loader/manifest.json b/chrome/browser/resources/image_loader/manifest.json
new file mode 100644
index 0000000..f95e63c
--- /dev/null
+++ b/chrome/browser/resources/image_loader/manifest.json
@@ -0,0 +1,19 @@
+{
+ // chrome-extension://pmfjbimdmchhbnneeidfognadeopoehp
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDowC9B4+K6zbl4PnALNyOUgra/MPdD8gZ39Fk/IAJWt03qrN7vz1gd/mmrBg0EEIsyLRmUmfyVEfvcIUOZxFqn4A9D2aaRSvNHy9qkasZMBDEql8Nt2iNZm/kGS7sizidDV6Bc/vyLNiH1gKOXBQ42JIxKjgtrmnhGV2giw2vJGwIDAQAB",
+ "name": "Image loader",
+ "version": "0.1",
+ "description": "Image loader",
+ "incognito" : "split",
+ "manifest_version": 2,
+ "permissions": [
+ "fileBrowserHandler",
+ "fileBrowserPrivate",
+ "https://*.googleusercontent.com",
+ "https://drive.google.com"
+ ],
+ "content_security_policy": "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-src 'self' about:; img-src 'self' chrome://resources chrome://theme data: https://docs.google.com https://*.googleusercontent.com chrome://extension-icon; media-src 'self' https://*.googleusercontent.com; connect-src https://drive.google.com https://*.googleusercontent.com",
+ "background": {
+ "scripts": ["image_loader.js" ]
+ }
+}
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 06f8901..5250503 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -1650,6 +1650,9 @@ const char kWaitForMutex[] = "wait-for-mutex";
// Enables overriding the path of file manager extension.
const char kFileManagerExtensionPath[] = "filemgr-ext-path";
+// Enables overriding the path of image loader extension.
+const char kImageLoaderExtensionPath[] = "image-loader-ext-path";
+
// Dumps dependency information about our profile services into a dot file in
// the profile directory.
const char kDumpProfileDependencyGraph[] = "dump-profile-graph";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 4e4daef..ad00c29 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -468,6 +468,7 @@ extern const char kWaitForMutex[];
#ifndef NDEBUG
extern const char kFileManagerExtensionPath[];
+extern const char kImageLoaderExtensionPath[];
extern const char kDumpProfileDependencyGraph[];
#endif