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);
+};