Replace image_messages.h with Mojo service

Implement ImageDownloader service and register it into RenderFrame
ServiceRegistry. This Mojo service will do the job did by
ImageLoadingHelper before.

Re-use mojom files:
third_party/mojo_services/src/geometry/public/interfaces/
geometry.mojom
skia/public/interfaces/bitmap.mojom

TBR=rockot@chromium.org,amistry@google.com,darin@chromium.org,nasko@chromium.org
BUG=

Review URL: https://codereview.chromium.org/1085783002

Cr-Commit-Position: refs/heads/master@{#337370}
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 474f562..9211dc1 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -116,6 +116,9 @@
       "//device/bluetooth",
       "//gin",
       "//mojo/application/public/interfaces",
+      "//mojo/common:url_type_converters",
+      "//mojo/converters/geometry",
+      "//skia/public",
       "//storage/browser",
       "//storage/common",
       "//third_party/WebKit/public:image_resources",
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index e24f00c8..45b08917 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -1873,6 +1873,9 @@
 #endif
 
   service_registry_.reset();
+
+  // Disconnect with ImageDownloader Mojo service in RenderFrame.
+  mojo_image_downloader_.reset();
 }
 
 bool RenderFrameHostImpl::IsFocused() {
@@ -1885,6 +1888,15 @@
           frame_tree_->GetFocusedFrame()->IsDescendantOf(frame_tree_node()));
 }
 
