Introduce the image_decoder service
Moves OOP image decoding from a one-off utility process IPC
to its own service_manager service. This is then hooked up
by Chrome to be launched by content's utility-process-based
sandboxed service launcher.
This serves as a precursor to extracing process launch
code from content, as it defines a service which is used
in production across all platforms that support multiprocess.
The ImageDecoder helper defined in src/chrome is substantially
simplified here, as it's reduced to a thin wrapper around the
image_decoder client library.
In order to support Chrome content_browser packaging the
service, this also adds support for packaging services within
manifest overlays by introducing a new service_manifest_overlay
GN template.
Finally this also introduces a new service_unittests binary
as a default home for service implementations to stash their
unittest sources. Follow-up work will add this test suite to
bot configurations.
BUG=654986
Review-Url: https://codereview.chromium.org/2475543003
Cr-Commit-Position: refs/heads/master@{#431605}
diff --git a/BUILD.gn b/BUILD.gn
index 64ab231..73c3053 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -882,6 +882,7 @@
deps = [
"//ipc:ipc_tests",
"//mojo:tests",
+ "//services:service_unittests",
]
}
}
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index 1e77e51..cbb7cd8 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -353,6 +353,14 @@
}
}
+service_manifest_overlay("chrome_content_browser_manifest_overlay") {
+ source = "//chrome/browser/chrome_content_browser_manifest_overlay.json"
+ packaged_services = [ "image_decoder" ]
+ deps = [
+ "//services/image_decoder:manifest",
+ ]
+}
+
if (use_aura) {
# NOTE: These rules generate compiled versions of the content service
# manifests with Chrome's overlays applied. These are only used at run-time,
@@ -365,8 +373,9 @@
output_name = "chrome_content_browser"
source = "${root_out_dir}/Packages/content_browser/manifest.json"
overlays =
- [ "//chrome/browser/chrome_content_browser_manifest_overlay.json" ]
+ [ "${root_gen_dir}/chrome_content_browser_manifest_overlay.json" ]
deps = [
+ ":chrome_content_browser_manifest_overlay",
"//content/public/app:browser_manifest",
]
}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 74c463a..52e761c 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1443,6 +1443,7 @@
"//net:extras",
"//net:net_with_v8",
"//printing/features",
+ "//services/image_decoder/public/cpp",
"//services/service_manager/public/cpp",
"//skia",
"//sql",
@@ -4066,6 +4067,7 @@
# Depend only on the generated mojo bindings since we read the .mojom.js
# file, rather than the whole mojo target which will link the C++ bindings.
+ "//chrome/app:chrome_content_browser_manifest_overlay",
"//chrome/browser/ui/webui/engagement:mojo_bindings__generator",
"//chrome/browser/ui/webui/omnibox:mojo_bindings__generator",
"//chrome/browser/ui/webui/plugins:mojo_bindings__generator",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 0eebd89..6bc62ef 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -37,6 +37,7 @@
"+media/cdm/cdm_paths.h",
"+media/midi", # For midi switches
"+media/mojo", # For mojo media services.
+ "+services/image_decoder/public/cpp",
"+services/service_manager/public/cpp",
"+services/ui/public",
"+ppapi/c", # For various types.
diff --git a/chrome/browser/android/download/ui/thumbnail_provider.h b/chrome/browser/android/download/ui/thumbnail_provider.h
index dcdab20..aa3ea8e0 100644
--- a/chrome/browser/android/download/ui/thumbnail_provider.h
+++ b/chrome/browser/android/download/ui/thumbnail_provider.h
@@ -8,6 +8,7 @@
#include <string>
#include "base/android/jni_android.h"
+#include "base/memory/weak_ptr.h"
#include "chrome/browser/image_decoder.h"
// Kicks off asynchronous pipelines for creating thumbnails for local files.
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 6590f23..bd05bf6 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -606,7 +606,7 @@
<include name="IDR_IME_WINDOW_CLOSE_C" file="resources\input_ime\ime_window_close_click.png" type="BINDATA" />
<include name="IDR_IME_WINDOW_CLOSE_H" file="resources\input_ime\ime_window_close_hover.png" type="BINDATA" />
</if>
- <include name="IDR_CHROME_CONTENT_BROWSER_MANIFEST_OVERLAY" file="chrome_content_browser_manifest_overlay.json" type="BINDATA" />
+ <include name="IDR_CHROME_CONTENT_BROWSER_MANIFEST_OVERLAY" file="${root_gen_dir}\chrome_content_browser_manifest_overlay.json" use_base_dir="false" type="BINDATA" />
<include name="IDR_CHROME_CONTENT_GPU_MANIFEST_OVERLAY" file="chrome_content_gpu_manifest_overlay.json" type="BINDATA" />
<include name="IDR_CHROME_CONTENT_PLUGIN_MANIFEST_OVERLAY" file="chrome_content_plugin_manifest_overlay.json" type="BINDATA" />
<include name="IDR_CHROME_CONTENT_RENDERER_MANIFEST_OVERLAY" file="chrome_content_renderer_manifest_overlay.json" type="BINDATA" />
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 3e19a3b..47900fbf 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -183,6 +183,7 @@
#include "net/ssl/ssl_cert_request_info.h"
#include "ppapi/host/ppapi_host.h"
#include "printing/features/features.h"
+#include "services/image_decoder/public/cpp/constants.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "services/service_manager/public/cpp/interface_registry.h"
#include "services/service_manager/public/cpp/service.h"
@@ -3012,6 +3013,8 @@
services->insert(std::make_pair("service:media",
base::ASCIIToUTF16("Media Service")));
#endif
+ services->insert(std::make_pair(image_decoder::kServiceName,
+ base::ASCIIToUTF16("Image Decoder Service")));
}
std::unique_ptr<base::Value>
diff --git a/chrome/browser/chrome_content_browser_manifest_overlay.json b/chrome/browser/chrome_content_browser_manifest_overlay.json
index 8f7fec4..ed6a8a1 100644
--- a/chrome/browser/chrome_content_browser_manifest_overlay.json
+++ b/chrome/browser/chrome_content_browser_manifest_overlay.json
@@ -33,7 +33,8 @@
]
},
"requires": {
- "service:ash": [ "ash" ]
+ "service:ash": [ "ash" ],
+ "service:image_decoder": [ "decode" ]
}
},
"navigation:frame": {
diff --git a/chrome/browser/image_decoder.cc b/chrome/browser/image_decoder.cc
index 3815f26dc..93360b70 100644
--- a/chrome/browser/image_decoder.cc
+++ b/chrome/browser/image_decoder.cc
@@ -7,53 +7,78 @@
#include <utility>
#include "base/bind.h"
+#include "base/callback.h"
+#include "base/lazy_instance.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/common/image_decoder.mojom.h"
-#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/utility_process_host.h"
-#include "services/service_manager/public/cpp/interface_provider.h"
+#include "content/public/common/service_manager_connection.h"
+#include "ipc/ipc_channel.h"
+#include "services/image_decoder/public/cpp/decode.h"
+#include "services/service_manager/public/cpp/connector.h"
#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/base/l10n/l10n_util.h"
-
-using content::BrowserThread;
-using content::UtilityProcessHost;
namespace {
// static, Leaky to allow access from any thread.
base::LazyInstance<ImageDecoder>::Leaky g_decoder = LAZY_INSTANCE_INITIALIZER;
-// How long to wait after the last request has been received before ending
-// batch mode.
-const int kBatchModeTimeoutSeconds = 5;
+const int64_t kMaxImageSizeInBytes =
+ static_cast<int64_t>(IPC::Channel::kMaximumMessageSize);
+// Note that this is always called on the thread which initiated the
+// corresponding image_decoder::Decode request.
void OnDecodeImageDone(
base::Callback<void(int)> fail_callback,
base::Callback<void(const SkBitmap&, int)> success_callback,
int request_id,
const SkBitmap& image) {
- DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!image.isNull() && !image.empty())
success_callback.Run(image, request_id);
else
fail_callback.Run(request_id);
}
+void BindToBrowserConnector(service_manager::mojom::ConnectorRequest request) {
+ if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(&BindToBrowserConnector, base::Passed(&request)));
+ return;
+ }
+
+ content::ServiceManagerConnection::GetForProcess()->GetConnector()
+ ->BindRequest(std::move(request));
+}
+
+void RunDecodeCallbackOnTaskRunner(
+ const image_decoder::mojom::ImageDecoder::DecodeImageCallback& callback,
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ const SkBitmap& image) {
+ task_runner->PostTask(FROM_HERE, base::Bind(callback, image));
+}
+
+void DecodeImage(
+ std::vector<uint8_t> image_data,
+ image_decoder::mojom::ImageCodec codec,
+ bool shrink_to_fit,
+ const image_decoder::mojom::ImageDecoder::DecodeImageCallback& callback,
+ scoped_refptr<base::SequencedTaskRunner> callback_task_runner) {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+ service_manager::mojom::ConnectorRequest connector_request;
+ std::unique_ptr<service_manager::Connector> connector =
+ service_manager::Connector::Create(&connector_request);
+ BindToBrowserConnector(std::move(connector_request));
+
+ image_decoder::Decode(connector.get(), image_data, codec, shrink_to_fit,
+ kMaxImageSizeInBytes,
+ base::Bind(&RunDecodeCallbackOnTaskRunner,
+ callback, callback_task_runner));
+}
+
} // namespace
-ImageDecoder::ImageDecoder()
- : image_request_id_counter_(0) {
- // A single ImageDecoder instance should live for the life of the program.
- // Explicitly add a reference so the object isn't deleted.
- AddRef();
-}
-
-ImageDecoder::~ImageDecoder() {
-}
-
ImageDecoder::ImageRequest::ImageRequest()
: task_runner_(base::ThreadTaskRunnerHandle::Get()) {
DCHECK(sequence_checker_.CalledOnValidSequence());
@@ -70,6 +95,10 @@
ImageDecoder::Cancel(this);
}
+ImageDecoder::ImageDecoder() : image_request_id_counter_(0) {}
+
+ImageDecoder::~ImageDecoder() {}
+
// static
void ImageDecoder::Start(ImageRequest* image_request,
std::vector<uint8_t> image_data) {
@@ -88,9 +117,8 @@
std::vector<uint8_t> image_data,
ImageCodec image_codec,
bool shrink_to_fit) {
- g_decoder.Pointer()->StartWithOptionsImpl(image_request,
- std::move(image_data),
- image_codec, shrink_to_fit);
+ g_decoder.Get().StartWithOptionsImpl(image_request, std::move(image_data),
+ image_codec, shrink_to_fit);
}
// static
@@ -117,68 +145,35 @@
image_request_id_map_.insert(std::make_pair(request_id, image_request));
}
- BrowserThread::PostTask(
- BrowserThread::IO, FROM_HERE,
- base::Bind(
- &ImageDecoder::DecodeImageInSandbox,
- g_decoder.Pointer(), request_id,
- base::Passed(std::move(image_data)),
- image_codec, shrink_to_fit));
+ image_decoder::mojom::ImageCodec codec =
+ image_decoder::mojom::ImageCodec::DEFAULT;
+#if defined(OS_CHROMEOS)
+ if (image_codec == ROBUST_JPEG_CODEC)
+ codec = image_decoder::mojom::ImageCodec::ROBUST_JPEG;
+ if (image_codec == ROBUST_PNG_CODEC)
+ codec = image_decoder::mojom::ImageCodec::ROBUST_PNG;
+#endif // defined(OS_CHROMEOS)
+
+ auto callback = base::Bind(
+ &OnDecodeImageDone,
+ base::Bind(&ImageDecoder::OnDecodeImageFailed, base::Unretained(this)),
+ base::Bind(&ImageDecoder::OnDecodeImageSucceeded, base::Unretained(this)),
+ request_id);
+
+ // NOTE: There exist ImageDecoder consumers which implicitly rely on this
+ // operation happening on a thread which always has a ThreadTaskRunnerHandle.
+ // We arbitrarily use the IO thread here to match details of the legacy
+ // implementation.
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO, FROM_HERE,
+ base::Bind(&DecodeImage, base::Passed(&image_data), codec, shrink_to_fit,
+ callback, make_scoped_refptr(image_request->task_runner())));
}
// static
void ImageDecoder::Cancel(ImageRequest* image_request) {
DCHECK(image_request);
- g_decoder.Pointer()->CancelImpl(image_request);
-}
-
-void ImageDecoder::DecodeImageInSandbox(
- int request_id,
- std::vector<uint8_t> image_data,
- ImageCodec image_codec,
- bool shrink_to_fit) {
- DCHECK_CURRENTLY_ON(BrowserThread::IO);
- base::AutoLock lock(map_lock_);
- const auto it = image_request_id_map_.find(request_id);
- if (it == image_request_id_map_.end())
- return;
-
- ImageRequest* image_request = it->second;
- if (!utility_process_host_) {
- StartBatchMode();
- }
- if (!utility_process_host_) {
- // Utility process failed to start; notify delegate and return.
- // Without this check, we were seeing crashes on startup. Further
- // investigation is needed to determine why the utility process
- // is failing to start. See crbug.com/472272
- image_request->task_runner()->PostTask(
- FROM_HERE,
- base::Bind(&ImageDecoder::RunOnDecodeImageFailed, this, request_id));
- return;
- }
-
- if (!batch_mode_timer_) {
- // Created here so it will call StopBatchMode() on the right thread.
- batch_mode_timer_.reset(new base::DelayTimer(
- FROM_HERE, base::TimeDelta::FromSeconds(kBatchModeTimeoutSeconds), this,
- &ImageDecoder::StopBatchMode));
- }
- batch_mode_timer_->Reset();
-
- mojom::ImageCodec mojo_codec = mojom::ImageCodec::DEFAULT;
-#if defined(OS_CHROMEOS)
- if (image_codec == ROBUST_JPEG_CODEC)
- mojo_codec = mojom::ImageCodec::ROBUST_JPEG;
- if (image_codec == ROBUST_PNG_CODEC)
- mojo_codec = mojom::ImageCodec::ROBUST_PNG;
-#endif // defined(OS_CHROMEOS)
- decoder_->DecodeImage(
- image_data, mojo_codec, shrink_to_fit,
- base::Bind(&OnDecodeImageDone,
- base::Bind(&ImageDecoder::OnDecodeImageFailed, this),
- base::Bind(&ImageDecoder::OnDecodeImageSucceeded, this),
- request_id));
+ g_decoder.Get().CancelImpl(image_request);
}
void ImageDecoder::CancelImpl(ImageRequest* image_request) {
@@ -193,104 +188,9 @@
}
}
-void ImageDecoder::StartBatchMode() {
- DCHECK_CURRENTLY_ON(BrowserThread::IO);
- utility_process_host_ =
- UtilityProcessHost::Create(
- this, base::ThreadTaskRunnerHandle::Get().get())->AsWeakPtr();
- utility_process_host_->SetName(l10n_util::GetStringUTF16(
- IDS_UTILITY_PROCESS_IMAGE_DECODER_NAME));
- if (!utility_process_host_->Start()) {
- delete utility_process_host_.get();
- return;
- }
- utility_process_host_->GetRemoteInterfaces()->GetInterface(&decoder_);
-}
-
-void ImageDecoder::StopBatchMode() {
- DCHECK_CURRENTLY_ON(BrowserThread::IO);
- {
- // Check for outstanding requests and wait for them to finish.
- base::AutoLock lock(map_lock_);
- if (!image_request_id_map_.empty()) {
- batch_mode_timer_->Reset();
- return;
- }
- }
-
- if (utility_process_host_) {
- // With Mojo, the utility process needs to be explicitly shut down by
- // deleting the host.
- delete utility_process_host_.get();
- decoder_.reset();
- utility_process_host_.reset();
- }
-}
-
-void ImageDecoder::FailAllRequests() {
- RequestMap requests;
- {
- base::AutoLock lock(map_lock_);
- requests = image_request_id_map_;
- }
-
- // Since |OnProcessCrashed| and |OnProcessLaunchFailed| are run asynchronously
- // from the actual event, it's possible for a new utility process to have been
- // created and sent requests by the time these functions are run. This results
- // in failing requests that are unaffected by the crash. Although not ideal,
- // this is valid and simpler than tracking which request is sent to which
- // utility process, and whether the request has been sent at all.
- for (const auto& request : requests)
- OnDecodeImageFailed(request.first);
-}
-
-void ImageDecoder::OnProcessCrashed(int exit_code) {
- DCHECK_CURRENTLY_ON(BrowserThread::IO);
- FailAllRequests();
-}
-
-void ImageDecoder::OnProcessLaunchFailed(int error_code) {
- DCHECK_CURRENTLY_ON(BrowserThread::IO);
- FailAllRequests();
-}
-
-bool ImageDecoder::OnMessageReceived(const IPC::Message& message) {
- return false;
-}
-
void ImageDecoder::OnDecodeImageSucceeded(
const SkBitmap& decoded_image,
int request_id) {
- DCHECK_CURRENTLY_ON(BrowserThread::IO);
- base::AutoLock lock(map_lock_);
- auto it = image_request_id_map_.find(request_id);
- if (it == image_request_id_map_.end())
- return;
-
- ImageRequest* image_request = it->second;
- image_request->task_runner()->PostTask(
- FROM_HERE,
- base::Bind(&ImageDecoder::RunOnImageDecoded,
- this,
- decoded_image,
- request_id));
-}
-
-void ImageDecoder::OnDecodeImageFailed(int request_id) {
- DCHECK_CURRENTLY_ON(BrowserThread::IO);
- base::AutoLock lock(map_lock_);
- auto it = image_request_id_map_.find(request_id);
- if (it == image_request_id_map_.end())
- return;
-
- ImageRequest* image_request = it->second;
- image_request->task_runner()->PostTask(
- FROM_HERE,
- base::Bind(&ImageDecoder::RunOnDecodeImageFailed, this, request_id));
-}
-
-void ImageDecoder::RunOnImageDecoded(const SkBitmap& decoded_image,
- int request_id) {
ImageRequest* image_request;
{
base::AutoLock lock(map_lock_);
@@ -305,7 +205,7 @@
image_request->OnImageDecoded(decoded_image);
}
-void ImageDecoder::RunOnDecodeImageFailed(int request_id) {
+void ImageDecoder::OnDecodeImageFailed(int request_id) {
ImageRequest* image_request;
{
base::AutoLock lock(map_lock_);
diff --git a/chrome/browser/image_decoder.h b/chrome/browser/image_decoder.h
index 2c8160d..d8bda97 100644
--- a/chrome/browser/image_decoder.h
+++ b/chrome/browser/image_decoder.h
@@ -6,35 +6,29 @@
#define CHROME_BROWSER_IMAGE_DECODER_H_
#include <map>
-#include <memory>
#include <string>
#include <vector>
-#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "base/synchronization/lock.h"
-#include "base/timer/timer.h"
-#include "build/build_config.h"
-#include "chrome/common/image_decoder.mojom.h"
-#include "content/public/browser/utility_process_host.h"
-#include "content/public/browser/utility_process_host_client.h"
class SkBitmap;
-// This is a helper class for decoding images safely in a utility process. To
+// This is a helper class for decoding images safely in a sandboxed service. To
// use this, call ImageDecoder::Start(...) or
// ImageDecoder::StartWithOptions(...) on any thread.
//
-// Internally, most of the work happens on the IO thread, and then
-// the callback (ImageRequest::OnImageDecoded or
-// ImageRequest::OnDecodeImageFailed) is posted back to the |task_runner_|
-// associated with the ImageRequest.
-// The Cancel() method runs on whichever thread called it. |map_lock_| is used
-// to protect the data that is accessed from multiple threads.
-class ImageDecoder : public content::UtilityProcessHostClient {
+// ImageRequest::OnImageDecoded or ImageRequest::OnDecodeImageFailed is posted
+// back to the |task_runner_| associated with the ImageRequest.
+//
+// The Cancel() method runs on whichever thread called it.
+//
+// TODO(rockot): Use of this class should be replaced with direct image_decoder
+// client library usage.
+class ImageDecoder {
public:
// ImageRequest objects needs to be created and destroyed on the same
// SequencedTaskRunner.
@@ -52,8 +46,9 @@
}
protected:
- // Creates an ImageRequest that runs on the thread creating it.
+ // Creates an ImageRequest that runs on the thread which created it.
ImageRequest();
+
// Explicitly pass in |task_runner| if the current thread is part of a
// thread pool.
explicit ImageRequest(
@@ -76,6 +71,9 @@
#endif // defined(OS_CHROMEOS)
};
+ ImageDecoder();
+ ~ImageDecoder();
+
// Calls StartWithOptions() with ImageCodec::DEFAULT_CODEC and
// shrink_to_fit = false.
static void Start(ImageRequest* image_request,
@@ -101,54 +99,19 @@
static void Cancel(ImageRequest* image_request);
private:
- friend struct base::DefaultLazyInstanceTraits<ImageDecoder>;
-
using RequestMap = std::map<int, ImageRequest*>;
- ImageDecoder();
- // It's a reference counted object, so destructor is private.
- ~ImageDecoder() override;
-
- // Sends a request to the sandboxed process to decode the image. Starts
- // batch mode if necessary. If the utility process fails to start,
- // an OnDecodeImageFailed task is posted to image_request's |task_runner_|.
- void DecodeImageInSandbox(int request_id,
- std::vector<uint8_t> image_data,
- ImageCodec image_codec,
- bool shrink_to_fit);
-
void StartWithOptionsImpl(ImageRequest* image_request,
std::vector<uint8_t> image_data,
ImageCodec image_codec,
bool shrink_to_fit);
+
void CancelImpl(ImageRequest* image_request);
- // Starts UtilityProcessHost in batch mode and starts |batch_mode_timer_|.
- // If the utility process fails to start, the method resets
- // |utility_process_host_| and returns.
- void StartBatchMode();
-
- // Stops batch mode if no requests have come in since
- // |kBatchModeTimeoutSeconds|.
- void StopBatchMode();
-
- // Fails all outstanding requests.
- void FailAllRequests();
-
- // Overidden from UtilityProcessHostClient.
- void OnProcessCrashed(int exit_code) override;
- void OnProcessLaunchFailed(int error_code) override;
- bool OnMessageReceived(const IPC::Message& message) override;
-
// IPC message handlers.
void OnDecodeImageSucceeded(const SkBitmap& decoded_image, int request_id);
void OnDecodeImageFailed(int request_id);
- // For the ImageRequest identified by |request_id|, call its OnImageDecoded()
- // or OnDecodeImageFailed() method on its task runner thread.
- void RunOnImageDecoded(const SkBitmap& decoded_image, int request_id);
- void RunOnDecodeImageFailed(int request_id);
-
// id to use for the next Start() request that comes in.
int image_request_id_counter_;
@@ -158,17 +121,6 @@
// Protects |image_request_id_map_| and |image_request_id_counter_|.
base::Lock map_lock_;
- // The UtilityProcessHost requests are sent to.
- base::WeakPtr<content::UtilityProcessHost> utility_process_host_;
-
- // Calls StopBatchMode() after |kBatchModeTimeoutSeconds| have elapsed,
- // unless a new decoding request resets the timer.
- std::unique_ptr<base::DelayTimer> batch_mode_timer_;
-
- // Mojo service connection. Must always be bound/reset and used on the IO
- // thread.
- mojom::ImageDecoderPtr decoder_;
-
DISALLOW_COPY_AND_ASSIGN(ImageDecoder);
};
diff --git a/chrome/browser/search/suggestions/image_decoder_impl.cc b/chrome/browser/search/suggestions/image_decoder_impl.cc
index 2b7543d..bb3094c 100644
--- a/chrome/browser/search/suggestions/image_decoder_impl.cc
+++ b/chrome/browser/search/suggestions/image_decoder_impl.cc
@@ -5,6 +5,7 @@
#include <algorithm>
#include <vector>
+#include "base/callback.h"
#include "chrome/browser/search/suggestions/image_decoder_impl.h"
#include "ui/gfx/image/image.h"
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index 8830d23..96ac6a7d 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -662,14 +662,12 @@
mojom("mojo_bindings") {
sources = [
- "image_decoder.mojom",
"network_diagnostics.mojom",
"resource_usage_reporter.mojom",
"shell_handler_win.mojom",
]
public_deps = [
- "//skia/public/interfaces",
"//url/mojo:url_mojom_gurl",
]
}
diff --git a/chrome/common/image_decoder.mojom b/chrome/common/image_decoder.mojom
deleted file mode 100644
index 3467afa..0000000
--- a/chrome/common/image_decoder.mojom
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2016 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 mojom;
-
-import "skia/public/interfaces/bitmap.mojom";
-
-enum ImageCodec {
- DEFAULT,
- ROBUST_JPEG,
- ROBUST_PNG,
-};
-
-interface ImageDecoder {
- DecodeImage(array<uint8> encoded_data, ImageCodec codec, bool shrink_to_fit)
- => (skia.mojom.Bitmap? decoded_image);
-};
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 4d6ee71d..bda31843 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3343,7 +3343,6 @@
"../test/base/v8_unit_test.cc",
"../test/base/v8_unit_test.h",
"../test/logging/win/mof_data_parser_unittest.cc",
- "../utility/image_decoder_impl_unittest.cc",
# Duplicate these tests here because PathService has more items in
# unit_tests than in base_unittests.
diff --git a/chrome/utility/BUILD.gn b/chrome/utility/BUILD.gn
index f4f8a8f..596dc18 100644
--- a/chrome/utility/BUILD.gn
+++ b/chrome/utility/BUILD.gn
@@ -17,8 +17,6 @@
"cloud_print/bitmap_image.h",
"cloud_print/pwg_encoder.cc",
"cloud_print/pwg_encoder.h",
- "image_decoder_impl.cc",
- "image_decoder_impl.h",
"ipc_shell_handler_win.cc",
"ipc_shell_handler_win.h",
"printing_handler.cc",
@@ -52,6 +50,8 @@
"//media",
"//net:net_with_v8",
"//printing/features",
+ "//services/image_decoder:lib",
+ "//services/image_decoder/public/cpp",
"//services/service_manager/public/cpp",
"//skia",
"//sql",
diff --git a/chrome/utility/DEPS b/chrome/utility/DEPS
index a43b1da..e3811b9 100644
--- a/chrome/utility/DEPS
+++ b/chrome/utility/DEPS
@@ -8,6 +8,7 @@
"+courgette",
"+extensions/common",
"+media",
+ "+services/image_decoder",
"+services/service_manager/public/cpp",
"+skia/ext",
"+third_party/libxml",
diff --git a/chrome/utility/chrome_content_utility_client.cc b/chrome/utility/chrome_content_utility_client.cc
index 0c2fc19..d653678 100644
--- a/chrome/utility/chrome_content_utility_client.cc
+++ b/chrome/utility/chrome_content_utility_client.cc
@@ -16,17 +16,19 @@
#include "chrome/common/safe_browsing/zip_analyzer.h"
#include "chrome/common/safe_browsing/zip_analyzer_results.h"
#include "chrome/utility/chrome_content_utility_ipc_whitelist.h"
-#include "chrome/utility/image_decoder_impl.h"
#include "chrome/utility/utility_message_handler.h"
#include "components/safe_json/utility/safe_json_parser_mojo_impl.h"
#include "content/public/child/image_decoder_utils.h"
#include "content/public/common/content_switches.h"
+#include "content/public/common/service_info.h"
#include "content/public/utility/utility_thread.h"
#include "courgette/courgette.h"
#include "courgette/third_party/bsdiff/bsdiff.h"
#include "ipc/ipc_channel.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "printing/features/features.h"
+#include "services/image_decoder/image_decoder_service.h"
+#include "services/image_decoder/public/cpp/constants.h"
#include "services/service_manager/public/cpp/interface_registry.h"
#include "third_party/zlib/google/zip.h"
#include "ui/gfx/geometry/size.h"
@@ -100,10 +102,9 @@
}
#endif // !defined(OS_ANDROID)
-void CreateImageDecoder(mojo::InterfaceRequest<mojom::ImageDecoder> request) {
+std::unique_ptr<service_manager::Service> CreateImageDecoderService() {
content::UtilityThread::Get()->EnsureBlinkInitialized();
- mojo::MakeStrongBinding(base::MakeUnique<ImageDecoderImpl>(),
- std::move(request));
+ return image_decoder::ImageDecoderService::Create();
}
} // namespace
@@ -206,7 +207,6 @@
base::Bind(CreateProxyResolverFactory));
registry->AddInterface(base::Bind(CreateResourceUsageReporter));
#endif
- registry->AddInterface(base::Bind(&CreateImageDecoder));
registry->AddInterface(
base::Bind(&safe_json::SafeJsonParserMojoImpl::Create));
#if defined(OS_WIN)
@@ -214,6 +214,13 @@
#endif
}
+void ChromeContentUtilityClient::RegisterServices(StaticServiceMap* services) {
+ content::ServiceInfo image_decoder_info;
+ image_decoder_info.factory = base::Bind(&CreateImageDecoderService);
+ services->insert(
+ std::make_pair(image_decoder::kServiceName, image_decoder_info));
+}
+
void ChromeContentUtilityClient::AddHandler(
std::unique_ptr<UtilityMessageHandler> handler) {
handlers_.push_back(std::move(handler));
diff --git a/chrome/utility/chrome_content_utility_client.h b/chrome/utility/chrome_content_utility_client.h
index 0f2f481f..c1c8eb7 100644
--- a/chrome/utility/chrome_content_utility_client.h
+++ b/chrome/utility/chrome_content_utility_client.h
@@ -35,6 +35,7 @@
bool OnMessageReceived(const IPC::Message& message) override;
void ExposeInterfacesToBrowser(
service_manager::InterfaceRegistry* registry) override;
+ void RegisterServices(StaticServiceMap* services) override;
void AddHandler(std::unique_ptr<UtilityMessageHandler> handler);
diff --git a/chrome/utility/image_decoder_impl.cc b/chrome/utility/image_decoder_impl.cc
deleted file mode 100644
index 472f987..0000000
--- a/chrome/utility/image_decoder_impl.cc
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2016 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 "chrome/utility/image_decoder_impl.h"
-
-#include <string.h>
-
-#include <utility>
-
-#include "base/logging.h"
-#include "content/public/child/image_decoder_utils.h"
-#include "ipc/ipc_channel.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
-#include "skia/ext/image_operations.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/gfx/geometry/size.h"
-
-#if defined(OS_CHROMEOS)
-#include "ui/gfx/chromeos/codec/jpeg_codec_robust_slow.h"
-#include "ui/gfx/codec/png_codec.h"
-#endif
-
-namespace {
-int64_t kMaxMessageSize = IPC::Channel::kMaximumMessageSize;
-int64_t kPadding = 64;
-}
-
-ImageDecoderImpl::ImageDecoderImpl() : ImageDecoderImpl(kMaxMessageSize) {}
-
-ImageDecoderImpl::ImageDecoderImpl(int64_t max_message_size)
- : max_message_size_(max_message_size) {}
-
-ImageDecoderImpl::~ImageDecoderImpl() {
-}
-
-void ImageDecoderImpl::DecodeImage(const std::vector<uint8_t>& encoded_data,
- mojom::ImageCodec codec,
- bool shrink_to_fit,
- const DecodeImageCallback& callback) {
- if (encoded_data.empty()) {
- callback.Run(SkBitmap());
- return;
- }
-
- SkBitmap decoded_image;
-#if defined(OS_CHROMEOS)
- if (codec == mojom::ImageCodec::ROBUST_JPEG) {
- // Our robust jpeg decoding is using IJG libjpeg.
- std::unique_ptr<SkBitmap> decoded_jpeg(gfx::JPEGCodecRobustSlow::Decode(
- encoded_data.data(), encoded_data.size()));
- if (decoded_jpeg.get() && !decoded_jpeg->empty())
- decoded_image = *decoded_jpeg;
- } else if (codec == mojom::ImageCodec::ROBUST_PNG) {
- // Our robust PNG decoding is using libpng.
- SkBitmap decoded_png;
- if (gfx::PNGCodec::Decode(encoded_data.data(), encoded_data.size(),
- &decoded_png)) {
- decoded_image = decoded_png;
- }
- }
-#endif // defined(OS_CHROMEOS)
- if (codec == mojom::ImageCodec::DEFAULT) {
- decoded_image = content::DecodeImage(encoded_data.data(), gfx::Size(),
- encoded_data.size());
- }
-
- if (!decoded_image.isNull()) {
- // When serialized, the space taken up by a skia::mojom::Bitmap excluding
- // the pixel data payload should be:
- // sizeof(skia::mojom::Bitmap::Data_) + pixel data array header (8 bytes)
- // Use a bigger number in case we need padding at the end.
- int64_t struct_size = sizeof(skia::mojom::Bitmap::Data_) + kPadding;
- int64_t image_size = decoded_image.computeSize64();
- int halves = 0;
- while (struct_size + (image_size >> 2 * halves) > max_message_size_)
- halves++;
- if (halves) {
- if (shrink_to_fit) {
- // If decoded image is too large for IPC message, shrink it by halves.
- // This prevents quality loss, and should never overshrink on displays
- // smaller than 3600x2400.
- // TODO (Issue 416916): Instead of shrinking, return via shared memory
- decoded_image = skia::ImageOperations::Resize(
- decoded_image, skia::ImageOperations::RESIZE_LANCZOS3,
- decoded_image.width() >> halves, decoded_image.height() >> halves);
- } else {
- decoded_image.reset();
- }
- }
- }
-
- callback.Run(decoded_image);
-}
diff --git a/chrome/utility/image_decoder_impl.h b/chrome/utility/image_decoder_impl.h
deleted file mode 100644
index 7f82054d..0000000
--- a/chrome/utility/image_decoder_impl.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2016 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 CHROME_UTILITY_IMAGE_DECODER_IMPL_H_
-#define CHROME_UTILITY_IMAGE_DECODER_IMPL_H_
-
-#include "base/macros.h"
-#include "chrome/common/image_decoder.mojom.h"
-
-class ImageDecoderImpl : public mojom::ImageDecoder {
- public:
- ImageDecoderImpl();
- explicit ImageDecoderImpl(int64_t max_message_size);
- ~ImageDecoderImpl() override;
-
- // Overridden from mojom::ImageDecoder:
- void DecodeImage(const std::vector<uint8_t>& encoded_data,
- mojom::ImageCodec codec,
- bool shrink_to_fit,
- const DecodeImageCallback& callback) override;
-
- private:
- int64_t max_message_size_;
-
- DISALLOW_COPY_AND_ASSIGN(ImageDecoderImpl);
-};
-
-#endif // CHROME_UTILITY_IMAGE_DECODER_IMPL_H_
diff --git a/chrome/utility/image_decoder_impl_unittest.cc b/chrome/utility/image_decoder_impl_unittest.cc
deleted file mode 100644
index ea22b05..0000000
--- a/chrome/utility/image_decoder_impl_unittest.cc
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2016 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 "chrome/utility/image_decoder_impl.h"
-
-#include <vector>
-
-#include "base/bind.h"
-#include "ipc/ipc_channel.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/gfx/codec/jpeg_codec.h"
-
-namespace mojom {
-
-namespace {
-
-bool CreateJPEGImage(int width,
- int height,
- SkColor color,
- std::vector<unsigned char>* output) {
- SkBitmap bitmap;
- bitmap.allocN32Pixels(width, height);
- bitmap.eraseColor(color);
-
- const int kQuality = 50;
- if (!gfx::JPEGCodec::Encode(
- static_cast<const unsigned char*>(bitmap.getPixels()),
- gfx::JPEGCodec::FORMAT_SkBitmap, width, height,
- static_cast<int>(bitmap.rowBytes()), kQuality, output)) {
- LOG(ERROR) << "Unable to encode " << width << "x" << height << " bitmap";
- return false;
- }
- return true;
-}
-
-class Request {
- public:
- explicit Request(ImageDecoderImpl* decoder) : decoder_(decoder) {}
-
- void DecodeImage(const std::vector<unsigned char>& image, bool shrink) {
- decoder_->DecodeImage(
- image, ImageCodec::DEFAULT, shrink,
- base::Bind(&Request::OnRequestDone, base::Unretained(this)));
- }
-
- const SkBitmap& bitmap() const { return bitmap_; }
-
- private:
- void OnRequestDone(const SkBitmap& result_image) { bitmap_ = result_image; }
-
- ImageDecoderImpl* decoder_;
- SkBitmap bitmap_;
-};
-
-} // namespace
-
-// Test that DecodeImage() doesn't return image message > (max message size)
-TEST(ImageDecoderImplTest, DecodeImageSizeLimit) {
- // Using actual limit generates 14000 x 9400 images, which causes the test to
- // timeout. We test with a smaller limit for efficiency.
- const size_t kTestMessageSize = IPC::Channel::kMaximumMessageSize / 1024;
-
- ImageDecoderImpl decoder(kTestMessageSize);
-
- // Approx max height for 3:2 image that will fit in IPC message;
- // 1.5 for width/height ratio, 4 for bytes/pixel
- int max_height_for_msg = sqrt(kTestMessageSize / (1.5 * 4));
- int base_msg_size = sizeof(skia::mojom::Bitmap::Data_);
-
- // Sizes which should trigger dimension-halving 0, 1 and 2 times
- int heights[] = {max_height_for_msg - 10,
- max_height_for_msg + 10,
- 2 * max_height_for_msg + 10};
- int widths[] = {heights[0] * 3 / 2, heights[1] * 3 / 2, heights[2] * 3 / 2};
- for (size_t i = 0; i < arraysize(heights); i++) {
- std::vector<unsigned char> jpg;
- ASSERT_TRUE(CreateJPEGImage(widths[i], heights[i], SK_ColorRED, &jpg));
-
- Request request(&decoder);
- request.DecodeImage(jpg, true);
- ASSERT_FALSE(request.bitmap().isNull());
-
- // Check that image has been shrunk appropriately
- EXPECT_LT(request.bitmap().computeSize64() + base_msg_size,
- static_cast<int64_t>(kTestMessageSize));
-// Android does its own image shrinking for memory conservation deeper in
-// the decode, so more specific tests here won't work.
-#if !defined(OS_ANDROID)
- EXPECT_EQ(widths[i] >> i, request.bitmap().width());
- EXPECT_EQ(heights[i] >> i, request.bitmap().height());
-
- // Check that if resize not requested and image exceeds IPC size limit,
- // an empty image is returned
- if (heights[i] > max_height_for_msg) {
- Request request(&decoder);
- request.DecodeImage(jpg, false);
- EXPECT_TRUE(request.bitmap().isNull());
- }
-#endif
- }
-}
-
-TEST(ImageDecoderImplTest, DecodeImageFailed) {
- ImageDecoderImpl decoder(IPC::Channel::kMaximumMessageSize);
-
- // The "jpeg" is just some "random" data;
- const char kRandomData[] = "u gycfy7xdjkhfgui bdui ";
- std::vector<unsigned char> jpg(kRandomData,
- kRandomData + sizeof(kRandomData));
-
- Request request(&decoder);
- request.DecodeImage(jpg, false);
- EXPECT_TRUE(request.bitmap().isNull());
-}
-
-} // namespace mojom
diff --git a/services/BUILD.gn b/services/BUILD.gn
new file mode 100644
index 0000000..5cb1983
--- /dev/null
+++ b/services/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2016 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.
+
+import("//testing/test.gni")
+
+# One Big Target for services to register their unit test sources. This exists
+# to avoid having to maintain a separate test binary for every service.
+#
+# To add tests for a new service, please define a "tests" source_set in the
+# service subdirectory and add it as a dependency here.
+test("service_unittests") {
+ deps = [
+ "//services/image_decoder:tests",
+ "//services/service_manager/public/cpp/test:run_all_service_tests",
+ ]
+}
diff --git a/services/OWNERS b/services/OWNERS
new file mode 100644
index 0000000..f5ec3de
--- /dev/null
+++ b/services/OWNERS
@@ -0,0 +1,3 @@
[email protected]
[email protected]
[email protected]
diff --git a/services/image_decoder/BUILD.gn b/services/image_decoder/BUILD.gn
new file mode 100644
index 0000000..7556c0c
--- /dev/null
+++ b/services/image_decoder/BUILD.gn
@@ -0,0 +1,59 @@
+# Copyright 2016 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.
+
+import("//services/service_manager/public/cpp/service.gni")
+import("//services/service_manager/public/service_manifest.gni")
+
+source_set("lib") {
+ sources = [
+ "image_decoder_impl.cc",
+ "image_decoder_impl.h",
+ "image_decoder_service.cc",
+ "image_decoder_service.h",
+ ]
+
+ deps = [
+ "//base",
+ "//content/public/child",
+ "//mojo/public/cpp/bindings",
+ "//skia",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+
+ public_deps = [
+ "//services/image_decoder/public/interfaces",
+ "//services/service_manager/public/cpp",
+ ]
+
+ data_deps = [
+ ":manifest",
+ ]
+}
+
+source_set("tests") {
+ testonly = true
+
+ sources = [
+ "image_decoder_impl_unittest.cc",
+ ]
+
+ deps = [
+ ":lib",
+ "//base",
+ "//gin",
+ "//gin:gin_test",
+ "//skia",
+ "//testing/gtest",
+ "//third_party/WebKit/public:blink",
+ "//ui/gfx",
+ ]
+
+ configs += [ "//v8:external_startup_data" ]
+}
+
+service_manifest("manifest") {
+ name = "image_decoder"
+ source = "manifest.json"
+}
diff --git a/services/image_decoder/DEPS b/services/image_decoder/DEPS
new file mode 100644
index 0000000..9dca8ee
--- /dev/null
+++ b/services/image_decoder/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ "+content/public/child",
+ "+gin",
+ "+skia",
+ "+third_party/WebKit/public",
+ "+third_party/skia",
+ "+ui/gfx",
+]
diff --git a/services/image_decoder/README.md b/services/image_decoder/README.md
new file mode 100644
index 0000000..8b983626
--- /dev/null
+++ b/services/image_decoder/README.md
@@ -0,0 +1,3 @@
+The image_decoder service exists to facilitate safe image decoding within an
+isolated sandboxed process.
+
diff --git a/services/image_decoder/image_decoder_impl.cc b/services/image_decoder/image_decoder_impl.cc
new file mode 100644
index 0000000..e82c497
--- /dev/null
+++ b/services/image_decoder/image_decoder_impl.cc
@@ -0,0 +1,103 @@
+// Copyright 2016 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 "services/image_decoder/image_decoder_impl.h"
+
+#include <string.h>
+
+#include <utility>
+
+#include "base/logging.h"
+#include "content/public/child/image_decoder_utils.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/geometry/size.h"
+
+#if defined(OS_CHROMEOS)
+#include "ui/gfx/chromeos/codec/jpeg_codec_robust_slow.h"
+#include "ui/gfx/codec/png_codec.h"
+#endif
+
+namespace image_decoder {
+
+namespace {
+
+int64_t kPadding = 64;
+
+} // namespace
+
+ImageDecoderImpl::ImageDecoderImpl(
+ std::unique_ptr<service_manager::ServiceContextRef> service_ref)
+ : service_ref_(std::move(service_ref)) {}
+
+ImageDecoderImpl::~ImageDecoderImpl() = default;
+
+void ImageDecoderImpl::DecodeImage(const std::vector<uint8_t>& encoded_data,
+ mojom::ImageCodec codec,
+ bool shrink_to_fit,
+ int64_t max_size_in_bytes,
+ const DecodeImageCallback& callback) {
+ if (encoded_data.size() == 0) {
+ callback.Run(SkBitmap());
+ return;
+ }
+
+ SkBitmap decoded_image;
+#if defined(OS_CHROMEOS)
+ if (codec == mojom::ImageCodec::ROBUST_JPEG) {
+ // Our robust jpeg decoding is using IJG libjpeg.
+ if (encoded_data.size()) {
+ std::unique_ptr<SkBitmap> decoded_jpeg(gfx::JPEGCodecRobustSlow::Decode(
+ encoded_data.data(), encoded_data.size()));
+ if (decoded_jpeg.get() && !decoded_jpeg->empty())
+ decoded_image = *decoded_jpeg;
+ }
+ } else if (codec == mojom::ImageCodec::ROBUST_PNG) {
+ // Our robust PNG decoding is using libpng.
+ if (encoded_data.size()) {
+ SkBitmap decoded_png;
+ if (gfx::PNGCodec::Decode(
+ encoded_data.data(), encoded_data.size(), &decoded_png)) {
+ decoded_image = decoded_png;
+ }
+ }
+ }
+#endif // defined(OS_CHROMEOS)
+ if (codec == mojom::ImageCodec::DEFAULT) {
+ decoded_image = content::DecodeImage(
+ encoded_data.data(), gfx::Size(), encoded_data.size());
+ }
+
+ if (!decoded_image.isNull()) {
+ // When serialized, the space taken up by a skia::mojom::Bitmap excluding
+ // the pixel data payload should be:
+ // sizeof(skia::mojom::Bitmap::Data_) + pixel data array header (8 bytes)
+ // Use a bigger number in case we need padding at the end.
+ int64_t struct_size = sizeof(skia::mojom::Bitmap::Data_) + kPadding;
+ int64_t image_size = decoded_image.computeSize64();
+ int halves = 0;
+ while (struct_size + (image_size >> 2 * halves) > max_size_in_bytes)
+ halves++;
+ if (halves) {
+ // If the decoded image is too large, either discard it or shrink it.
+ //
+ // TODO(rockot): Also support exposing the bytes via shared memory for
+ // larger images. https://crbug.com/416916.
+ if (shrink_to_fit) {
+ // Shrinking by halves prevents quality loss and should never overshrink
+ // on displays smaller than 3600x2400.
+ decoded_image = skia::ImageOperations::Resize(
+ decoded_image, skia::ImageOperations::RESIZE_LANCZOS3,
+ decoded_image.width() >> halves, decoded_image.height() >> halves);
+ } else {
+ decoded_image.reset();
+ }
+ }
+ }
+
+ callback.Run(decoded_image);
+}
+
+} // namespace image_decoder
diff --git a/services/image_decoder/image_decoder_impl.h b/services/image_decoder/image_decoder_impl.h
new file mode 100644
index 0000000..8d07486
--- /dev/null
+++ b/services/image_decoder/image_decoder_impl.h
@@ -0,0 +1,38 @@
+// Copyright 2016 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 CHROME_UTILITY_IMAGE_DECODER_IMPL_H_
+#define CHROME_UTILITY_IMAGE_DECODER_IMPL_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "services/image_decoder/public/interfaces/image_decoder.mojom.h"
+#include "services/service_manager/public/cpp/service_context_ref.h"
+
+namespace image_decoder {
+
+class ImageDecoderImpl : public mojom::ImageDecoder {
+ public:
+ explicit ImageDecoderImpl(
+ std::unique_ptr<service_manager::ServiceContextRef> service_ref);
+ ~ImageDecoderImpl() override;
+
+ // Overridden from mojom::ImageDecoder:
+ void DecodeImage(
+ const std::vector<uint8_t>& encoded_data,
+ mojom::ImageCodec codec,
+ bool shrink_to_fit,
+ int64_t max_size_in_bytes,
+ const DecodeImageCallback& callback) override;
+
+ private:
+ const std::unique_ptr<service_manager::ServiceContextRef> service_ref_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageDecoderImpl);
+};
+
+} // namespace image_decoder
+
+#endif // CHROME_UTILITY_IMAGE_DECODER_IMPL_H_
diff --git a/services/image_decoder/image_decoder_impl_unittest.cc b/services/image_decoder/image_decoder_impl_unittest.cc
new file mode 100644
index 0000000..82c7a00
--- /dev/null
+++ b/services/image_decoder/image_decoder_impl_unittest.cc
@@ -0,0 +1,161 @@
+// Copyright 2016 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 <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "gin/array_buffer.h"
+#include "gin/public/isolate_holder.h"
+#include "services/image_decoder/image_decoder_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/WebKit/public/platform/scheduler/utility/webthread_impl_for_utility_thread.h"
+#include "third_party/WebKit/public/web/WebKit.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+
+#if defined(V8_USE_EXTERNAL_STARTUP_DATA)
+#include "gin/v8_initializer.h"
+#endif
+
+namespace image_decoder {
+
+namespace {
+
+const int64_t kTestMaxImageSize = 128 * 1024;
+
+bool CreateJPEGImage(int width,
+ int height,
+ SkColor color,
+ std::vector<unsigned char>* output) {
+ SkBitmap bitmap;
+ bitmap.allocN32Pixels(width, height);
+ bitmap.eraseColor(color);
+
+ const int kQuality = 50;
+ if (!gfx::JPEGCodec::Encode(
+ static_cast<const unsigned char*>(bitmap.getPixels()),
+ gfx::JPEGCodec::FORMAT_SkBitmap, width, height,
+ static_cast<int>(bitmap.rowBytes()), kQuality, output)) {
+ LOG(ERROR) << "Unable to encode " << width << "x" << height << " bitmap";
+ return false;
+ }
+ return true;
+}
+
+class Request {
+ public:
+ explicit Request(ImageDecoderImpl* decoder) : decoder_(decoder) {}
+
+ void DecodeImage(const std::vector<unsigned char>& image, bool shrink) {
+ decoder_->DecodeImage(
+ image, mojom::ImageCodec::DEFAULT, shrink, kTestMaxImageSize,
+ base::Bind(&Request::OnRequestDone, base::Unretained(this)));
+ }
+
+ const SkBitmap& bitmap() const { return bitmap_; }
+
+ private:
+ void OnRequestDone(const SkBitmap& result_image) { bitmap_ = result_image; }
+
+ ImageDecoderImpl* decoder_;
+ SkBitmap bitmap_;
+};
+
+// We need to ensure that Blink and V8 are initialized in order to use content's
+// image decoding call.
+class BlinkInitializer : public blink::Platform {
+ public:
+ BlinkInitializer()
+ : main_thread_(new blink::scheduler::WebThreadImplForUtilityThread()) {
+#if defined(V8_USE_EXTERNAL_STARTUP_DATA)
+ gin::V8Initializer::LoadV8Snapshot();
+ gin::V8Initializer::LoadV8Natives();
+#endif
+
+ blink::initialize(this);
+ }
+
+ ~BlinkInitializer() override {}
+
+ private:
+ std::unique_ptr<blink::scheduler::WebThreadImplForUtilityThread> main_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlinkInitializer);
+};
+
+base::LazyInstance<BlinkInitializer>::Leaky g_blink_initializer =
+ LAZY_INSTANCE_INITIALIZER;
+
+class ImageDecoderImplTest : public testing::Test {
+ public:
+ ImageDecoderImplTest() : decoder_(nullptr) {}
+ ~ImageDecoderImplTest() override {}
+
+ void SetUp() override { g_blink_initializer.Get(); }
+
+ protected:
+ ImageDecoderImpl* decoder() { return &decoder_; }
+
+ private:
+ base::MessageLoop message_loop_;
+ ImageDecoderImpl decoder_;
+};
+
+} // namespace
+
+// Test that DecodeImage() doesn't return image message > (max message size)
+TEST_F(ImageDecoderImplTest, DecodeImageSizeLimit) {
+ // Approx max height for 3:2 image that will fit in the allotted space.
+ // 1.5 for width/height ratio, 4 for bytes/pixel.
+ int max_height_for_msg = sqrt(kTestMaxImageSize / (1.5 * 4));
+ int base_msg_size = sizeof(skia::mojom::Bitmap::Data_);
+
+ // Sizes which should trigger dimension-halving 0, 1 and 2 times
+ int heights[] = {max_height_for_msg - 10,
+ max_height_for_msg + 10,
+ 2 * max_height_for_msg + 10};
+ int widths[] = {heights[0] * 3 / 2, heights[1] * 3 / 2, heights[2] * 3 / 2};
+ for (size_t i = 0; i < arraysize(heights); i++) {
+ std::vector<unsigned char> jpg;
+ ASSERT_TRUE(CreateJPEGImage(widths[i], heights[i], SK_ColorRED, &jpg));
+
+ Request request(decoder());
+ request.DecodeImage(jpg, true);
+ ASSERT_FALSE(request.bitmap().isNull());
+
+ // Check that image has been shrunk appropriately
+ EXPECT_LT(request.bitmap().computeSize64() + base_msg_size,
+ kTestMaxImageSize);
+// Android does its own image shrinking for memory conservation deeper in
+// the decode, so more specific tests here won't work.
+#if !defined(OS_ANDROID)
+ EXPECT_EQ(widths[i] >> i, request.bitmap().width());
+ EXPECT_EQ(heights[i] >> i, request.bitmap().height());
+
+ // Check that if resize not requested and image exceeds IPC size limit,
+ // an empty image is returned
+ if (heights[i] > max_height_for_msg) {
+ Request request(decoder());
+ request.DecodeImage(jpg, false);
+ EXPECT_TRUE(request.bitmap().isNull());
+ }
+#endif
+ }
+}
+
+TEST_F(ImageDecoderImplTest, DecodeImageFailed) {
+ // The "jpeg" is just some "random" data;
+ const char kRandomData[] = "u gycfy7xdjkhfgui bdui ";
+ std::vector<unsigned char> jpg(kRandomData,
+ kRandomData + sizeof(kRandomData));
+
+ Request request(decoder());
+ request.DecodeImage(jpg, false);
+ EXPECT_TRUE(request.bitmap().isNull());
+}
+
+} // namespace image_decoder
diff --git a/services/image_decoder/image_decoder_service.cc b/services/image_decoder/image_decoder_service.cc
new file mode 100644
index 0000000..ddfbf93d
--- /dev/null
+++ b/services/image_decoder/image_decoder_service.cc
@@ -0,0 +1,66 @@
+// Copyright 2016 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 "services/image_decoder/image_decoder_service.h"
+
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "services/image_decoder/image_decoder_impl.h"
+#include "services/image_decoder/public/interfaces/image_decoder.mojom.h"
+#include "services/service_manager/public/cpp/interface_registry.h"
+#include "services/service_manager/public/cpp/service_context.h"
+
+namespace image_decoder {
+
+namespace {
+
+void OnConnectionLost(std::unique_ptr<service_manager::ServiceContextRef> ref) {
+ // No-op. This merely takes ownership of |ref| so it can be destroyed when
+ // this function is invoked.
+}
+
+void OnImageDecoderRequest(
+ service_manager::ServiceContextRefFactory* ref_factory,
+ mojom::ImageDecoderRequest request) {
+ mojo::MakeStrongBinding(
+ base::MakeUnique<ImageDecoderImpl>(ref_factory->CreateRef()),
+ std::move(request));
+}
+
+} // namespace
+
+ImageDecoderService::ImageDecoderService() = default;
+
+ImageDecoderService::~ImageDecoderService() = default;
+
+// static
+std::unique_ptr<service_manager::Service> ImageDecoderService::Create() {
+ return base::MakeUnique<ImageDecoderService>();
+}
+
+void ImageDecoderService::OnStart(service_manager::ServiceContext* context) {
+ ref_factory_.reset(new service_manager::ServiceContextRefFactory(
+ base::Bind(&service_manager::ServiceContext::RequestQuit,
+ base::Unretained(context))));
+}
+
+bool ImageDecoderService::OnConnect(
+ const service_manager::ServiceInfo& remote_info,
+ service_manager::InterfaceRegistry* registry) {
+ // Add a reference to the service and tie it to the lifetime of the
+ // InterfaceRegistry's connection.
+ std::unique_ptr<service_manager::ServiceContextRef> connection_ref =
+ ref_factory_->CreateRef();
+ registry->AddConnectionLostClosure(
+ base::Bind(&OnConnectionLost, base::Passed(&connection_ref)));
+ registry->AddInterface(
+ base::Bind(&OnImageDecoderRequest, ref_factory_.get()));
+ return true;
+}
+
+bool ImageDecoderService::OnStop() {
+ return true;
+}
+
+} // namespace image_decoder
diff --git a/services/image_decoder/image_decoder_service.h b/services/image_decoder/image_decoder_service.h
new file mode 100644
index 0000000..2f4643f
--- /dev/null
+++ b/services/image_decoder/image_decoder_service.h
@@ -0,0 +1,39 @@
+// Copyright 2016 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 SERVICES_IMAGE_DECODER_IMAGE_DECODER_SERVICE_H_
+#define SERVICES_IMAGE_DECODER_IMAGE_DECODER_SERVICE_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "services/service_manager/public/cpp/service.h"
+#include "services/service_manager/public/cpp/service_context_ref.h"
+
+namespace image_decoder {
+
+class ImageDecoderService : public service_manager::Service {
+ public:
+ ImageDecoderService();
+ ~ImageDecoderService() override;
+
+ // Factory function for use as an embedded service.
+ static std::unique_ptr<service_manager::Service> Create();
+
+ // service_manager::Service:
+ void OnStart(service_manager::ServiceContext* context) override;
+ bool OnConnect(const service_manager::ServiceInfo& remote_info,
+ service_manager::InterfaceRegistry* registry) override;
+ bool OnStop() override;
+
+ private:
+ std::unique_ptr<service_manager::ServiceContextRefFactory> ref_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageDecoderService);
+};
+
+} // namespace image_decoder
+
+#endif // SERVICES_IMAGE_DECODER_IMAGE_DECODER_SERVICE_H_
diff --git a/services/image_decoder/manifest.json b/services/image_decoder/manifest.json
new file mode 100644
index 0000000..69aaf730
--- /dev/null
+++ b/services/image_decoder/manifest.json
@@ -0,0 +1,12 @@
+{
+ "name": "service:image_decoder",
+ "display_name": "Image Decoder Service",
+ "interface_provider_specs": {
+ "service_manager:connector": {
+ "provides": {
+ "decode": [ "image_decoder::mojom::ImageDecoder" ]
+ },
+ "requires": {}
+ }
+ }
+}
diff --git a/services/image_decoder/public/cpp/BUILD.gn b/services/image_decoder/public/cpp/BUILD.gn
new file mode 100644
index 0000000..8bc897a
--- /dev/null
+++ b/services/image_decoder/public/cpp/BUILD.gn
@@ -0,0 +1,19 @@
+# Copyright 2016 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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+source_set("cpp") {
+ sources = [
+ "constants.cc",
+ "constants.h",
+ "decode.cc",
+ "decode.h",
+ ]
+
+ public_deps = [
+ "//services/image_decoder/public/interfaces",
+ "//services/service_manager/public/cpp",
+ ]
+}
diff --git a/services/image_decoder/public/cpp/constants.cc b/services/image_decoder/public/cpp/constants.cc
new file mode 100644
index 0000000..e87e47bb
--- /dev/null
+++ b/services/image_decoder/public/cpp/constants.cc
@@ -0,0 +1,11 @@
+// Copyright 2016 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 "services/image_decoder/public/cpp/constants.h"
+
+namespace image_decoder {
+
+const char kServiceName[] = "service:image_decoder";
+
+} // namespace image_decoder
diff --git a/services/image_decoder/public/cpp/constants.h b/services/image_decoder/public/cpp/constants.h
new file mode 100644
index 0000000..9c9029d
--- /dev/null
+++ b/services/image_decoder/public/cpp/constants.h
@@ -0,0 +1,14 @@
+// Copyright 2016 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 SERVICES_IMAGE_DECODER_PUBLIC_CPP_CONSTANTS_H_
+#define SERVICES_IMAGE_DECODER_PUBLIC_CPP_CONSTANTS_H_
+
+namespace image_decoder {
+
+extern const char kServiceName[];
+
+} // namespace image_decoder
+
+#endif // SERVICES_IMAGE_DECODER_PUBLIC_CPP_CONSTANTS_H_
diff --git a/services/image_decoder/public/cpp/decode.cc b/services/image_decoder/public/cpp/decode.cc
new file mode 100644
index 0000000..24dbd5bf
--- /dev/null
+++ b/services/image_decoder/public/cpp/decode.cc
@@ -0,0 +1,49 @@
+// Copyright 2016 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 "services/image_decoder/public/cpp/decode.h"
+
+#include "services/image_decoder/public/cpp/constants.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+namespace image_decoder {
+
+namespace {
+
+// Helper callback which owns an ImageDecoderPtr until invoked. This keeps the
+// ImageDecoder pipe open just long enough to dispatch a reply, at which point
+// the reply is forwarded to the wrapped |callback|.
+void OnDecodeImage(mojom::ImageDecoderPtr decoder,
+ const mojom::ImageDecoder::DecodeImageCallback& callback,
+ const SkBitmap& bitmap) {
+ callback.Run(bitmap);
+}
+
+// Called in the case of a connection error on an ImageDecoder proxy.
+void OnConnectionError(
+ const mojom::ImageDecoder::DecodeImageCallback& callback) {
+ SkBitmap null_bitmap;
+ callback.Run(null_bitmap);
+}
+
+} // namespace
+
+void Decode(service_manager::Connector* connector,
+ const std::vector<uint8_t>& encoded_bytes,
+ mojom::ImageCodec codec,
+ bool shrink_to_fit,
+ uint64_t max_size_in_bytes,
+ const mojom::ImageDecoder::DecodeImageCallback& callback) {
+ mojom::ImageDecoderPtr decoder;
+ connector->ConnectToInterface(kServiceName, &decoder);
+ decoder.set_connection_error_handler(
+ base::Bind(&OnConnectionError, callback));
+ mojom::ImageDecoder* raw_decoder = decoder.get();
+ raw_decoder->DecodeImage(
+ encoded_bytes, codec, shrink_to_fit, max_size_in_bytes,
+ base::Bind(&OnDecodeImage, base::Passed(&decoder), callback));
+}
+
+} // namespace image_decoder
diff --git a/services/image_decoder/public/cpp/decode.h b/services/image_decoder/public/cpp/decode.h
new file mode 100644
index 0000000..3996172
--- /dev/null
+++ b/services/image_decoder/public/cpp/decode.h
@@ -0,0 +1,35 @@
+// Copyright 2016 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 SERVICES_IMAGE_DECODER_PUBLIC_CPP_DECODE_H_
+#define SERVICES_IMAGE_DECODER_PUBLIC_CPP_DECODE_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "services/image_decoder/public/interfaces/image_decoder.mojom.h"
+
+namespace service_manager {
+class Connector;
+}
+
+namespace image_decoder {
+
+const uint64_t kDefaultMaxSizeInBytes = 128 * 1024 * 1024;
+
+// Helper function to decode an image via the image_decoder service. Upon
+// completion, |callback| is invoked on the calling thread TaskRunner with an
+// SkBitmap argument. The SkBitmap will be null on failure and non-null on
+// success.
+void Decode(service_manager::Connector* connector,
+ const std::vector<uint8_t>& encoded_bytes,
+ mojom::ImageCodec codec,
+ bool shrink_to_fit,
+ uint64_t max_size_in_bytes,
+ const mojom::ImageDecoder::DecodeImageCallback& callback);
+
+} // namespace image_decoder
+
+#endif // SERVICES_IMAGE_DECODER_PUBLIC_CPP_DECODE_H_
diff --git a/services/image_decoder/public/interfaces/BUILD.gn b/services/image_decoder/public/interfaces/BUILD.gn
new file mode 100644
index 0000000..18abeac5
--- /dev/null
+++ b/services/image_decoder/public/interfaces/BUILD.gn
@@ -0,0 +1,15 @@
+# Copyright 2016 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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("interfaces") {
+ sources = [
+ "image_decoder.mojom",
+ ]
+
+ public_deps = [
+ "//skia/public/interfaces",
+ ]
+}
diff --git a/services/image_decoder/public/interfaces/OWNERS b/services/image_decoder/public/interfaces/OWNERS
new file mode 100644
index 0000000..08850f4
--- /dev/null
+++ b/services/image_decoder/public/interfaces/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/services/image_decoder/public/interfaces/image_decoder.mojom b/services/image_decoder/public/interfaces/image_decoder.mojom
new file mode 100644
index 0000000..39a9acb
--- /dev/null
+++ b/services/image_decoder/public/interfaces/image_decoder.mojom
@@ -0,0 +1,28 @@
+// Copyright 2016 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_decoder.mojom;
+
+import "skia/public/interfaces/bitmap.mojom";
+
+enum ImageCodec {
+ DEFAULT,
+ ROBUST_JPEG,
+ ROBUST_PNG,
+};
+
+interface ImageDecoder {
+ // Decodes image data to a raw skia bitmap.
+ //
+ // If the total size of the decoded image data in bytes exceeds
+ // |max_size_in_bytes| and |shrink_to_fit| is true, the image is halved
+ // successively until its total size no longer exceeds |max_size_in_bytes|.
+ //
+ // If the total size of the decoded image data in bytes exceeds
+ // |max_size_in_bytes| and |shrink_to_fit| is false, this is treated as a
+ // decoding failure and the |decoded_image| response is null.
+ DecodeImage(array<uint8> encoded_data, ImageCodec codec, bool shrink_to_fit,
+ int64 max_size_in_bytes)
+ => (skia.mojom.Bitmap? decoded_image);
+};
diff --git a/services/service_manager/public/cpp/connector.h b/services/service_manager/public/cpp/connector.h
index 1b39e977..5251b26f 100644
--- a/services/service_manager/public/cpp/connector.h
+++ b/services/service_manager/public/cpp/connector.h
@@ -109,6 +109,9 @@
// The returned object may be passed multiple times until Connect() is called,
// at which point this method must be called again to pass again.
virtual std::unique_ptr<Connector> Clone() = 0;
+
+ // Binds a Connector request to the other end of this Connector.
+ virtual void BindRequest(mojom::ConnectorRequest request) = 0;
};
} // namespace service_manager
diff --git a/services/service_manager/public/cpp/lib/connector_impl.cc b/services/service_manager/public/cpp/lib/connector_impl.cc
index d4d4ec7..e704037 100644
--- a/services/service_manager/public/cpp/lib/connector_impl.cc
+++ b/services/service_manager/public/cpp/lib/connector_impl.cc
@@ -94,6 +94,12 @@
return base::MakeUnique<ConnectorImpl>(connector.PassInterface());
}
+void ConnectorImpl::BindRequest(mojom::ConnectorRequest request) {
+ if (!BindIfNecessary())
+ return;
+ connector_->Clone(std::move(request));
+}
+
bool ConnectorImpl::BindIfNecessary() {
// Bind this object to the current thread the first time it is used to
// connect.
diff --git a/services/service_manager/public/cpp/lib/connector_impl.h b/services/service_manager/public/cpp/lib/connector_impl.h
index 487a33b0..a07858c 100644
--- a/services/service_manager/public/cpp/lib/connector_impl.h
+++ b/services/service_manager/public/cpp/lib/connector_impl.h
@@ -27,6 +27,7 @@
std::unique_ptr<Connection> Connect(const std::string& name) override;
std::unique_ptr<Connection> Connect(ConnectParams* params) override;
std::unique_ptr<Connector> Clone() override;
+ void BindRequest(mojom::ConnectorRequest request) override;
bool BindIfNecessary();
diff --git a/services/service_manager/public/service_manifest.gni b/services/service_manager/public/service_manifest.gni
index 58e38d48..22fc38e 100644
--- a/services/service_manager/public/service_manifest.gni
+++ b/services/service_manager/public/service_manifest.gni
@@ -2,6 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//build/config/dcheck_always_on.gni")
import("//services/service_manager/public/constants.gni")
# Used to produce a Service Manifest for a Service.
@@ -104,6 +105,10 @@
"--output=$rebase_output",
]
+ if (is_debug || dcheck_always_on) {
+ args += [ "--pretty" ]
+ }
+
if (defined(invoker.overlays)) {
args += [ "--overlays" ]
foreach(overlay_path, invoker.overlays) {
@@ -112,6 +117,7 @@
}
if (defined(invoker.packaged_services)) {
+ args += [ "--packaged-services" ]
foreach(name, invoker.packaged_services) {
input = "$root_out_dir/$packages_directory/$name/manifest.json"
inputs += [ input ]
@@ -131,17 +137,134 @@
all_deps += invoker.deps
}
- group("${target_name}__is_service_manifest") {
+ group("${target_name}__is_service_manifest_or_overlay") {
}
# Explicitly ensure that all dependencies are service_manifest
- # targets themselves.
+ # or service_manifest_overlay targets themselves.
group("${target_name}__check_deps_are_all_service_manifest") {
deps = []
foreach(d, all_deps) {
name = get_label_info(d, "label_no_toolchain")
toolchain = get_label_info(d, "toolchain")
- deps += [ "${name}__is_service_manifest(${toolchain})" ]
+ deps += [ "${name}__is_service_manifest_or_overlay(${toolchain})" ]
+ }
+ }
+}
+
+# A simple derivative of the service_manifest template above. This allows for
+# embedding of packaged services into a manifest overlay.
+#
+# Parameters:
+#
+# source
+# The manifest overlay file to procss.
+#
+# overlays (optional)
+# A list of partial manifests to overlay onto the source manifest before
+# emitting the final output. Overlays are applied as the last step, after
+# any packaged manifests are embedded.
+#
+# output_name (optional)
+# The output name of the manifest. Defaults to the target name.
+#
+# deps (optional)
+# An array of the service_manifest targets this overlay depends on.
+#
+# packaged_services (optional)
+# An array of names of the services packaged into this overlay.
+#
+# Outputs:
+#
+# An instantiation of this template produces in
+# ${root_gen_dir}/${output_name}.json
+#
+template("service_manifest_overlay") {
+ assert(defined(invoker.source),
+ "\"source\" must be defined for the $target_name template")
+ if (defined(invoker.deps)) {
+ assert(defined(invoker.packaged_services) || defined(invoker.overlays),
+ "\"deps\" implies that you also want \"packaged_services\" and/or" +
+ "\"overlays\", but you have neither.")
+ }
+ if (defined(invoker.packaged_services)) {
+ assert(defined(invoker.deps),
+ "\"deps\" building the dependent packaged services must be " +
+ "provided.")
+ }
+ if (defined(invoker.type)) {
+ assert(invoker.type == "mojo" || invoker.type == "exe",
+ "\"type\" must be one of \"mojo\" or \"exe\".")
+ }
+
+ action(target_name) {
+ script =
+ "//services/service_manager/public/tools/manifest/manifest_collator.py"
+
+ inputs = [
+ invoker.source,
+ ]
+ if (defined(invoker.overlays)) {
+ inputs += invoker.overlays
+ }
+
+ output_name = target_name
+ if (defined(invoker.output_name)) {
+ output_name = invoker.output_name
+ }
+ output = "${root_gen_dir}/${output_name}.json"
+
+ outputs = [
+ output,
+ ]
+
+ rebase_parent = rebase_path(invoker.source, root_build_dir)
+ rebase_output = rebase_path(output, root_build_dir)
+
+ args = [
+ "--parent=$rebase_parent",
+ "--output=$rebase_output",
+ ]
+
+ if (is_debug || dcheck_always_on) {
+ args += [ "--pretty" ]
+ }
+
+ if (defined(invoker.overlays)) {
+ args += [ "--overlays" ]
+ foreach(overlay_path, invoker.overlays) {
+ args += [ rebase_path(overlay_path, root_build_dir) ]
+ }
+ }
+
+ if (defined(invoker.packaged_services)) {
+ args += [ "--packaged-services" ]
+ foreach(name, invoker.packaged_services) {
+ input = "$root_out_dir/$packages_directory/$name/manifest.json"
+ inputs += [ input ]
+ args += [ rebase_path(input, root_build_dir) ]
+ }
+ }
+
+ deps = []
+ data_deps = []
+ if (defined(invoker.deps)) {
+ deps += invoker.deps
+ data_deps += invoker.deps
+ }
+ }
+
+ group("${target_name}__is_service_manifest_or_overlay") {
+ }
+
+ # Explicitly ensure that all dependencies are service_manifest
+ # or service_manifest_overlay targets themselves.
+ group("${target_name}__check_deps_are_all_service_manifest") {
+ deps = []
+ foreach(d, all_deps) {
+ name = get_label_info(d, "label_no_toolchain")
+ toolchain = get_label_info(d, "toolchain")
+ deps += [ "${name}__is_service_manifest_or_overlay(${toolchain})" ]
}
}
}
diff --git a/services/service_manager/public/tools/manifest/manifest_collator.py b/services/service_manager/public/tools/manifest/manifest_collator.py
index 1d2d76e..3944d2f 100755
--- a/services/service_manager/public/tools/manifest/manifest_collator.py
+++ b/services/service_manager/public/tools/manifest/manifest_collator.py
@@ -72,8 +72,11 @@
parser.add_argument("--parent")
parser.add_argument("--output")
parser.add_argument("--name")
+ parser.add_argument("--pretty", action="store_true")
parser.add_argument("--overlays", nargs="+", dest="overlays", default=[])
- args, children = parser.parse_known_args()
+ parser.add_argument("--packaged-services", nargs="+",
+ dest="packaged_services", default=[])
+ args, _ = parser.parse_known_args()
parent = ParseJSONFile(args.parent)
@@ -82,23 +85,26 @@
raise ValueError("Service name path component '%s' must not start " \
"with //" % service_path)
- if args.name != service_path:
+ if args.name and args.name != service_path:
raise ValueError("Service name '%s' specified in build file does not " \
"match name '%s' specified in manifest." %
(args.name, service_path))
- services = []
- for child in children:
- services.append(ParseJSONFile(child))
+ packaged_services = []
+ for child_manifest in args.packaged_services:
+ packaged_services.append(ParseJSONFile(child_manifest))
- if len(services) > 0:
- parent['services'] = services
+ if len(packaged_services) > 0:
+ if 'services' not in parent:
+ parent['services'] = packaged_services
+ else:
+ parent['services'].extend(packaged_services)
for overlay_path in args.overlays:
MergeManifestOverlay(parent, ParseJSONFile(overlay_path))
with open(args.output, 'w') as output_file:
- json.dump(parent, output_file)
+ json.dump(parent, output_file, indent=2 if args.pretty else -1)
return 0