MediaStream Image Capture (3): Adding mojo and browser impl for dummy takePhoto().

This CL adds idl and mojo definitions of JS method
takePhoto(). It also adds the necessary browser side
files: context and implementation. finally, it plugs them
into RendererBlinkPlatformImpl.

(The idea is to land all the necessary files and
gyp/gn/DEPS changes without having to dig deep
into the functionality, since that would make the CL
intractable, and would ramify into
MediaStreamManager, VideoCaptureManager etc.)

A LayoutTest is also added for takePhoto().

BUG=518807

TEST= run demo html in
https://cdn.rawgit.com/Miguelao/demos/master/imagecapture.html
with flag --enable-blink-features=ImageCapture
(click on "Open Camera 320x240", then on
"Create Image Capturer" and finally, repeatedly,
on "takePhoto").

Review-Url: https://codereview.chromium.org/1926873002
Cr-Commit-Position: refs/heads/master@{#391163}
diff --git a/content/browser/DEPS b/content/browser/DEPS
index 3b979f4..16537a9 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -66,6 +66,7 @@
   "+third_party/WebKit/public/platform/WebScreenInfo.h",
   "+third_party/WebKit/public/platform/WebString.h",
   "+third_party/WebKit/public/platform/modules/geolocation/geolocation.mojom.h",
+  "+third_party/WebKit/public/platform/modules/imagecapture/image_capture.mojom.h",
   "+third_party/WebKit/public/platform/modules/indexeddb/WebIDBDatabaseException.h",
   "+third_party/WebKit/public/platform/modules/indexeddb/WebIDBTypes.h",
   "+third_party/WebKit/public/platform/modules/notifications/WebNotificationConstants.h",
diff --git a/content/browser/media/capture/image_capture_impl.cc b/content/browser/media/capture/image_capture_impl.cc
new file mode 100644
index 0000000..bfc8b0d0
--- /dev/null
+++ b/content/browser/media/capture/image_capture_impl.cc
@@ -0,0 +1,34 @@
+// 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 "content/browser/media/capture/image_capture_impl.h"
+
+#include "base/bind_helpers.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace content {
+
+// static
+void ImageCaptureImpl::Create(
+    mojo::InterfaceRequest<blink::mojom::ImageCapture> request) {
+  // |binding_| will take ownership of ImageCaptureImpl.
+  new ImageCaptureImpl(std::move(request));
+}
+
+ImageCaptureImpl::~ImageCaptureImpl() {}
+
+void ImageCaptureImpl::TakePhoto(const mojo::String& /* source_id */,
+                                 const TakePhotoCallback& callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  // TODO(mcasas): Implement this feature., https://crbug.com/518807.
+  mojo::Array<uint8_t> data(1);
+  callback.Run("text/plain", std::move(data));
+}
+
+ImageCaptureImpl::ImageCaptureImpl(
+    mojo::InterfaceRequest<blink::mojom::ImageCapture> request)
+    : binding_(this, std::move(request)) {}
+
+}  // namespace content
diff --git a/content/browser/media/capture/image_capture_impl.h b/content/browser/media/capture/image_capture_impl.h
new file mode 100644
index 0000000..eef6732
--- /dev/null
+++ b/content/browser/media/capture/image_capture_impl.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 CONTENT_BROWSER_MEDIA_CAPTURE_IMAGE_CAPTURE_IMPL_H_
+#define CONTENT_BROWSER_MEDIA_CAPTURE_IMAGE_CAPTURE_IMPL_H_
+
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/string.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "third_party/WebKit/public/platform/modules/imagecapture/image_capture.mojom.h"
+
+namespace content {
+
+class ImageCaptureImpl : public blink::mojom::ImageCapture {
+ public:
+  static void Create(
+      mojo::InterfaceRequest<blink::mojom::ImageCapture> request);
+  ~ImageCaptureImpl() override;
+
+  void TakePhoto(const mojo::String& source_id,
+                 const TakePhotoCallback& callback) override;
+
+ private:
+  explicit ImageCaptureImpl(
+      mojo::InterfaceRequest<blink::mojom::ImageCapture> request);
+
+  mojo::StrongBinding<blink::mojom::ImageCapture> binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(ImageCaptureImpl);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_MEDIA_CAPTURE_IMAGE_CAPTURE_IMPL_H_
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 29ec744d..1792b84 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -80,6 +80,7 @@
 #include "content/browser/loader/resource_message_filter.h"
 #include "content/browser/loader/resource_scheduler_filter.h"
 #include "content/browser/media/capture/audio_mirroring_manager.h"
+#include "content/browser/media/capture/image_capture_impl.h"
 #include "content/browser/media/media_internals.h"
 #include "content/browser/media/midi_host.h"
 #include "content/browser/memory/memory_message_filter.h"
@@ -1051,6 +1052,10 @@
       base::Bind(&PermissionServiceContext::CreateService,
                  base::Unretained(permission_service_context_.get())));
 
+  // TODO(mcasas): finalize arguments.
+  mojo_application_host_->service_registry()->AddService(
+      base::Bind(&ImageCaptureImpl::Create));
+
   mojo_application_host_->service_registry()->AddService(base::Bind(
       &BackgroundSyncContextImpl::CreateService,
       base::Unretained(storage_partition_impl_->GetBackgroundSyncContext())));
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index 9d475eb..9bebc3a 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -1068,6 +1068,8 @@
       'browser/media/capture/cursor_renderer_mac.mm',
       'browser/media/capture/desktop_capture_device_uma_types.cc',
       'browser/media/capture/desktop_capture_device_uma_types.h',
+      'browser/media/capture/image_capture_impl.cc',
+      'browser/media/capture/image_capture_impl.h',
       'browser/media/capture/web_contents_audio_input_stream.cc',
       'browser/media/capture/web_contents_audio_input_stream.h',
       'browser/media/capture/web_contents_audio_muter.cc',
diff --git a/third_party/WebKit/LayoutTests/fast/imagecapture/ImageCapture-takePhoto.html b/third_party/WebKit/LayoutTests/fast/imagecapture/ImageCapture-takePhoto.html
new file mode 100644
index 0000000..b8d4000
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/imagecapture/ImageCapture-takePhoto.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<script src=../../resources/testharness.js></script>
+<script src=../../resources/testharnessreport.js></script>
+<body>
+<canvas id='canvas0' width=10 height=10/>
+</body>
+<script>
+
+// This test verifies ImageCapture's takePhoto().
+
+var test = async_test(function() {
+  var canvas0 = document.getElementById('canvas0');
+  var context0 = canvas0.getContext("2d");
+  context0.fillStyle = "red";
+  context0.fillRect(0, 0, 10, 10);
+
+  var stream = canvas0.captureStream();
+
+  var capturer = new ImageCapture(stream.getVideoTracks()[0]);
+
+  capturer.takePhoto()
+    .then(blob => {
+      assert_true(blob.type.length > 0);
+      assert_true(blob.size > 0);
+      this.done();
+    })
+    .catch(error => {
+      assert_unreached('Error during takePhoto(): '+ error);
+    });
+}, 'exercises the ImageCapture API takePhoto().');
+
+</script>
diff --git a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
index 56811251..424c4d9 100644
--- a/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/WebKit/LayoutTests/webexposed/global-interface-listing-expected.txt
@@ -3384,6 +3384,7 @@
     getter videoStreamTrack
     method constructor
     method grabFrame
+    method takePhoto
 interface ImageData
     attribute @@toStringTag
     getter height
diff --git a/third_party/WebKit/Source/modules/imagecapture/ImageCapture.cpp b/third_party/WebKit/Source/modules/imagecapture/ImageCapture.cpp
index 4d55d835..874ca55 100644
--- a/third_party/WebKit/Source/modules/imagecapture/ImageCapture.cpp
+++ b/third_party/WebKit/Source/modules/imagecapture/ImageCapture.cpp
@@ -8,15 +8,31 @@
 #include "bindings/core/v8/ScriptPromiseResolver.h"
 #include "core/dom/DOMException.h"
 #include "core/dom/ExceptionCode.h"
+#include "core/fileapi/Blob.h"
 #include "core/frame/ImageBitmap.h"
 #include "modules/EventTargetModules.h"
 #include "modules/mediastream/MediaStreamTrack.h"
+#include "platform/mojo/MojoHelper.h"
 #include "public/platform/Platform.h"
+#include "public/platform/ServiceRegistry.h"
 #include "public/platform/WebImageCaptureFrameGrabber.h"
 #include "public/platform/WebMediaStreamTrack.h"
 
 namespace blink {
 
+namespace {
+
+const char kNoServiceError[] = "ImageCapture service unavailable.";
+
+bool trackIsInactive(const MediaStreamTrack& track)
+{
+    // Spec instructs to return an exception if the Track's readyState() is not
+    // "live". Also reject if the track is disabled or muted.
+    return track.readyState() != "live" || !track.enabled() || track.muted();
+}
+
+} // anonymous namespace
+
 ImageCapture* ImageCapture::create(ExecutionContext* context, MediaStreamTrack* track, ExceptionState& exceptionState)
 {
     if (track->kind() != "video") {
@@ -30,6 +46,9 @@
 ImageCapture::~ImageCapture()
 {
     DCHECK(!hasEventListeners());
+    // There should be no more outstanding |m_serviceRequests| at this point
+    // since each of them holds a persistent handle to this object.
+    DCHECK(m_serviceRequests.isEmpty());
 }
 
 const AtomicString& ImageCapture::interfaceName() const
@@ -50,26 +69,49 @@
 void ImageCapture::contextDestroyed()
 {
     removeAllEventListeners();
+    m_serviceRequests.clear();
     DCHECK(!hasEventListeners());
 }
 
+ScriptPromise ImageCapture::takePhoto(ScriptState* scriptState, ExceptionState& exceptionState)
+{
+
+    ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
+    ScriptPromise promise = resolver->promise();
+
+    if (trackIsInactive(*m_streamTrack)) {
+        resolver->reject(DOMException::create(InvalidStateError, "The associated Track is in an invalid state."));
+        return promise;
+    }
+
+    if (!m_service) {
+        resolver->reject(DOMException::create(NotFoundError, kNoServiceError));
+        return promise;
+    }
+
+    m_serviceRequests.add(resolver);
+
+    // m_streamTrack->component()->source()->id() is the renderer "name" of the camera;
+    // TODO(mcasas) consider sending the security origin as well:
+    // scriptState->getExecutionContext()->getSecurityOrigin()->toString()
+    m_service->TakePhoto(m_streamTrack->component()->source()->id(), createBaseCallback(bind<String, mojo::WTFArray<uint8_t>>(&ImageCapture::onTakePhoto, this, resolver)));
+    return promise;
+}
+
 ScriptPromise ImageCapture::grabFrame(ScriptState* scriptState, ExceptionState& exceptionState)
 {
     ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
     ScriptPromise promise = resolver->promise();
 
-    // Spec instructs to return an exception if the track's ready state is not "live". Also reject if the track is disabled or muted.
-    if (m_streamTrack->readyState() != "live" || !m_streamTrack->enabled() || m_streamTrack->muted()) {
+    if (trackIsInactive(*m_streamTrack)) {
         resolver->reject(DOMException::create(InvalidStateError, "The associated Track is in an invalid state."));
         return promise;
     }
 
     // Create |m_frameGrabber| the first time.
-    if (!m_frameGrabber) {
+    if (!m_frameGrabber)
         m_frameGrabber = adoptPtr(Platform::current()->createImageCaptureFrameGrabber());
 
-    }
-
     if (!m_frameGrabber) {
         resolver->reject(DOMException::create(UnknownError, "Couldn't create platform resources"));
         return promise;
@@ -88,6 +130,30 @@
     , m_streamTrack(track)
 {
     DCHECK(m_streamTrack);
+    DCHECK(!m_service.is_bound());
+
+    Platform::current()->serviceRegistry()->connectToRemoteService(mojo::GetProxy(&m_service));
+
+    m_service.set_connection_error_handler(createBaseCallback(bind(&ImageCapture::onServiceConnectionError, WeakPersistentThisPointer<ImageCapture>(this))));
+}
+
+void ImageCapture::onTakePhoto(ScriptPromiseResolver* resolver, const String& mimeType, mojo::WTFArray<uint8_t> data)
+{
+    if (!m_serviceRequests.contains(resolver))
+        return;
+
+    DCHECK(!data.is_null());
+    const auto& storage = data.storage();
+    resolver->resolve(Blob::create(storage.data(), storage.size(), mimeType));
+    m_serviceRequests.remove(resolver);
+}
+
+void ImageCapture::onServiceConnectionError()
+{
+    m_service.reset();
+    for (ScriptPromiseResolver* resolver : m_serviceRequests)
+        resolver->reject(DOMException::create(NotFoundError, kNoServiceError));
+    m_serviceRequests.clear();
 }
 
 bool ImageCapture::addEventListenerInternal(const AtomicString& eventType, EventListener* listener, const EventListenerOptions& options)
@@ -98,6 +164,7 @@
 DEFINE_TRACE(ImageCapture)
 {
     visitor->trace(m_streamTrack);
+    visitor->trace(m_serviceRequests);
     EventTargetWithInlineData::trace(visitor);
     ContextLifecycleObserver::trace(visitor);
 }
diff --git a/third_party/WebKit/Source/modules/imagecapture/ImageCapture.h b/third_party/WebKit/Source/modules/imagecapture/ImageCapture.h
index 16633e5b..a02f91d 100644
--- a/third_party/WebKit/Source/modules/imagecapture/ImageCapture.h
+++ b/third_party/WebKit/Source/modules/imagecapture/ImageCapture.h
@@ -12,6 +12,7 @@
 #include "modules/EventTargetModules.h"
 #include "modules/ModulesExport.h"
 #include "platform/AsyncMethodRunner.h"
+#include "public/platform/modules/imagecapture/image_capture.mojom-blink.h"
 
 namespace blink {
 
@@ -43,6 +44,8 @@
 
     MediaStreamTrack* videoStreamTrack() const { return m_streamTrack.get(); }
 
+    ScriptPromise takePhoto(ScriptState*, ExceptionState&);
+
     ScriptPromise grabFrame(ScriptState*, ExceptionState&);
 
     DECLARE_VIRTUAL_TRACE();
@@ -50,11 +53,17 @@
 private:
     ImageCapture(ExecutionContext*, MediaStreamTrack*);
 
+    void onTakePhoto(ScriptPromiseResolver*, const String& mimeType, mojo::WTFArray<uint8_t> data);
+    void onServiceConnectionError();
+
     // EventTarget implementation.
     bool addEventListenerInternal(const AtomicString& eventType, EventListener*, const EventListenerOptions&) override;
 
     Member<MediaStreamTrack> m_streamTrack;
     OwnPtr<WebImageCaptureFrameGrabber> m_frameGrabber;
+    mojom::blink::ImageCapturePtr m_service;
+
+    HeapHashSet<Member<ScriptPromiseResolver>> m_serviceRequests;
 };
 
 } // namespace blink
diff --git a/third_party/WebKit/Source/modules/imagecapture/ImageCapture.idl b/third_party/WebKit/Source/modules/imagecapture/ImageCapture.idl
index c5c76552..c8c306e 100644
--- a/third_party/WebKit/Source/modules/imagecapture/ImageCapture.idl
+++ b/third_party/WebKit/Source/modules/imagecapture/ImageCapture.idl
@@ -13,5 +13,6 @@
 ] interface ImageCapture {
     readonly attribute MediaStreamTrack videoStreamTrack;
 
+    [CallWith=ScriptState, RaisesException] Promise<Blob> takePhoto();
     [CallWith=ScriptState, RaisesException] Promise<ImageBitmap> grabFrame();
 };
diff --git a/third_party/WebKit/public/BUILD.gn b/third_party/WebKit/public/BUILD.gn
index a2533d9..d16eb8a7 100644
--- a/third_party/WebKit/public/BUILD.gn
+++ b/third_party/WebKit/public/BUILD.gn
@@ -183,6 +183,7 @@
   sources = [
     "platform/modules/bluetooth/web_bluetooth.mojom",
     "platform/modules/geolocation/geolocation.mojom",
+    "platform/modules/imagecapture/image_capture.mojom",
     "platform/modules/notifications/notification.mojom",
     "platform/modules/permissions/permission.mojom",
     "platform/modules/permissions/permission_status.mojom",
diff --git a/third_party/WebKit/public/blink.gyp b/third_party/WebKit/public/blink.gyp
index e8c15dc..b9c5dd8 100644
--- a/third_party/WebKit/public/blink.gyp
+++ b/third_party/WebKit/public/blink.gyp
@@ -35,6 +35,7 @@
         'blink_mojo_sources': [
             'platform/modules/bluetooth/web_bluetooth.mojom',
             'platform/modules/geolocation/geolocation.mojom',
+            'platform/modules/imagecapture/image_capture.mojom',
             'platform/modules/notifications/notification.mojom',
             'platform/modules/permissions/permission.mojom',
             'platform/modules/permissions/permission_status.mojom',
diff --git a/third_party/WebKit/public/platform/modules/imagecapture/OWNERS b/third_party/WebKit/public/platform/modules/imagecapture/OWNERS
new file mode 100644
index 0000000..9e621796
--- /dev/null
+++ b/third_party/WebKit/public/platform/modules/imagecapture/OWNERS
@@ -0,0 +1,13 @@
+# Changes to Mojo interfaces require a security review to avoid
+# introducing new sandbox escapes.
+per-file *.mojom=set noparent
+per-file *.mojom=dcheng@chromium.org
+per-file *.mojom=inferno@chromium.org
+per-file *.mojom=jln@chromium.org
+per-file *.mojom=jschuh@chromium.org
+per-file *.mojom=kenrb@chromium.org
+per-file *.mojom=mkwst@chromium.org
+per-file *.mojom=nasko@chromium.org
+per-file *.mojom=palmer@chromium.org
+per-file *.mojom=tsepez@chromium.org
+per-file *.mojom=wfh@chromium.org
diff --git a/third_party/WebKit/public/platform/modules/imagecapture/image_capture.mojom b/third_party/WebKit/public/platform/modules/imagecapture/image_capture.mojom
new file mode 100644
index 0000000..11c909f
--- /dev/null
+++ b/third_party/WebKit/public/platform/modules/imagecapture/image_capture.mojom
@@ -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.
+
+module blink.mojom;
+
+interface ImageCapture
+{
+    TakePhoto(string sourceid)
+        => (string mime_type, array<uint8> data);
+};