+const image_downloader::ImageDownloaderPtr&
+RenderFrameHostImpl::GetMojoImageDownloader() {
+  if (!mojo_image_downloader_.get()) {
+    GetServiceRegistry()->ConnectToRemoteService(
+        mojo::GetProxy(&mojo_image_downloader_));
+  }
+  return mojo_image_downloader_;
+}
+
 void RenderFrameHostImpl::UpdateCrossProcessIframeAccessibility(
     const std::map<int32, int>& node_to_frame_routing_id_map) {
   for (const auto& iter : node_to_frame_routing_id_map) {
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index d34b4ef3..72e39da 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -20,6 +20,7 @@
 #include "content/common/content_export.h"
 #include "content/common/frame_message_enums.h"
 #include "content/common/frame_replication_state.h"
+#include "content/common/image_downloader/image_downloader.mojom.h"
 #include "content/common/mojo/service_registry_impl.h"
 #include "content/common/navigation_params.h"
 #include "content/public/browser/render_frame_host.h"
@@ -445,6 +446,9 @@
   // addition, its associated RenderWidgetHost has to be focused.
   bool IsFocused();
 
+  // Returns the Mojo ImageDownloader service pointer.
+  const image_downloader::ImageDownloaderPtr& GetMojoImageDownloader();
+
  protected:
   friend class RenderFrameHostFactory;
 
@@ -735,6 +739,9 @@
   // The frame's Mojo Shell service.
   scoped_ptr<FrameMojoShell> frame_mojo_shell_;
 
+  // Holder of Mojo connection with ImageDownloader service in RenderFrame.
+  image_downloader::ImageDownloaderPtr mojo_image_downloader_;
+
   // NOTE: This must be the last member.
   base::WeakPtrFactory<RenderFrameHostImpl> weak_ptr_factory_;
 
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 55d2e571..3df8edb 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -62,7 +62,6 @@
 #include "content/common/browser_plugin/browser_plugin_constants.h"
 #include "content/common/browser_plugin/browser_plugin_messages.h"
 #include "content/common/frame_messages.h"
-#include "content/common/image_messages.h"
 #include "content/common/input_messages.h"
 #include "content/common/ssl_status_serialization.h"
 #include "content/common/view_messages.h"
@@ -97,11 +96,15 @@
 #include "content/public/common/url_constants.h"
 #include "content/public/common/url_utils.h"
 #include "content/public/common/web_preferences.h"
+#include "mojo/common/url_type_converters.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
 #include "net/base/net_util.h"
 #include "net/http/http_cache.h"
 #include "net/http/http_transaction_factory.h"
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
+#include "skia/public/type_converters.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/layout.h"
 #include "ui/gfx/display.h"
 #include "ui/gfx/screen.h"
@@ -135,19 +138,19 @@
 base::LazyInstance<std::vector<WebContentsImpl::CreatedCallback> >
 g_created_callbacks = LAZY_INSTANCE_INITIALIZER;
 
-static int StartDownload(RenderFrameHost* rfh,
-                         const GURL& url,
-                         bool is_favicon,
-                         uint32_t max_bitmap_size,
-                         bool bypass_cache) {
-  static int g_next_image_download_id = 0;
-  rfh->Send(new ImageMsg_DownloadImage(rfh->GetRoutingID(),
-                                       ++g_next_image_download_id,
-                                       url,
-                                       is_favicon,
-                                       max_bitmap_size,
-                                       bypass_cache));
-  return g_next_image_download_id;
+static void DidDownloadImage(const WebContents::ImageDownloadCallback& callback,
+                             int id,
+                             const GURL& image_url,
+                             image_downloader::DownloadResultPtr result) {
+  DCHECK(result);
+
+  const std::vector<SkBitmap> images =
+      result->images.To<std::vector<SkBitmap>>();
+  const std::vector<gfx::Size> original_image_sizes =
+      result->original_image_sizes.To<std::vector<gfx::Size>>();
+
+  callback.Run(id, result->http_status_code, image_url, images,
+               original_image_sizes);
 }
 
 void NotifyCacheOnIO(
@@ -611,7 +614,6 @@
                                 OnBrowserPluginMessage(render_frame_host,
                                                        message))
 #endif
-    IPC_MESSAGE_HANDLER(ImageHostMsg_DidDownloadImage, OnDidDownloadImage)
     IPC_MESSAGE_HANDLER(ViewHostMsg_UpdateFaviconURL, OnUpdateFaviconURL)
     IPC_MESSAGE_HANDLER(ViewHostMsg_ShowValidationMessage,
                         OnShowValidationMessage)
@@ -2593,15 +2595,27 @@
   color_chooser_info_.reset();
 }
 
-int WebContentsImpl::DownloadImage(const GURL& url,
-                                   bool is_favicon,
-                                   uint32_t max_bitmap_size,
-                                   bool bypass_cache,
-                                   const ImageDownloadCallback& callback) {
-  int id = StartDownload(GetMainFrame(), url, is_favicon, max_bitmap_size,
-                         bypass_cache);
-  image_download_map_[id] = callback;
-  return id;
+int WebContentsImpl::DownloadImage(
+    const GURL& url,
+    bool is_favicon,
+    uint32_t max_bitmap_size,
+    bool bypass_cache,
+    const WebContents::ImageDownloadCallback& callback) {
+  static int next_image_download_id = 0;
+  const image_downloader::ImageDownloaderPtr& mojo_image_downloader =
+      GetMainFrame()->GetMojoImageDownloader();
+  image_downloader::DownloadRequestPtr req =
+      image_downloader::DownloadRequest::New();
+
+  req->url = mojo::String::From(url);
+  req->is_favicon = is_favicon;
+  req->max_bitmap_size = max_bitmap_size;
+  req->bypass_cache = bypass_cache;
+
+  mojo_image_downloader->DownloadImage(
+      req.Pass(),
+      base::Bind(&DidDownloadImage, callback, ++next_image_download_id, url));
+  return next_image_download_id;
 }
 
 bool WebContentsImpl::IsSubframe() const {
@@ -3176,28 +3190,6 @@
 }
 #endif  // defined(ENABLE_PLUGINS)
 
-void WebContentsImpl::OnDidDownloadImage(
-    int id,
-    int http_status_code,
-    const GURL& image_url,
-    const std::vector<SkBitmap>& bitmaps,
-    const std::vector<gfx::Size>& original_bitmap_sizes) {
-  if (bitmaps.size() != original_bitmap_sizes.size())
-    return;
-
-  ImageDownloadMap::iterator iter = image_download_map_.find(id);
-  if (iter == image_download_map_.end()) {
-    // Currently WebContents notifies us of ANY downloads so that it is
-    // possible to get here.
-    return;
-  }
-  if (!iter->second.is_null()) {
-    iter->second.Run(
-        id, http_status_code, image_url, bitmaps, original_bitmap_sizes);
-  }
-  image_download_map_.erase(id);
-}
-
 void WebContentsImpl::OnUpdateFaviconURL(
     const std::vector<FaviconURL>& candidates) {
   // We get updated favicon URLs after the page stops loading. If a cross-site
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index b41a3507..8f3c90a7 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -856,11 +856,6 @@
   void OnBrowserPluginMessage(RenderFrameHost* render_frame_host,
                               const IPC::Message& message);
 #endif  // defined(ENABLE_PLUGINS)
-  void OnDidDownloadImage(int id,
-                          int http_status_code,
-                          const GURL& image_url,
-                          const std::vector<SkBitmap>& bitmaps,
-                          const std::vector<gfx::Size>& original_bitmap_sizes);
   void OnUpdateFaviconURL(const std::vector<FaviconURL>& candidates);
   void OnFirstVisuallyNonEmptyPaint();
   void OnMediaPlayingNotification(int64 player_cookie,
@@ -1242,10 +1237,6 @@
   // completed making layout changes to effect an exit from fullscreen mode.
   bool fullscreen_widget_had_focus_at_shutdown_;
 
-  // Maps the ids of pending image downloads to their callbacks
-  typedef std::map<int, ImageDownloadCallback> ImageDownloadMap;
-  ImageDownloadMap image_download_map_;
-
   // Whether this WebContents is responsible for displaying a subframe in a
   // different process from its parent page.
   bool is_subframe_;
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index 5a383d1..ca573cd 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -489,6 +489,7 @@
     "application_setup.mojom",
     "background_sync_service.mojom",
     "geolocation_service.mojom",
+    "image_downloader/image_downloader.mojom",
     "permission_service.mojom",
     "presentation/presentation_service.mojom",
     "process_control.mojom",
@@ -502,5 +503,7 @@
   deps = [
     "//content/public/common:mojo_bindings",
     "//mojo/application/public/interfaces",
+    "//skia/public/interfaces",
+    "//ui/mojo/geometry:interfaces",
   ]
 }
diff --git a/content/common/content_message_generator.h b/content/common/content_message_generator.h
index 803e5f6..eebe1d5 100644
--- a/content/common/content_message_generator.h
+++ b/content/common/content_message_generator.h
@@ -28,7 +28,6 @@
 #include "content/common/gamepad_messages.h"
 #include "content/common/geofencing_messages.h"
 #include "content/common/gpu/gpu_messages.h"
-#include "content/common/image_messages.h"
 #include "content/common/indexed_db/indexed_db_messages.h"
 #include "content/common/input_messages.h"
 #include "content/common/manifest_manager_messages.h"
diff --git a/content/common/image_downloader/image_downloader.mojom b/content/common/image_downloader/image_downloader.mojom
new file mode 100644
index 0000000..7f3d289
--- /dev/null
+++ b/content/common/image_downloader/image_downloader.mojom
@@ -0,0 +1,28 @@
+// Copyright 2015 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.
+
+module image_downloader;
+
+import "skia/public/interfaces/bitmap.mojom";
+import "ui/mojo/geometry/geometry.mojom";
+
+struct DownloadRequest {
+  string url;
+  bool is_favicon;
+  uint32 max_bitmap_size;
+  bool bypass_cache;
+};
+
+struct DownloadResult {
+  int32 http_status_code;
+  array<skia.Bitmap> images;
+  array<mojo.Size> original_image_sizes;
+};
+
+interface ImageDownloader {
+  // Fetch and decode an image from a given URL.
+  // Returns the decoded images, or http_status_code to indicate error.
+  // Each call is independent, overlapping calls are possible.
+  DownloadImage(DownloadRequest request) => (DownloadResult result);
+};
diff --git a/content/common/image_messages.h b/content/common/image_messages.h
deleted file mode 100644
index 8e8279f..0000000
--- a/content/common/image_messages.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2012 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.
-
-// Multiply-included message file, no traditional include guard.
-#include <vector>
-
-#include "ipc/ipc_message.h"
-#include "ipc/ipc_message_macros.h"
-#include "ipc/ipc_param_traits.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/gfx/geometry/size.h"
-
-#define IPC_MESSAGE_START ImageMsgStart
-
-// Messages sent from the browser to the renderer.
-
-// Requests the renderer to download the specified image, decode it,
-// and send the image data back via ImageHostMsg_DidDownloadImage.
-IPC_MESSAGE_ROUTED5(ImageMsg_DownloadImage,
-                    int /* Identifier for the request */,
-                    GURL /* URL of the image */,
-                    bool /* is favicon (turn off cookies) */,
-                    uint32_t /* Maximal bitmap size in pixel. The results are
-                                filtered according the max size. If there are no
-                                bitmaps at the passed in GURL <= max size, the
-                                smallest bitmap is resized to the max size and
-                                is the only result. A max size of zero means
-                                that the max size is unlimited. */,
-                    bool /* bypass cache */)
-
-// Messages sent from the renderer to the browser.
-
-IPC_MESSAGE_ROUTED5(ImageHostMsg_DidDownloadImage,
-                    int /* Identifier of the request */,
-                    int /* HTTP response status */,
-                    GURL /* URL of the image */,
-                    std::vector<SkBitmap> /* bitmap data */,
-                    /* The sizes in pixel of the bitmaps before they were
-                       resized due to the maximal bitmap size passed to
-                       ImageMsg_DownloadImage. Each entry in the bitmaps vector
-                       corresponds to an entry in the sizes vector. If a bitmap
-                       was resized, there should be a single returned bitmap. */
-                    std::vector<gfx::Size>)
diff --git a/content/content.gyp b/content/content.gyp
index 41beae7..85ed59f 100644
--- a/content/content.gyp
+++ b/content/content.gyp
@@ -434,12 +434,14 @@
             '../mojo/mojo_base.gyp:mojo_application_bindings',
             '../mojo/mojo_base.gyp:mojo_system_java',
             '../net/net.gyp:net',
+            '../skia/skia.gyp:skia_mojo',
             '../third_party/mojo/mojo_public.gyp:mojo_bindings_java',
             '../ui/android/ui_android.gyp:ui_java',
             '../ui/touch_selection/ui_touch_selection.gyp:selection_event_type_java',
             '../ui/touch_selection/ui_touch_selection.gyp:touch_handle_orientation_java',
             '../third_party/android_tools/android_tools.gyp:android_support_v13_javalib',
             '../third_party/WebKit/public/blink_headers.gyp:blink_headers_java',
+            '../ui/mojo/geometry/mojo_bindings.gyp:mojo_geometry_bindings',
             'common_aidl',
             'console_message_level_java',
             'content_common',
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index d2888f8..db8c43d 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -12,6 +12,7 @@
     '../device/vibration/vibration.gyp:device_vibration_mojo_bindings',
     '../google_apis/google_apis.gyp:google_apis',
     '../mojo/mojo_base.gyp:mojo_application_base',
+    '../mojo/mojo_base.gyp:mojo_geometry_lib',
     '../mojo/mojo_base.gyp:mojo_url_type_converters',
     '../mojo/mojo_services.gyp:network_service_bindings_lib',
     '../mojo/mojo_services.gyp:updater_bindings_lib',
@@ -19,6 +20,7 @@
     '../net/net.gyp:net',
     '../net/net.gyp:net_extras',
     '../skia/skia.gyp:skia',
+    '../skia/skia.gyp:skia_mojo',
     '../sql/sql.gyp:sql',
     '../third_party/mojo/mojo_public.gyp:mojo_cpp_bindings',
     '../third_party/re2/re2.gyp:re2',
diff --git a/content/content_common.gypi b/content/content_common.gypi
index 57c6a5d..31072e5 100644
--- a/content/content_common.gypi
+++ b/content/content_common.gypi
@@ -359,7 +359,6 @@
       'common/host_discardable_shared_memory_manager.h',
       'common/host_shared_bitmap_manager.cc',
       'common/host_shared_bitmap_manager.h',
-      'common/image_messages.h',
       'common/indexed_db/indexed_db_constants.h',
       'common/indexed_db/indexed_db_key.cc',
       'common/indexed_db/indexed_db_key.h',
diff --git a/content/content_common_mojo_bindings.gyp b/content/content_common_mojo_bindings.gyp
index 2bd97b4..f4175a3 100644
--- a/content/content_common_mojo_bindings.gyp
+++ b/content/content_common_mojo_bindings.gyp
@@ -14,6 +14,7 @@
           'common/application_setup.mojom',
           'common/background_sync_service.mojom',
           'common/geolocation_service.mojom',
+          'common/image_downloader/image_downloader.mojom',
           'common/permission_service.mojom',
           'common/presentation/presentation_service.mojom',
           'common/process_control.mojom',
@@ -38,7 +39,9 @@
         'content_common_mojo_bindings_mojom',
         '../mojo/mojo_base.gyp:mojo_application_bindings',
         '../mojo/mojo_base.gyp:mojo_environment_chromium',
+        '../skia/skia.gyp:skia_mojo',
         '../third_party/mojo/mojo_public.gyp:mojo_cpp_bindings',
+        '../ui/mojo/geometry/mojo_bindings.gyp:mojo_geometry_bindings',
       ]
     },
   ]
diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi
index 4cfd238..675b6a6 100644
--- a/content/content_renderer.gypi
+++ b/content/content_renderer.gypi
@@ -19,8 +19,11 @@
     '../media/blink/media_blink.gyp:media_blink',
     '../media/media.gyp:media',
     '../mojo/mojo_base.gyp:mojo_environment_chromium',
+    '../mojo/mojo_base.gyp:mojo_geometry_lib',
+    '../mojo/mojo_base.gyp:mojo_url_type_converters',
     '../net/net.gyp:net',
     '../skia/skia.gyp:skia',
+    '../skia/skia.gyp:skia_mojo',
     '../storage/storage_common.gyp:storage_common',
     '../third_party/WebKit/public/blink.gyp:blink',
     '../third_party/icu/icu.gyp:icui18n',
@@ -198,8 +201,8 @@
       'renderer/history_serialization.h',
       'renderer/idle_user_detector.cc',
       'renderer/idle_user_detector.h',
-      'renderer/image_loading_helper.cc',
-      'renderer/image_loading_helper.h',
+      'renderer/image_downloader/image_downloader_impl.cc',
+      'renderer/image_downloader/image_downloader_impl.h',
       'renderer/ime_event_guard.cc',
       'renderer/ime_event_guard.h',
       'renderer/in_process_renderer_thread.cc',
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index 9c8e19e..e4a9a0b 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -44,12 +44,16 @@
     "//media/blink",
     "//mojo/application/public/interfaces",
     "//mojo/environment:chromium",
+    "//mojo/common:url_type_converters",
+    "//mojo/converters/geometry",
     "//net",
     "//skia",
+    "//skia/public",
     "//storage/common",
     "//third_party/icu",
     "//third_party/libjingle",
     "//third_party/mojo/src/mojo/edk/js",
+    "//third_party/mojo/src/mojo/public/cpp/bindings",
     "//third_party/mojo/src/mojo/public/js",
     "//third_party/npapi",
     "//third_party/WebKit/public:blink",
diff --git a/content/renderer/DEPS b/content/renderer/DEPS
index 1156983..5ea2ee71 100644
--- a/content/renderer/DEPS
+++ b/content/renderer/DEPS
@@ -12,7 +12,7 @@
   "+gin",
   "+jingle/glue",
   "+media",  # For audio input/output and audio/video decoding.
-  "+mojo/application/public/interfaces",
+  "+mojo",
   "-storage/browser",
   "+third_party/hyphen/hyphen.h",
   "+third_party/libjingle",
diff --git a/content/renderer/image_downloader/image_downloader_impl.cc b/content/renderer/image_downloader/image_downloader_impl.cc
new file mode 100644
index 0000000..c969418b
--- /dev/null
+++ b/content/renderer/image_downloader/image_downloader_impl.cc
@@ -0,0 +1,224 @@
+// Copyright 2015 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.
+
+#include "content/renderer/image_downloader/image_downloader_impl.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "content/child/image_decoder.h"
+#include "content/public/renderer/render_frame.h"
+#include "content/renderer/fetchers/multi_resolution_image_resource_fetcher.h"
+#include "mojo/common/url_type_converters.h"
+#include "mojo/converters/geometry/geometry_type_converters.h"
+#include "net/base/data_url.h"
+#include "skia/ext/image_operations.h"
+#include "skia/public/type_converters.h"
+#include "third_party/WebKit/public/platform/WebURLRequest.h"
+#include "third_party/WebKit/public/platform/WebVector.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebView.h"
+#include "ui/gfx/favicon_size.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/skbitmap_operations.h"
+#include "url/url_constants.h"
+
+using blink::WebFrame;
+using blink::WebVector;
+using blink::WebURL;
+using blink::WebURLRequest;
+
+namespace {
+
+// Decodes a data: URL image or returns an empty image in case of failure.
+SkBitmap ImageFromDataUrl(const GURL& url) {
+  std::string mime_type, char_set, data;
+  if (net::DataURL::Parse(url, &mime_type, &char_set, &data) && !data.empty()) {
+    // Decode the image using Blink's image decoder.
+    content::ImageDecoder decoder(
+        gfx::Size(gfx::kFaviconSize, gfx::kFaviconSize));
+    const unsigned char* src_data =
+        reinterpret_cast<const unsigned char*>(data.data());
+
+    return decoder.Decode(src_data, data.size());
+  }
+  return SkBitmap();
+}
+
+//  Proportionally resizes the |image| to fit in a box of size
+// |max_image_size|.
+SkBitmap ResizeImage(const SkBitmap& image, uint32_t max_image_size) {
+  if (max_image_size == 0)
+    return image;
+  uint32_t max_dimension = std::max(image.width(), image.height());
+  if (max_dimension <= max_image_size)
+    return image;
+  // Proportionally resize the minimal image to fit in a box of size
+  // max_image_size.
+  return skia::ImageOperations::Resize(
+      image, skia::ImageOperations::RESIZE_BEST,
+      static_cast<uint64_t>(image.width()) * max_image_size / max_dimension,
+      static_cast<uint64_t>(image.height()) * max_image_size / max_dimension);
+}
+
+// Filters the array of bitmaps, removing all images that do not fit in a box of
+// size |max_image_size|. Returns the result if it is not empty. Otherwise,
+// find the smallest image in the array and resize it proportionally to fit
+// in a box of size |max_image_size|.
+// Sets |original_image_sizes| to the sizes of |images| before resizing.
+void FilterAndResizeImagesForMaximalSize(
+    const std::vector<SkBitmap>& unfiltered,
+    uint32_t max_image_size,
+    std::vector<SkBitmap>* images,
+    std::vector<gfx::Size>* original_image_sizes) {
+  images->clear();
+  original_image_sizes->clear();
+
+  if (!unfiltered.size())
+    return;
+
+  if (max_image_size == 0)
+    max_image_size = std::numeric_limits<uint32_t>::max();
+
+  const SkBitmap* min_image = NULL;
+  uint32_t min_image_size = std::numeric_limits<uint32_t>::max();
+  // Filter the images by |max_image_size|, and also identify the smallest image
+  // in case all the images are bigger than |max_image_size|.
+  for (std::vector<SkBitmap>::const_iterator it = unfiltered.begin();
+       it != unfiltered.end(); ++it) {
+    const SkBitmap& image = *it;
+    uint32_t current_size = std::max(it->width(), it->height());
+    if (current_size < min_image_size) {
+      min_image = &image;
+      min_image_size = current_size;
+    }
+    if (static_cast<uint32_t>(image.width()) <= max_image_size &&
+        static_cast<uint32_t>(image.height()) <= max_image_size) {
+      images->push_back(image);
+      original_image_sizes->push_back(gfx::Size(image.width(), image.height()));
+    }
+  }
+  DCHECK(min_image);
+  if (images->size())
+    return;
+  // Proportionally resize the minimal image to fit in a box of size
+  // |max_image_size|.
+  images->push_back(ResizeImage(*min_image, max_image_size));
+  original_image_sizes->push_back(
+      gfx::Size(min_image->width(), min_image->height()));
+}
+
+}  // namespace
+
+namespace content {
+
+ImageDownloaderImpl::ImageDownloaderImpl(
+    RenderFrame* render_frame,
+    mojo::InterfaceRequest<image_downloader::ImageDownloader> request)
+    : RenderFrameObserver(render_frame), binding_(this, request.Pass()) {
+  DCHECK(render_frame);
+}
+
+ImageDownloaderImpl::~ImageDownloaderImpl() {
+}
+
+// static
+void ImageDownloaderImpl::CreateMojoService(
+    RenderFrame* render_frame,
+    mojo::InterfaceRequest<image_downloader::ImageDownloader> request) {
+  DVLOG(1) << "ImageDownloaderImpl::CreateService";
+  DCHECK(render_frame);
+
+  new ImageDownloaderImpl(render_frame, request.Pass());
+}
+
+// ImageDownloader methods:
+void ImageDownloaderImpl::DownloadImage(
+    image_downloader::DownloadRequestPtr req,
+    const DownloadImageCallback& callback) {
+  const GURL image_url = req->url.To<GURL>();
+  bool is_favicon = req->is_favicon;
+  uint32_t max_image_size = req->max_bitmap_size;
+  bool bypass_cache = req->bypass_cache;
+
+  std::vector<SkBitmap> result_images;
+  std::vector<gfx::Size> result_original_image_sizes;
+
+  if (image_url.SchemeIs(url::kDataScheme)) {
+    SkBitmap data_image = ImageFromDataUrl(image_url);
+    if (!data_image.empty()) {
+      result_images.push_back(ResizeImage(data_image, max_image_size));
+      result_original_image_sizes.push_back(
+          gfx::Size(data_image.width(), data_image.height()));
+    }
+  } else {
+    if (FetchImage(image_url, is_favicon, max_image_size, bypass_cache,
+                   callback)) {
+      // Will complete asynchronously via ImageDownloaderImpl::DidFetchImage
+      return;
+    }
+  }
+
+  ReplyDownloadResult(0, result_images, result_original_image_sizes, callback);
+}
+
+bool ImageDownloaderImpl::FetchImage(const GURL& image_url,
+                                     bool is_favicon,
+                                     uint32_t max_image_size,
+                                     bool bypass_cache,
+                                     const DownloadImageCallback& callback) {
+  blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+  DCHECK(frame);
+
+  // Create an image resource fetcher and assign it with a call back object.
+  image_fetchers_.push_back(new MultiResolutionImageResourceFetcher(
+      image_url, frame, 0, is_favicon ? WebURLRequest::RequestContextFavicon
+                                      : WebURLRequest::RequestContextImage,
+      bypass_cache ? WebURLRequest::ReloadBypassingCache
+                   : WebURLRequest::UseProtocolCachePolicy,
+      base::Bind(&ImageDownloaderImpl::DidFetchImage, base::Unretained(this),
+                 max_image_size, callback)));
+  return true;
+}
+
+void ImageDownloaderImpl::DidFetchImage(
+    uint32_t max_image_size,
+    const DownloadImageCallback& callback,
+    MultiResolutionImageResourceFetcher* fetcher,
+    const std::vector<SkBitmap>& images) {
+  std::vector<SkBitmap> result_images;
+  std::vector<gfx::Size> result_original_image_sizes;
+  FilterAndResizeImagesForMaximalSize(images, max_image_size, &result_images,
+                                      &result_original_image_sizes);
+
+  ReplyDownloadResult(fetcher->http_status_code(), result_images,
+                      result_original_image_sizes, callback);
+
+  // Remove the image fetcher from our pending list. We're in the callback from
+  // MultiResolutionImageResourceFetcher, best to delay deletion.
+  ImageResourceFetcherList::iterator iter =
+      std::find(image_fetchers_.begin(), image_fetchers_.end(), fetcher);
+  if (iter != image_fetchers_.end()) {
+    image_fetchers_.weak_erase(iter);
+    base::MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
+  }
+}
+
+void ImageDownloaderImpl::ReplyDownloadResult(
+    int32_t http_status_code,
+    const std::vector<SkBitmap>& result_images,
+    const std::vector<gfx::Size>& result_original_image_sizes,
+    const DownloadImageCallback& callback) {
+  image_downloader::DownloadResultPtr result =
+      image_downloader::DownloadResult::New();
+
+  result->http_status_code = http_status_code;
+  result->images = mojo::Array<skia::BitmapPtr>::From(result_images);
+  result->original_image_sizes =
+      mojo::Array<mojo::SizePtr>::From(result_original_image_sizes);
+
+  callback.Run(result.Pass());
+}
+
+}  // namespace content
diff --git a/content/renderer/image_downloader/image_downloader_impl.h b/content/renderer/image_downloader/image_downloader_impl.h
new file mode 100644
index 0000000..1dba6d25
--- /dev/null
+++ b/content/renderer/image_downloader/image_downloader_impl.h
@@ -0,0 +1,88 @@
+// Copyright 2015 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.
+
+#ifndef CONTENT_RENDERER_IMAGE_DOWNLOADER_IMAGE_DOWNLOADER_IMPL_H_
+#define CONTENT_RENDERER_IMAGE_DOWNLOADER_IMAGE_DOWNLOADER_IMPL_H_
+
+#include <vector>
+
+#include "base/memory/scoped_vector.h"
+#include "content/common/image_downloader/image_downloader.mojom.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "third_party/mojo/src/mojo/public/cpp/bindings/strong_binding.h"
+#include "url/gurl.h"
+
+class SkBitmap;
+
+namespace gfx {
+class Size;
+}
+
+namespace content {
+
+class MultiResolutionImageResourceFetcher;
+class RenderFrame;
+
+class ImageDownloaderImpl : public image_downloader::ImageDownloader,
+                            public RenderFrameObserver {
+ public:
+  static void CreateMojoService(
+      RenderFrame* render_frame,
+      mojo::InterfaceRequest<image_downloader::ImageDownloader> request);
+
+ private:
+  ImageDownloaderImpl(
+      RenderFrame* render_frame,
+      mojo::InterfaceRequest<image_downloader::ImageDownloader> request);
+  ~ImageDownloaderImpl() override;
+
+  // ImageDownloader methods:
+  void DownloadImage(image_downloader::DownloadRequestPtr req,
+                     const DownloadImageCallback& callback) override;
+
+  // Requests to fetch an image. When done, the ImageDownloaderImpl
+  // is notified by way of DidFetchImage. Returns true if the
+  // request was successfully started, false otherwise.
+  // If the image is a favicon, cookies will not be
+  // sent nor accepted during download. If the image has multiple frames, all
+  // the frames whose size <= |max_image_size| are returned. If all of the
+  // frames are larger than |max_image_size|, the smallest frame is resized to
+  // |max_image_size| and is the only result. |max_image_size| == 0 is
+  // interpreted as no max image size.
+  bool FetchImage(const GURL& image_url,
+                  bool is_favicon,
+                  uint32_t max_image_size,
+                  bool bypass_cache,
+                  const DownloadImageCallback& callback);
+
+  // This callback is triggered when FetchImage completes, either
+  // succesfully or with a failure. See FetchImage for more
+  // details.
+  void DidFetchImage(uint32_t max_image_size,
+                     const DownloadImageCallback& callback,
+                     MultiResolutionImageResourceFetcher* fetcher,
+                     const std::vector<SkBitmap>& images);
+
+  // Reply download result
+  void ReplyDownloadResult(
+      int32_t http_status_code,
+      const std::vector<SkBitmap>& result_images,
+      const std::vector<gfx::Size>& result_original_image_sizes,
+      const DownloadImageCallback& callback);
+
+  // We use StrongBinding to ensure deletion of "this" when connection closed
+  mojo::StrongBinding<ImageDownloader> binding_;
+
+  typedef ScopedVector<MultiResolutionImageResourceFetcher>
+      ImageResourceFetcherList;
+
+  // ImageResourceFetchers schedule via FetchImage.
+  ImageResourceFetcherList image_fetchers_;
+
+  DISALLOW_COPY_AND_ASSIGN(ImageDownloaderImpl);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_RENDERER_IMAGE_DOWNLOADER_IMAGE_DOWNLOADER_IMPL_H_
diff --git a/content/renderer/image_loading_helper.cc b/content/renderer/image_loading_helper.cc
deleted file mode 100644
index afd0fee..0000000
--- a/content/renderer/image_loading_helper.cc
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright (c) 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.
-
-#include "content/renderer/image_loading_helper.h"
-
-#include "base/bind.h"
-#include "base/message_loop/message_loop.h"
-#include "content/child/image_decoder.h"
-#include "content/common/image_messages.h"
-#include "content/public/renderer/render_frame.h"
-#include "content/renderer/fetchers/multi_resolution_image_resource_fetcher.h"
-#include "net/base/data_url.h"
-#include "skia/ext/image_operations.h"
-#include "third_party/WebKit/public/platform/WebURLRequest.h"
-#include "third_party/WebKit/public/platform/WebVector.h"
-#include "third_party/WebKit/public/web/WebLocalFrame.h"
-#include "third_party/WebKit/public/web/WebView.h"
-#include "ui/gfx/favicon_size.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/gfx/skbitmap_operations.h"
-#include "url/url_constants.h"
-
-using blink::WebFrame;
-using blink::WebVector;
-using blink::WebURL;
-using blink::WebURLRequest;
-
-namespace {
-
-//  Proportionally resizes the |image| to fit in a box of size
-// |max_image_size|.
-SkBitmap ResizeImage(const SkBitmap& image, uint32_t max_image_size) {
-  if (max_image_size == 0)
-    return image;
-  uint32_t max_dimension = std::max(image.width(), image.height());
-  if (max_dimension <= max_image_size)
-    return image;
-  // Proportionally resize the minimal image to fit in a box of size
-  // max_image_size.
-  return skia::ImageOperations::Resize(
-      image,
-      skia::ImageOperations::RESIZE_BEST,
-      static_cast<uint64_t>(image.width()) * max_image_size / max_dimension,
-      static_cast<uint64_t>(image.height()) * max_image_size / max_dimension);
-}
-
-// Filters the array of bitmaps, removing all images that do not fit in a box of
-// size |max_image_size|. Returns the result if it is not empty. Otherwise,
-// find the smallest image in the array and resize it proportionally to fit
-// in a box of size |max_image_size|.
-// Sets |original_image_sizes| to the sizes of |images| before resizing.
-void FilterAndResizeImagesForMaximalSize(
-    const std::vector<SkBitmap>& unfiltered,
-    uint32_t max_image_size,
-    std::vector<SkBitmap>* images,
-    std::vector<gfx::Size>* original_image_sizes) {
-  images->clear();
-  original_image_sizes->clear();
-
-  if (!unfiltered.size())
-    return;
-
-  if (max_image_size == 0)
-    max_image_size = std::numeric_limits<uint32_t>::max();
-
-  const SkBitmap* min_image = NULL;
-  uint32_t min_image_size = std::numeric_limits<uint32_t>::max();
-  // Filter the images by |max_image_size|, and also identify the smallest image
-  // in case all the images are bigger than |max_image_size|.
-  for (std::vector<SkBitmap>::const_iterator it = unfiltered.begin();
-       it != unfiltered.end();
-       ++it) {
-    const SkBitmap& image = *it;
-    uint32_t current_size = std::max(it->width(), it->height());
-    if (current_size < min_image_size) {
-      min_image = &image;
-      min_image_size = current_size;
-    }
-    if (static_cast<uint32_t>(image.width()) <= max_image_size &&
-        static_cast<uint32_t>(image.height()) <= max_image_size) {
-      images->push_back(image);
-      original_image_sizes->push_back(gfx::Size(image.width(), image.height()));
-    }
-  }
-  DCHECK(min_image);
-  if (images->size())
-    return;
-  // Proportionally resize the minimal image to fit in a box of size
-  // |max_image_size|.
-  images->push_back(ResizeImage(*min_image, max_image_size));
-  original_image_sizes->push_back(
-      gfx::Size(min_image->width(), min_image->height()));
-}
-
-}  // namespace
-
-namespace content {
-
-ImageLoadingHelper::ImageLoadingHelper(RenderFrame* render_frame)
-    : RenderFrameObserver(render_frame) {
-}
-
-ImageLoadingHelper::~ImageLoadingHelper() {
-}
-
-void ImageLoadingHelper::OnDownloadImage(
-    int id,
-    const GURL& image_url,
-    bool is_favicon,
-    uint32_t max_image_size,
-    bool bypass_cache) {
-  std::vector<SkBitmap> result_images;
-  std::vector<gfx::Size> result_original_image_sizes;
-  if (image_url.SchemeIs(url::kDataScheme)) {
-    SkBitmap data_image = ImageFromDataUrl(image_url);
-    if (!data_image.empty()) {
-      result_images.push_back(ResizeImage(data_image, max_image_size));
-      result_original_image_sizes.push_back(
-          gfx::Size(data_image.width(), data_image.height()));
-    }
-  } else {
-    if (DownloadImage(id, image_url, is_favicon, max_image_size,
-                      bypass_cache)) {
-      // Will complete asynchronously via ImageLoadingHelper::DidDownloadImage
-      return;
-    }
-  }
-
-  Send(new ImageHostMsg_DidDownloadImage(routing_id(),
-                                         id,
-                                         0,
-                                         image_url,
-                                         result_images,
-                                         result_original_image_sizes));
-}
-
-bool ImageLoadingHelper::DownloadImage(
-    int id,
-    const GURL& image_url,
-    bool is_favicon,
-    uint32_t max_image_size,
-    bool bypass_cache) {
-  // Create an image resource fetcher and assign it with a call back object.
-  image_fetchers_.push_back(new MultiResolutionImageResourceFetcher(
-      image_url, render_frame()->GetWebFrame(), id,
-      is_favicon ? WebURLRequest::RequestContextFavicon
-                 : WebURLRequest::RequestContextImage,
-      bypass_cache ? WebURLRequest::ReloadBypassingCache
-                   : WebURLRequest::UseProtocolCachePolicy,
-      base::Bind(&ImageLoadingHelper::DidDownloadImage, base::Unretained(this),
-                 max_image_size)));
-  return true;
-}
-
-void ImageLoadingHelper::DidDownloadImage(
-    uint32_t max_image_size,
-    MultiResolutionImageResourceFetcher* fetcher,
-    const std::vector<SkBitmap>& images) {
-  std::vector<SkBitmap> result_images;
-  std::vector<gfx::Size> result_original_image_sizes;
-  FilterAndResizeImagesForMaximalSize(images, max_image_size, &result_images,
-      &result_original_image_sizes);
-
-  // Notify requester of image download status.
-  Send(new ImageHostMsg_DidDownloadImage(
-      routing_id(),
-      fetcher->id(),
-      fetcher->http_status_code(),
-      fetcher->image_url(),
-      result_images,
-      result_original_image_sizes));
-
-  // Remove the image fetcher from our pending list. We're in the callback from
-  // MultiResolutionImageResourceFetcher, best to delay deletion.
-  ImageResourceFetcherList::iterator iter =
-      std::find(image_fetchers_.begin(), image_fetchers_.end(), fetcher);
-  if (iter != image_fetchers_.end()) {
-    image_fetchers_.weak_erase(iter);
-    base::MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
-  }
-}
-
-SkBitmap ImageLoadingHelper::ImageFromDataUrl(const GURL& url) const {
-  std::string mime_type, char_set, data;
-  if (net::DataURL::Parse(url, &mime_type, &char_set, &data) && !data.empty()) {
-    // Decode the image using WebKit's image decoder.
-    ImageDecoder decoder(gfx::Size(gfx::kFaviconSize, gfx::kFaviconSize));
-    const unsigned char* src_data =
-        reinterpret_cast<const unsigned char*>(&data[0]);
-
-    return decoder.Decode(src_data, data.size());
-  }
-  return SkBitmap();
-}
-
-bool ImageLoadingHelper::OnMessageReceived(const IPC::Message& message) {
-  bool handled = true;
-  IPC_BEGIN_MESSAGE_MAP(ImageLoadingHelper, message)
-    IPC_MESSAGE_HANDLER(ImageMsg_DownloadImage, OnDownloadImage)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-
-  return handled;
-}
-
-}  // namespace content
diff --git a/content/renderer/image_loading_helper.h b/content/renderer/image_loading_helper.h
deleted file mode 100644
index d5daf8fa..0000000
--- a/content/renderer/image_loading_helper.h
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright (c) 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.
-
-#ifndef CONTENT_RENDERER_IMAGE_LOADING_HELPER_H_
-#define CONTENT_RENDERER_IMAGE_LOADING_HELPER_H_
-
-#include <string>
-#include <vector>
-
-#include "base/memory/scoped_vector.h"
-#include "content/public/renderer/render_frame_observer.h"
-#include "url/gurl.h"
-
-class SkBitmap;
-
-namespace content {
-
-class MultiResolutionImageResourceFetcher;
-
-// This class deals with image downloading.
-// One instance of ImageLoadingHelper is owned by RenderFrame.
-class ImageLoadingHelper : public RenderFrameObserver {
- public:
-  explicit ImageLoadingHelper(RenderFrame* render_frame);
-
- private:
-  ~ImageLoadingHelper() override;
-
-  // Message handler.
-  void OnDownloadImage(int id,
-                       const GURL& image_url,
-                       bool is_favicon,
-                       uint32_t max_image_size,
-                       bool bypass_cache);
-
-  // Requests to download an image. When done, the ImageLoadingHelper
-  // is notified by way of DidDownloadImage. Returns true if the
-  // request was successfully started, false otherwise. id is used to
-  // uniquely identify the request and passed back to the
-  // DidDownloadImage method. If the image is a favicon, cookies will not be
-  // sent nor accepted during download. If the image has multiple frames, all
-  // the frames whose size <= |max_image_size| are returned. If all of the
-  // frames are larger than |max_image_size|, the smallest frame is resized to
-  // |max_image_size| and is the only result. |max_image_size| == 0 is
-  // interpreted as no max image size.
-  bool DownloadImage(int id,
-                     const GURL& image_url,
-                     bool is_favicon,
-                     uint32_t max_image_size,
-                     bool bypass_cache);
-
-  // This callback is triggered when DownloadImage completes, either
-  // succesfully or with a failure. See DownloadImage for more
-  // details.
-  void DidDownloadImage(
-      uint32_t max_image_size,
-      MultiResolutionImageResourceFetcher* fetcher,
-      const std::vector<SkBitmap>& images);
-
-  // Decodes a data: URL image or returns an empty image in case of failure.
-  SkBitmap ImageFromDataUrl(const GURL&) const;
-
-  // RenderFrameObserver implementation.
-  bool OnMessageReceived(const IPC::Message& message) override;
-
-  typedef ScopedVector<MultiResolutionImageResourceFetcher>
-      ImageResourceFetcherList;
-
-  // ImageResourceFetchers schedule via DownloadImage.
-  ImageResourceFetcherList image_fetchers_;
-
-  DISALLOW_COPY_AND_ASSIGN(ImageLoadingHelper);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_IMAGE_LOADING_HELPER_H_
-
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 2e7d77a..afaffcd 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -72,7 +72,7 @@
 #include "content/renderer/gpu/gpu_benchmarking_extension.h"
 #include "content/renderer/history_controller.h"
 #include "content/renderer/history_serialization.h"
-#include "content/renderer/image_loading_helper.h"
+#include "content/renderer/image_downloader/image_downloader_impl.h"
 #include "content/renderer/ime_event_guard.h"
 #include "content/renderer/internal_document_state_data.h"
 #include "content/renderer/manifest/manifest_manager.h"
@@ -741,15 +741,14 @@
 #endif
   new SharedWorkerRepository(this);
 
-  if (!frame_->parent())
-    new ImageLoadingHelper(this);
-
   if (is_local_root_ && !render_frame_proxy_) {
     // DevToolsAgent is a RenderFrameObserver, and will destruct itself
     // when |this| is deleted.
     devtools_agent_ = new DevToolsAgent(this);
   }
 
+  RegisterMojoServices();
+
   // We delay calling this until we have the WebFrame so that any observer or
   // embedder can call GetWebFrame on any RenderFrame.
   GetContentClient()->renderer()->RenderFrameCreated(this);
@@ -5008,4 +5007,13 @@
   return cdm_factory_.get();
 }
 
+void RenderFrameImpl::RegisterMojoServices() {
+  // Only main frame have ImageDownloader service.
+  if (!frame_->parent()) {
+    GetServiceRegistry()->AddService<image_downloader::ImageDownloader>(
+        base::Bind(&ImageDownloaderImpl::CreateMojoService,
+                   base::Unretained(this)));
+  }
+}
+
 }  // namespace content
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index d6bcafd..4259baa 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -823,6 +823,8 @@
 
   media::CdmFactory* GetCdmFactory();
 
+  void RegisterMojoServices();
+
   // Stores the WebLocalFrame we are associated with.  This is null from the
   // constructor until SetWebFrame is called, and it is null after
   // frameDetached is called until destruction (which is asynchronous in the
diff --git a/content/test/test_web_contents.cc b/content/test/test_web_contents.cc
index 65bb578..f93024c 100644
--- a/content/test/test_web_contents.cc
+++ b/content/test/test_web_contents.cc
@@ -64,6 +64,15 @@
       GetRenderManager()->pending_frame_host());
 }
 
+int TestWebContents::DownloadImage(const GURL& url,
+                                   bool is_favicon,
+                                   uint32_t max_bitmap_size,
+                                   bool bypass_cache,
+                                   const ImageDownloadCallback& callback) {
+  static int g_next_image_download_id = 0;
+  return ++g_next_image_download_id;
+}
+
 void TestWebContents::TestDidNavigate(RenderFrameHost* render_frame_host,
                                       int page_id,
                                       int nav_entry_id,
diff --git a/content/test/test_web_contents.h b/content/test/test_web_contents.h
index 798a54d..fd3a3be4 100644
--- a/content/test/test_web_contents.h
+++ b/content/test/test_web_contents.h
@@ -35,6 +35,12 @@
   // WebContentsImpl overrides (returning the same values, but in Test* types)
   TestRenderFrameHost* GetMainFrame() override;
   TestRenderViewHost* GetRenderViewHost() const override;
+  // Overrides to avoid establishing Mojo connection with renderer process.
+  int DownloadImage(const GURL& url,
+                    bool is_favicon,
+                    uint32_t max_bitmap_size,
+                    bool bypass_cache,
+                    const ImageDownloadCallback& callback) override;
 
   // WebContentsTester implementation.
   void CommitPendingNavigation() override;
diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h
index bd47f20..b37c5c9 100644
--- a/ipc/ipc_message_start.h
+++ b/ipc/ipc_message_start.h
@@ -53,7 +53,6 @@
   ExtensionMsgStart,
   VideoCaptureMsgStart,
   QuotaMsgStart,
-  ImageMsgStart,
   TextInputClientMsgStart,
   ChromeUtilityMsgStart,
   MediaStreamMsgStart,
diff --git a/mojo/mojo_base.gyp b/mojo/mojo_base.gyp
index 70948a1..1e3b181 100644
--- a/mojo/mojo_base.gyp
+++ b/mojo/mojo_base.gyp
@@ -84,6 +84,25 @@
       ],
     },
     {
+      # GN version: //mojo/converters/geometry
+      'target_name': 'mojo_geometry_lib',
+      'type': '<(component)',
+      'defines': [
+        'MOJO_GEOMETRY_IMPLEMENTATION',
+      ],
+      'dependencies': [
+        '../ui/mojo/geometry/mojo_bindings.gyp:mojo_geometry_bindings',
+        '../ui/gfx/gfx.gyp:gfx_geometry',
+        'mojo_environment_chromium',
+        '<(mojo_system_for_component)',
+      ],
+      'sources': [
+        'converters/geometry/geometry_type_converters.cc',
+        'converters/geometry/geometry_type_converters.h',
+        'converters/geometry/mojo_geometry_export.h',
+      ],
+    },
+    {
       # GN version: //mojo/common:mojo_common_unittests
       'target_name': 'mojo_common_unittests',
       'type': 'executable',
diff --git a/ui/mojo/geometry/mojo_bindings.gyp b/ui/mojo/geometry/mojo_bindings.gyp
new file mode 100644
index 0000000..e5bfe67
--- /dev/null
+++ b/ui/mojo/geometry/mojo_bindings.gyp
@@ -0,0 +1,27 @@
+# Copyright 2015 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.
+
+{
+  'targets': [
+    {
+      # GN version: //ui/mojo/geometry
+      'target_name': 'mojo_geometry_bindings_mojom',
+      'type': 'none',
+      'variables': {
+        'mojom_files': [
+          'geometry.mojom',
+        ],
+      },
+      'includes': [ '../../../third_party/mojo/mojom_bindings_generator_explicit.gypi' ],
+    },
+    {
+      'target_name': 'mojo_geometry_bindings',
+      'type': 'static_library',
+      'dependencies': [
+        'mojo_geometry_bindings_mojom',
+        '../../../third_party/mojo/mojo_public.gyp:mojo_cpp_bindings',
+      ],
+    },
+  ],
+}