1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/modules/imagecapture/image_capture.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "mojo/public/cpp/bindings/pending_remote.h"
11 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
12 #include "third_party/blink/public/mojom/permissions/permission_status.mojom-blink.h"
13 #include "third_party/blink/public/platform/platform.h"
14 #include "third_party/blink/renderer/bindings/core/v8/callback_promise_adapter.h"
15 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
16 #include "third_party/blink/renderer/bindings/modules/v8/v8_fill_light_mode.h"
17 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_settings_range.h"
18 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_track_capabilities.h"
19 #include "third_party/blink/renderer/bindings/modules/v8/v8_media_track_constraints.h"
20 #include "third_party/blink/renderer/bindings/modules/v8/v8_photo_capabilities.h"
21 #include "third_party/blink/renderer/core/dom/dom_exception.h"
22 #include "third_party/blink/renderer/core/fileapi/blob.h"
23 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
24 #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
25 #include "third_party/blink/renderer/modules/event_target_modules.h"
26 #include "third_party/blink/renderer/modules/imagecapture/image_capture_frame_grabber.h"
27 #include "third_party/blink/renderer/modules/mediastream/media_stream_track.h"
28 #include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
29 #include "third_party/blink/renderer/modules/permissions/permission_utils.h"
30 #include "third_party/blink/renderer/platform/heap/heap.h"
31 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
32 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
33 #include "third_party/blink/renderer/platform/mojo/mojo_helper.h"
34 #include "third_party/blink/renderer/platform/wtf/functional.h"
35 
36 namespace blink {
37 
38 using FillLightMode = media::mojom::blink::FillLightMode;
39 using MeteringMode = media::mojom::blink::MeteringMode;
40 using RedEyeReduction = media::mojom::blink::RedEyeReduction;
41 
42 namespace {
43 
44 const char kNoServiceError[] = "ImageCapture service unavailable.";
45 
TrackIsInactive(const MediaStreamTrack & track)46 bool TrackIsInactive(const MediaStreamTrack& track) {
47   // Spec instructs to return an exception if the Track's readyState() is not
48   // "live". Also reject if the track is disabled or muted.
49   return track.readyState() != "live" || !track.enabled() || track.muted();
50 }
51 
ParseMeteringMode(const String & blink_mode)52 MeteringMode ParseMeteringMode(const String& blink_mode) {
53   if (blink_mode == "manual")
54     return MeteringMode::MANUAL;
55   if (blink_mode == "single-shot")
56     return MeteringMode::SINGLE_SHOT;
57   if (blink_mode == "continuous")
58     return MeteringMode::CONTINUOUS;
59   if (blink_mode == "none")
60     return MeteringMode::NONE;
61   NOTREACHED();
62   return MeteringMode::NONE;
63 }
64 
ParseFillLightMode(const String & blink_mode)65 FillLightMode ParseFillLightMode(const String& blink_mode) {
66   if (blink_mode == "off")
67     return FillLightMode::OFF;
68   if (blink_mode == "auto")
69     return FillLightMode::AUTO;
70   if (blink_mode == "flash")
71     return FillLightMode::FLASH;
72   NOTREACHED();
73   return FillLightMode::OFF;
74 }
75 
ToString(MeteringMode value)76 WebString ToString(MeteringMode value) {
77   switch (value) {
78     case MeteringMode::NONE:
79       return WebString::FromUTF8("none");
80     case MeteringMode::MANUAL:
81       return WebString::FromUTF8("manual");
82     case MeteringMode::SINGLE_SHOT:
83       return WebString::FromUTF8("single-shot");
84     case MeteringMode::CONTINUOUS:
85       return WebString::FromUTF8("continuous");
86   }
87 }
88 
89 #ifdef USE_BLINK_V8_BINDING_NEW_IDL_DICTIONARY
ToV8FillLightMode(FillLightMode value)90 V8FillLightMode ToV8FillLightMode(FillLightMode value) {
91   switch (value) {
92     case FillLightMode::OFF:
93       return V8FillLightMode(V8FillLightMode::Enum::kOff);
94     case FillLightMode::AUTO:
95       return V8FillLightMode(V8FillLightMode::Enum::kAuto);
96     case FillLightMode::FLASH:
97       return V8FillLightMode(V8FillLightMode::Enum::kFlash);
98   }
99 }
100 #else
ToV8FillLightMode(FillLightMode value)101 String ToV8FillLightMode(FillLightMode value) {
102   switch (value) {
103     case FillLightMode::OFF:
104       return String::FromUTF8("off");
105     case FillLightMode::AUTO:
106       return String::FromUTF8("auto");
107     case FillLightMode::FLASH:
108       return String::FromUTF8("flash");
109   }
110 }
111 #endif
112 
ToString(RedEyeReduction value)113 WebString ToString(RedEyeReduction value) {
114   switch (value) {
115     case RedEyeReduction::NEVER:
116       return WebString::FromUTF8("never");
117     case RedEyeReduction::ALWAYS:
118       return WebString::FromUTF8("always");
119     case RedEyeReduction::CONTROLLABLE:
120       return WebString::FromUTF8("controllable");
121   }
122 }
123 
ToMediaSettingsRange(const media::mojom::blink::Range & range)124 MediaSettingsRange* ToMediaSettingsRange(
125     const media::mojom::blink::Range& range) {
126   MediaSettingsRange* result = MediaSettingsRange::Create();
127   result->setMax(range.max);
128   result->setMin(range.min);
129   result->setStep(range.step);
130   return result;
131 }
132 
133 }  // anonymous namespace
134 
Create(ExecutionContext * context,MediaStreamTrack * track,ExceptionState & exception_state)135 ImageCapture* ImageCapture::Create(ExecutionContext* context,
136                                    MediaStreamTrack* track,
137                                    ExceptionState& exception_state) {
138   if (track->kind() != "video") {
139     exception_state.ThrowDOMException(
140         DOMExceptionCode::kNotSupportedError,
141         "Cannot create an ImageCapturer from a non-video Track.");
142     return nullptr;
143   }
144 
145   // The initial PTZ permission comes from the internal ImageCapture object of
146   // the track, if already created.
147   bool pan_tilt_zoom_allowed =
148       (track->GetImageCapture() &&
149        track->GetImageCapture()->HasPanTiltZoomPermissionGranted());
150 
151   return MakeGarbageCollected<ImageCapture>(
152       context, track, pan_tilt_zoom_allowed, base::DoNothing());
153 }
154 
~ImageCapture()155 ImageCapture::~ImageCapture() {
156   DCHECK(!HasEventListeners());
157   // There should be no more outstanding |m_serviceRequests| at this point
158   // since each of them holds a persistent handle to this object.
159   DCHECK(service_requests_.IsEmpty());
160 }
161 
InterfaceName() const162 const AtomicString& ImageCapture::InterfaceName() const {
163   return event_target_names::kImageCapture;
164 }
165 
GetExecutionContext() const166 ExecutionContext* ImageCapture::GetExecutionContext() const {
167   return ExecutionContextLifecycleObserver::GetExecutionContext();
168 }
169 
HasPendingActivity() const170 bool ImageCapture::HasPendingActivity() const {
171   return GetExecutionContext() && HasEventListeners();
172 }
173 
ContextDestroyed()174 void ImageCapture::ContextDestroyed() {
175   RemoveAllEventListeners();
176   service_requests_.clear();
177   DCHECK(!HasEventListeners());
178 }
179 
getPhotoCapabilities(ScriptState * script_state)180 ScriptPromise ImageCapture::getPhotoCapabilities(ScriptState* script_state) {
181   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
182   ScriptPromise promise = resolver->Promise();
183 
184   if (TrackIsInactive(*stream_track_)) {
185     resolver->Reject(MakeGarbageCollected<DOMException>(
186         DOMExceptionCode::kInvalidStateError,
187         "The associated Track is in an invalid state."));
188     return promise;
189   }
190 
191   if (!service_.is_bound()) {
192     resolver->Reject(MakeGarbageCollected<DOMException>(
193         DOMExceptionCode::kNotFoundError, kNoServiceError));
194     return promise;
195   }
196   service_requests_.insert(resolver);
197 
198   auto resolver_cb = WTF::Bind(&ImageCapture::ResolveWithPhotoCapabilities,
199                                WrapPersistent(this));
200 
201   // m_streamTrack->component()->source()->id() is the renderer "name" of the
202   // camera;
203   // TODO(mcasas) consider sending the security origin as well:
204   // scriptState->getExecutionContext()->getSecurityOrigin()->toString()
205   service_->GetPhotoState(
206       stream_track_->Component()->Source()->Id(),
207       WTF::Bind(&ImageCapture::OnMojoGetPhotoState, WrapPersistent(this),
208                 WrapPersistent(resolver), WTF::Passed(std::move(resolver_cb)),
209                 false /* trigger_take_photo */));
210   return promise;
211 }
212 
getPhotoSettings(ScriptState * script_state)213 ScriptPromise ImageCapture::getPhotoSettings(ScriptState* script_state) {
214   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
215   ScriptPromise promise = resolver->Promise();
216 
217   if (TrackIsInactive(*stream_track_)) {
218     resolver->Reject(MakeGarbageCollected<DOMException>(
219         DOMExceptionCode::kInvalidStateError,
220         "The associated Track is in an invalid state."));
221     return promise;
222   }
223 
224   if (!service_.is_bound()) {
225     resolver->Reject(MakeGarbageCollected<DOMException>(
226         DOMExceptionCode::kNotFoundError, kNoServiceError));
227     return promise;
228   }
229   service_requests_.insert(resolver);
230 
231   auto resolver_cb =
232       WTF::Bind(&ImageCapture::ResolveWithPhotoSettings, WrapPersistent(this));
233 
234   // m_streamTrack->component()->source()->id() is the renderer "name" of the
235   // camera;
236   // TODO(mcasas) consider sending the security origin as well:
237   // scriptState->getExecutionContext()->getSecurityOrigin()->toString()
238   service_->GetPhotoState(
239       stream_track_->Component()->Source()->Id(),
240       WTF::Bind(&ImageCapture::OnMojoGetPhotoState, WrapPersistent(this),
241                 WrapPersistent(resolver), WTF::Passed(std::move(resolver_cb)),
242                 false /* trigger_take_photo */));
243   return promise;
244 }
245 
setOptions(ScriptState * script_state,const PhotoSettings * photo_settings,bool trigger_take_photo)246 ScriptPromise ImageCapture::setOptions(ScriptState* script_state,
247                                        const PhotoSettings* photo_settings,
248                                        bool trigger_take_photo /* = false */) {
249   TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
250                        "ImageCapture::setOptions", TRACE_EVENT_SCOPE_PROCESS);
251   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
252   ScriptPromise promise = resolver->Promise();
253 
254   if (TrackIsInactive(*stream_track_)) {
255     resolver->Reject(MakeGarbageCollected<DOMException>(
256         DOMExceptionCode::kInvalidStateError,
257         "The associated Track is in an invalid state."));
258     return promise;
259   }
260 
261   if (!service_.is_bound()) {
262     resolver->Reject(MakeGarbageCollected<DOMException>(
263         DOMExceptionCode::kNotFoundError, kNoServiceError));
264     return promise;
265   }
266   service_requests_.insert(resolver);
267 
268   // TODO(mcasas): should be using a mojo::StructTraits instead.
269   auto settings = media::mojom::blink::PhotoSettings::New();
270 
271   settings->has_height = photo_settings->hasImageHeight();
272   if (settings->has_height) {
273     const double height = photo_settings->imageHeight();
274     if (photo_capabilities_ &&
275         (height < photo_capabilities_->imageHeight()->min() ||
276          height > photo_capabilities_->imageHeight()->max())) {
277       resolver->Reject(MakeGarbageCollected<DOMException>(
278           DOMExceptionCode::kNotSupportedError,
279           "imageHeight setting out of range"));
280       return promise;
281     }
282     settings->height = height;
283   }
284   settings->has_width = photo_settings->hasImageWidth();
285   if (settings->has_width) {
286     const double width = photo_settings->imageWidth();
287     if (photo_capabilities_ &&
288         (width < photo_capabilities_->imageWidth()->min() ||
289          width > photo_capabilities_->imageWidth()->max())) {
290       resolver->Reject(MakeGarbageCollected<DOMException>(
291           DOMExceptionCode::kNotSupportedError,
292           "imageWidth setting out of range"));
293       return promise;
294     }
295     settings->width = width;
296   }
297 
298   settings->has_red_eye_reduction = photo_settings->hasRedEyeReduction();
299   if (settings->has_red_eye_reduction) {
300     if (photo_capabilities_ &&
301         photo_capabilities_->redEyeReduction() != "controllable") {
302       resolver->Reject(MakeGarbageCollected<DOMException>(
303           DOMExceptionCode::kNotSupportedError,
304           "redEyeReduction is not controllable."));
305       return promise;
306     }
307     settings->red_eye_reduction = photo_settings->redEyeReduction();
308   }
309 
310   settings->has_fill_light_mode = photo_settings->hasFillLightMode();
311   if (settings->has_fill_light_mode) {
312     const String fill_light_mode = photo_settings->fillLightMode();
313     if (photo_capabilities_ && photo_capabilities_->fillLightMode().Find(
314                                    fill_light_mode) == kNotFound) {
315       resolver->Reject(MakeGarbageCollected<DOMException>(
316           DOMExceptionCode::kNotSupportedError, "Unsupported fillLightMode"));
317       return promise;
318     }
319     settings->fill_light_mode = ParseFillLightMode(fill_light_mode);
320   }
321 
322   service_->SetOptions(
323       stream_track_->Component()->Source()->Id(), std::move(settings),
324       WTF::Bind(&ImageCapture::OnMojoSetOptions, WrapPersistent(this),
325                 WrapPersistent(resolver), trigger_take_photo));
326   return promise;
327 }
328 
takePhoto(ScriptState * script_state,const PhotoSettings * photo_settings)329 ScriptPromise ImageCapture::takePhoto(ScriptState* script_state,
330                                       const PhotoSettings* photo_settings) {
331   TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
332                        "ImageCapture::takePhoto (with settings)",
333                        TRACE_EVENT_SCOPE_PROCESS);
334 
335   return setOptions(script_state, photo_settings,
336                     true /* trigger_take_photo */);
337 }
338 
grabFrame(ScriptState * script_state)339 ScriptPromise ImageCapture::grabFrame(ScriptState* script_state) {
340   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
341   ScriptPromise promise = resolver->Promise();
342 
343   if (TrackIsInactive(*stream_track_)) {
344     resolver->Reject(MakeGarbageCollected<DOMException>(
345         DOMExceptionCode::kInvalidStateError,
346         "The associated Track is in an invalid state."));
347     return promise;
348   }
349 
350   // Create |m_frameGrabber| the first time.
351   if (!frame_grabber_) {
352     frame_grabber_ = std::make_unique<ImageCaptureFrameGrabber>();
353   }
354 
355   if (!frame_grabber_) {
356     resolver->Reject(MakeGarbageCollected<DOMException>(
357         DOMExceptionCode::kUnknownError, "Couldn't create platform resources"));
358     return promise;
359   }
360 
361   auto resolver_callback_adapter =
362       std::make_unique<CallbackPromiseAdapter<ImageBitmap, void>>(resolver);
363   frame_grabber_->GrabFrame(stream_track_->Component(),
364                             std::move(resolver_callback_adapter),
365                             ExecutionContext::From(script_state)
366                                 ->GetTaskRunner(TaskType::kDOMManipulation));
367 
368   return promise;
369 }
370 
GetMediaTrackCapabilities(MediaTrackCapabilities * capabilities) const371 void ImageCapture::GetMediaTrackCapabilities(
372     MediaTrackCapabilities* capabilities) const {
373   // Merge any present |capabilities_| members into |capabilities|.
374 
375   if (capabilities_->hasWhiteBalanceMode())
376     capabilities->setWhiteBalanceMode(capabilities_->whiteBalanceMode());
377   if (capabilities_->hasExposureMode())
378     capabilities->setExposureMode(capabilities_->exposureMode());
379   if (capabilities_->hasFocusMode())
380     capabilities->setFocusMode(capabilities_->focusMode());
381   if (capabilities_->hasExposureCompensation()) {
382     capabilities->setExposureCompensation(
383         capabilities_->exposureCompensation());
384   }
385   if (capabilities_->hasExposureTime())
386     capabilities->setExposureTime(capabilities_->exposureTime());
387 
388   if (capabilities_->hasColorTemperature())
389     capabilities->setColorTemperature(capabilities_->colorTemperature());
390   if (capabilities_->hasIso())
391     capabilities->setIso(capabilities_->iso());
392 
393   if (capabilities_->hasBrightness())
394     capabilities->setBrightness(capabilities_->brightness());
395   if (capabilities_->hasContrast())
396     capabilities->setContrast(capabilities_->contrast());
397   if (capabilities_->hasSaturation())
398     capabilities->setSaturation(capabilities_->saturation());
399   if (capabilities_->hasSharpness())
400     capabilities->setSharpness(capabilities_->sharpness());
401 
402   if (capabilities_->hasFocusDistance())
403     capabilities->setFocusDistance(capabilities_->focusDistance());
404 
405   if (HasPanTiltZoomPermissionGranted()) {
406     if (capabilities_->hasPan())
407       capabilities->setPan(capabilities_->pan());
408     if (capabilities_->hasTilt())
409       capabilities->setTilt(capabilities_->tilt());
410   }
411   if (capabilities_->hasZoom() && HasZoomPermissionGranted()) {
412     capabilities->setZoom(capabilities_->zoom());
413   }
414 
415   if (capabilities_->hasTorch())
416     capabilities->setTorch(capabilities_->torch());
417 }
418 
419 // TODO(mcasas): make the implementation fully Spec compliant, see the TODOs
420 // inside the method, https://crbug.com/708723.
SetMediaTrackConstraints(ScriptPromiseResolver * resolver,const HeapVector<Member<MediaTrackConstraintSet>> & constraints_vector)421 void ImageCapture::SetMediaTrackConstraints(
422     ScriptPromiseResolver* resolver,
423     const HeapVector<Member<MediaTrackConstraintSet>>& constraints_vector) {
424   DCHECK_GT(constraints_vector.size(), 0u);
425   // TODO(mcasas): add support more than one single advanced constraint.
426   const MediaTrackConstraintSet* constraints = constraints_vector[0];
427 
428   ExecutionContext* context = GetExecutionContext();
429   if (constraints->hasWhiteBalanceMode())
430     UseCounter::Count(context, WebFeature::kImageCaptureWhiteBalanceMode);
431   if (constraints->hasExposureMode())
432     UseCounter::Count(context, WebFeature::kImageCaptureExposureMode);
433   if (constraints->hasFocusMode())
434     UseCounter::Count(context, WebFeature::kImageCaptureFocusMode);
435   if (constraints->hasPointsOfInterest())
436     UseCounter::Count(context, WebFeature::kImageCapturePointsOfInterest);
437   if (constraints->hasExposureCompensation())
438     UseCounter::Count(context, WebFeature::kImageCaptureExposureCompensation);
439   if (constraints->hasExposureTime())
440     UseCounter::Count(context, WebFeature::kImageCaptureExposureTime);
441   if (constraints->hasColorTemperature())
442     UseCounter::Count(context, WebFeature::kImageCaptureColorTemperature);
443   if (constraints->hasIso())
444     UseCounter::Count(context, WebFeature::kImageCaptureIso);
445   if (constraints->hasBrightness())
446     UseCounter::Count(context, WebFeature::kImageCaptureBrightness);
447   if (constraints->hasContrast())
448     UseCounter::Count(context, WebFeature::kImageCaptureContrast);
449   if (constraints->hasSaturation())
450     UseCounter::Count(context, WebFeature::kImageCaptureSaturation);
451   if (constraints->hasSharpness())
452     UseCounter::Count(context, WebFeature::kImageCaptureSharpness);
453   if (constraints->hasFocusDistance())
454     UseCounter::Count(context, WebFeature::kImageCaptureFocusDistance);
455   if (constraints->hasPan())
456     UseCounter::Count(context, WebFeature::kImageCapturePan);
457   if (constraints->hasTilt())
458     UseCounter::Count(context, WebFeature::kImageCaptureTilt);
459   if (constraints->hasZoom())
460     UseCounter::Count(context, WebFeature::kImageCaptureZoom);
461   if (constraints->hasTorch())
462     UseCounter::Count(context, WebFeature::kImageCaptureTorch);
463 
464   if (!service_.is_bound()) {
465     resolver->Reject(MakeGarbageCollected<DOMException>(
466         DOMExceptionCode::kNotFoundError, kNoServiceError));
467     return;
468   }
469 
470   if ((constraints->hasWhiteBalanceMode() &&
471        !capabilities_->hasWhiteBalanceMode()) ||
472       (constraints->hasExposureMode() && !capabilities_->hasExposureMode()) ||
473       (constraints->hasFocusMode() && !capabilities_->hasFocusMode()) ||
474       (constraints->hasExposureCompensation() &&
475        !capabilities_->hasExposureCompensation()) ||
476       (constraints->hasExposureTime() && !capabilities_->hasExposureTime()) ||
477       (constraints->hasColorTemperature() &&
478        !capabilities_->hasColorTemperature()) ||
479       (constraints->hasIso() && !capabilities_->hasIso()) ||
480       (constraints->hasBrightness() && !capabilities_->hasBrightness()) ||
481       (constraints->hasContrast() && !capabilities_->hasContrast()) ||
482       (constraints->hasSaturation() && !capabilities_->hasSaturation()) ||
483       (constraints->hasSharpness() && !capabilities_->hasSharpness()) ||
484       (constraints->hasFocusDistance() && !capabilities_->hasFocusDistance()) ||
485       (constraints->hasPan() &&
486        !(capabilities_->hasPan() && HasPanTiltZoomPermissionGranted())) ||
487       (constraints->hasTilt() &&
488        !(capabilities_->hasTilt() && HasPanTiltZoomPermissionGranted())) ||
489       (constraints->hasZoom() &&
490        !(capabilities_->hasZoom() && HasZoomPermissionGranted())) ||
491       (constraints->hasTorch() && !capabilities_->hasTorch())) {
492     resolver->Reject(MakeGarbageCollected<DOMException>(
493         DOMExceptionCode::kNotSupportedError, "Unsupported constraint(s)"));
494     return;
495   }
496 
497   auto settings = media::mojom::blink::PhotoSettings::New();
498   MediaTrackConstraintSet* temp_constraints =
499       current_constraints_ ? current_constraints_.Get()
500                            : MediaTrackConstraintSet::Create();
501 
502   // TODO(mcasas): support other Mode types beyond simple string i.e. the
503   // equivalents of "sequence<DOMString>"" or "ConstrainDOMStringParameters".
504   settings->has_white_balance_mode = constraints->hasWhiteBalanceMode() &&
505                                      constraints->whiteBalanceMode().IsString();
506   if (settings->has_white_balance_mode) {
507     const auto white_balance_mode =
508         constraints->whiteBalanceMode().GetAsString();
509     if (capabilities_->whiteBalanceMode().Find(white_balance_mode) ==
510         kNotFound) {
511       resolver->Reject(MakeGarbageCollected<DOMException>(
512           DOMExceptionCode::kNotSupportedError,
513           "Unsupported whiteBalanceMode."));
514       return;
515     }
516     temp_constraints->setWhiteBalanceMode(constraints->whiteBalanceMode());
517     settings->white_balance_mode = ParseMeteringMode(white_balance_mode);
518   }
519   settings->has_exposure_mode =
520       constraints->hasExposureMode() && constraints->exposureMode().IsString();
521   if (settings->has_exposure_mode) {
522     const auto exposure_mode = constraints->exposureMode().GetAsString();
523     if (capabilities_->exposureMode().Find(exposure_mode) == kNotFound) {
524       resolver->Reject(MakeGarbageCollected<DOMException>(
525           DOMExceptionCode::kNotSupportedError, "Unsupported exposureMode."));
526       return;
527     }
528     temp_constraints->setExposureMode(constraints->exposureMode());
529     settings->exposure_mode = ParseMeteringMode(exposure_mode);
530   }
531 
532   settings->has_focus_mode =
533       constraints->hasFocusMode() && constraints->focusMode().IsString();
534   if (settings->has_focus_mode) {
535     const auto focus_mode = constraints->focusMode().GetAsString();
536     if (capabilities_->focusMode().Find(focus_mode) == kNotFound) {
537       resolver->Reject(MakeGarbageCollected<DOMException>(
538           DOMExceptionCode::kNotSupportedError, "Unsupported focusMode."));
539       return;
540     }
541     temp_constraints->setFocusMode(constraints->focusMode());
542     settings->focus_mode = ParseMeteringMode(focus_mode);
543   }
544 
545   // TODO(mcasas): support ConstrainPoint2DParameters.
546   if (constraints->hasPointsOfInterest() &&
547       constraints->pointsOfInterest().IsPoint2DSequence()) {
548     for (const auto& point :
549          constraints->pointsOfInterest().GetAsPoint2DSequence()) {
550       auto mojo_point = media::mojom::blink::Point2D::New();
551       mojo_point->x = point->x();
552       mojo_point->y = point->y();
553       settings->points_of_interest.push_back(std::move(mojo_point));
554     }
555     temp_constraints->setPointsOfInterest(constraints->pointsOfInterest());
556   }
557 
558   // TODO(mcasas): support ConstrainDoubleRange where applicable.
559   settings->has_exposure_compensation =
560       constraints->hasExposureCompensation() &&
561       constraints->exposureCompensation().IsDouble();
562   if (settings->has_exposure_compensation) {
563     const auto exposure_compensation =
564         constraints->exposureCompensation().GetAsDouble();
565     if (exposure_compensation < capabilities_->exposureCompensation()->min() ||
566         exposure_compensation > capabilities_->exposureCompensation()->max()) {
567       resolver->Reject(MakeGarbageCollected<DOMException>(
568           DOMExceptionCode::kNotSupportedError,
569           "exposureCompensation setting out of range"));
570       return;
571     }
572     temp_constraints->setExposureCompensation(
573         constraints->exposureCompensation());
574     settings->exposure_compensation = exposure_compensation;
575   }
576 
577   settings->has_exposure_time =
578       constraints->hasExposureTime() && constraints->exposureTime().IsDouble();
579   if (settings->has_exposure_time) {
580     const auto exposure_time = constraints->exposureTime().GetAsDouble();
581     if (exposure_time < capabilities_->exposureTime()->min() ||
582         exposure_time > capabilities_->exposureTime()->max()) {
583       resolver->Reject(MakeGarbageCollected<DOMException>(
584           DOMExceptionCode::kNotSupportedError,
585           "exposureTime setting out of range"));
586       return;
587     }
588     temp_constraints->setExposureTime(constraints->exposureTime());
589     settings->exposure_time = exposure_time;
590   }
591   settings->has_color_temperature = constraints->hasColorTemperature() &&
592                                     constraints->colorTemperature().IsDouble();
593   if (settings->has_color_temperature) {
594     const auto color_temperature =
595         constraints->colorTemperature().GetAsDouble();
596     if (color_temperature < capabilities_->colorTemperature()->min() ||
597         color_temperature > capabilities_->colorTemperature()->max()) {
598       resolver->Reject(MakeGarbageCollected<DOMException>(
599           DOMExceptionCode::kNotSupportedError,
600           "colorTemperature setting out of range"));
601       return;
602     }
603     temp_constraints->setColorTemperature(constraints->colorTemperature());
604     settings->color_temperature = color_temperature;
605   }
606   settings->has_iso = constraints->hasIso() && constraints->iso().IsDouble();
607   if (settings->has_iso) {
608     const auto iso = constraints->iso().GetAsDouble();
609     if (iso < capabilities_->iso()->min() ||
610         iso > capabilities_->iso()->max()) {
611       resolver->Reject(MakeGarbageCollected<DOMException>(
612           DOMExceptionCode::kNotSupportedError, "iso setting out of range"));
613       return;
614     }
615     temp_constraints->setIso(constraints->iso());
616     settings->iso = iso;
617   }
618 
619   settings->has_brightness =
620       constraints->hasBrightness() && constraints->brightness().IsDouble();
621   if (settings->has_brightness) {
622     const auto brightness = constraints->brightness().GetAsDouble();
623     if (brightness < capabilities_->brightness()->min() ||
624         brightness > capabilities_->brightness()->max()) {
625       resolver->Reject(MakeGarbageCollected<DOMException>(
626           DOMExceptionCode::kNotSupportedError,
627           "brightness setting out of range"));
628       return;
629     }
630     temp_constraints->setBrightness(constraints->brightness());
631     settings->brightness = brightness;
632   }
633   settings->has_contrast =
634       constraints->hasContrast() && constraints->contrast().IsDouble();
635   if (settings->has_contrast) {
636     const auto contrast = constraints->contrast().GetAsDouble();
637     if (contrast < capabilities_->contrast()->min() ||
638         contrast > capabilities_->contrast()->max()) {
639       resolver->Reject(MakeGarbageCollected<DOMException>(
640           DOMExceptionCode::kNotSupportedError,
641           "contrast setting out of range"));
642       return;
643     }
644     temp_constraints->setContrast(constraints->contrast());
645     settings->contrast = contrast;
646   }
647   settings->has_saturation =
648       constraints->hasSaturation() && constraints->saturation().IsDouble();
649   if (settings->has_saturation) {
650     const auto saturation = constraints->saturation().GetAsDouble();
651     if (saturation < capabilities_->saturation()->min() ||
652         saturation > capabilities_->saturation()->max()) {
653       resolver->Reject(MakeGarbageCollected<DOMException>(
654           DOMExceptionCode::kNotSupportedError,
655           "saturation setting out of range"));
656       return;
657     }
658     temp_constraints->setSaturation(constraints->saturation());
659     settings->saturation = saturation;
660   }
661   settings->has_sharpness =
662       constraints->hasSharpness() && constraints->sharpness().IsDouble();
663   if (settings->has_sharpness) {
664     const auto sharpness = constraints->sharpness().GetAsDouble();
665     if (sharpness < capabilities_->sharpness()->min() ||
666         sharpness > capabilities_->sharpness()->max()) {
667       resolver->Reject(MakeGarbageCollected<DOMException>(
668           DOMExceptionCode::kNotSupportedError,
669           "sharpness setting out of range"));
670       return;
671     }
672     temp_constraints->setSharpness(constraints->sharpness());
673     settings->sharpness = sharpness;
674   }
675 
676   settings->has_focus_distance = constraints->hasFocusDistance() &&
677                                  constraints->focusDistance().IsDouble();
678   if (settings->has_focus_distance) {
679     const auto focus_distance = constraints->focusDistance().GetAsDouble();
680     if (focus_distance < capabilities_->focusDistance()->min() ||
681         focus_distance > capabilities_->focusDistance()->max()) {
682       resolver->Reject(MakeGarbageCollected<DOMException>(
683           DOMExceptionCode::kNotSupportedError,
684           "focusDistance setting out of range"));
685       return;
686     }
687     temp_constraints->setFocusDistance(constraints->focusDistance());
688     settings->focus_distance = focus_distance;
689   }
690 
691   settings->has_pan = constraints->hasPan() && constraints->pan().IsDouble();
692   if (settings->has_pan) {
693     if (!IsPageVisible()) {
694       resolver->Reject(MakeGarbageCollected<DOMException>(
695           DOMExceptionCode::kSecurityError, "the page is not visible"));
696       return;
697     }
698     const auto pan = constraints->pan().GetAsDouble();
699     if (pan < capabilities_->pan()->min() ||
700         pan > capabilities_->pan()->max()) {
701       resolver->Reject(MakeGarbageCollected<DOMException>(
702           DOMExceptionCode::kNotSupportedError, "pan setting out of range"));
703       return;
704     }
705     temp_constraints->setPan(constraints->pan());
706     settings->pan = pan;
707   }
708 
709   settings->has_tilt = constraints->hasTilt() && constraints->tilt().IsDouble();
710   if (settings->has_tilt) {
711     if (!IsPageVisible()) {
712       resolver->Reject(MakeGarbageCollected<DOMException>(
713           DOMExceptionCode::kSecurityError, "the page is not visible"));
714       return;
715     }
716     const auto tilt = constraints->tilt().GetAsDouble();
717     if (tilt < capabilities_->tilt()->min() ||
718         tilt > capabilities_->tilt()->max()) {
719       resolver->Reject(MakeGarbageCollected<DOMException>(
720           DOMExceptionCode::kNotSupportedError, "tilt setting out of range"));
721       return;
722     }
723     temp_constraints->setTilt(constraints->tilt());
724     settings->tilt = tilt;
725   }
726 
727   settings->has_zoom = constraints->hasZoom() && constraints->zoom().IsDouble();
728   if (settings->has_zoom) {
729     if (!IsPageVisible()) {
730       resolver->Reject(MakeGarbageCollected<DOMException>(
731           DOMExceptionCode::kSecurityError, "the page is not visible"));
732       return;
733     }
734     const auto zoom = constraints->zoom().GetAsDouble();
735     if (zoom < capabilities_->zoom()->min() ||
736         zoom > capabilities_->zoom()->max()) {
737       resolver->Reject(MakeGarbageCollected<DOMException>(
738           DOMExceptionCode::kNotSupportedError, "zoom setting out of range"));
739       return;
740     }
741     temp_constraints->setZoom(constraints->zoom());
742     settings->zoom = zoom;
743   }
744 
745   // TODO(mcasas): support ConstrainBooleanParameters where applicable.
746   settings->has_torch =
747       constraints->hasTorch() && constraints->torch().IsBoolean();
748   if (settings->has_torch) {
749     const auto torch = constraints->torch().GetAsBoolean();
750     if (torch && !capabilities_->torch()) {
751       resolver->Reject(MakeGarbageCollected<DOMException>(
752           DOMExceptionCode::kNotSupportedError, "torch not supported"));
753       return;
754     }
755     temp_constraints->setTorch(constraints->torch());
756     settings->torch = torch;
757   }
758 
759   current_constraints_ = temp_constraints;
760 
761   service_requests_.insert(resolver);
762 
763   service_->SetOptions(
764       stream_track_->Component()->Source()->Id(), std::move(settings),
765       WTF::Bind(&ImageCapture::OnMojoSetOptions, WrapPersistent(this),
766                 WrapPersistent(resolver), false /* trigger_take_photo */));
767 }
768 
SetPanTiltZoomSettingsFromTrack(base::OnceClosure initialized_callback,media::mojom::blink::PhotoStatePtr photo_state)769 void ImageCapture::SetPanTiltZoomSettingsFromTrack(
770     base::OnceClosure initialized_callback,
771     media::mojom::blink::PhotoStatePtr photo_state) {
772   UpdateMediaTrackCapabilities(base::DoNothing(), std::move(photo_state));
773 
774   auto* video_track = MediaStreamVideoTrack::From(stream_track_->Component());
775   DCHECK(video_track);
776 
777   base::Optional<double> pan = video_track->pan();
778   base::Optional<double> tilt = video_track->tilt();
779   base::Optional<double> zoom = video_track->zoom();
780 
781   const bool ptz_requested =
782       pan.has_value() || tilt.has_value() || zoom.has_value();
783   const bool ptz_supported = capabilities_->hasPan() ||
784                              capabilities_->hasTilt() ||
785                              capabilities_->hasZoom();
786   if (!ptz_supported || !ptz_requested || !HasPanTiltZoomPermissionGranted() ||
787       !service_.is_bound()) {
788     std::move(initialized_callback).Run();
789     return;
790   }
791 
792   ExecutionContext* context = GetExecutionContext();
793   if (pan.has_value())
794     UseCounter::Count(context, WebFeature::kImageCapturePan);
795   if (tilt.has_value())
796     UseCounter::Count(context, WebFeature::kImageCaptureTilt);
797   if (zoom.has_value())
798     UseCounter::Count(context, WebFeature::kImageCaptureZoom);
799 
800   auto settings = media::mojom::blink::PhotoSettings::New();
801 
802   if (capabilities_->hasPan() && pan.has_value() &&
803       pan.value() >= capabilities_->pan()->min() &&
804       pan.value() <= capabilities_->pan()->max()) {
805     settings->has_pan = true;
806     settings->pan = pan.value();
807   }
808   if (capabilities_->hasTilt() && tilt.has_value() &&
809       tilt.value() >= capabilities_->tilt()->min() &&
810       tilt.value() <= capabilities_->tilt()->max()) {
811     settings->has_tilt = true;
812     settings->tilt = tilt.value();
813   }
814   if (capabilities_->hasZoom() && zoom.has_value() &&
815       zoom.value() >= capabilities_->zoom()->min() &&
816       zoom.value() <= capabilities_->zoom()->max()) {
817     settings->has_zoom = true;
818     settings->zoom = zoom.value();
819   }
820 
821   service_->SetOptions(
822       stream_track_->Component()->Source()->Id(), std::move(settings),
823       WTF::Bind(&ImageCapture::OnSetPanTiltZoomSettingsFromTrack,
824                 WrapPersistent(this), std::move(initialized_callback)));
825 }
826 
OnSetPanTiltZoomSettingsFromTrack(base::OnceClosure done_callback,bool result)827 void ImageCapture::OnSetPanTiltZoomSettingsFromTrack(
828     base::OnceClosure done_callback,
829     bool result) {
830   service_->GetPhotoState(
831       stream_track_->Component()->Source()->Id(),
832       WTF::Bind(&ImageCapture::UpdateMediaTrackCapabilities,
833                 WrapPersistent(this), std::move(done_callback)));
834 }
835 
GetMediaTrackConstraints() const836 const MediaTrackConstraintSet* ImageCapture::GetMediaTrackConstraints() const {
837   return current_constraints_;
838 }
839 
ClearMediaTrackConstraints()840 void ImageCapture::ClearMediaTrackConstraints() {
841   current_constraints_ = nullptr;
842 
843   // TODO(mcasas): Clear also any PhotoSettings that the device might have got
844   // configured, for that we need to know a "default" state of the device; take
845   // a snapshot upon first opening. https://crbug.com/700607.
846 }
847 
GetMediaTrackSettings(MediaTrackSettings * settings) const848 void ImageCapture::GetMediaTrackSettings(MediaTrackSettings* settings) const {
849   // Merge any present |settings_| members into |settings|.
850 
851   if (settings_->hasWhiteBalanceMode())
852     settings->setWhiteBalanceMode(settings_->whiteBalanceMode());
853   if (settings_->hasExposureMode())
854     settings->setExposureMode(settings_->exposureMode());
855   if (settings_->hasFocusMode())
856     settings->setFocusMode(settings_->focusMode());
857 
858   if (settings_->hasPointsOfInterest() &&
859       !settings_->pointsOfInterest().IsEmpty()) {
860     settings->setPointsOfInterest(settings_->pointsOfInterest());
861   }
862 
863   if (settings_->hasExposureCompensation())
864     settings->setExposureCompensation(settings_->exposureCompensation());
865   if (settings_->hasExposureTime())
866     settings->setExposureTime(settings_->exposureTime());
867   if (settings_->hasColorTemperature())
868     settings->setColorTemperature(settings_->colorTemperature());
869   if (settings_->hasIso())
870     settings->setIso(settings_->iso());
871 
872   if (settings_->hasBrightness())
873     settings->setBrightness(settings_->brightness());
874   if (settings_->hasContrast())
875     settings->setContrast(settings_->contrast());
876   if (settings_->hasSaturation())
877     settings->setSaturation(settings_->saturation());
878   if (settings_->hasSharpness())
879     settings->setSharpness(settings_->sharpness());
880 
881   if (settings_->hasFocusDistance())
882     settings->setFocusDistance(settings_->focusDistance());
883 
884   if (HasPanTiltZoomPermissionGranted()) {
885     if (settings_->hasPan())
886       settings->setPan(settings_->pan());
887     if (settings_->hasTilt())
888       settings->setTilt(settings_->tilt());
889   }
890   if (settings_->hasZoom() && HasZoomPermissionGranted()) {
891     settings->setZoom(settings_->zoom());
892   }
893 
894   if (settings_->hasTorch())
895     settings->setTorch(settings_->torch());
896 }
897 
ImageCapture(ExecutionContext * context,MediaStreamTrack * track,bool pan_tilt_zoom_allowed,base::OnceClosure initialized_callback)898 ImageCapture::ImageCapture(ExecutionContext* context,
899                            MediaStreamTrack* track,
900                            bool pan_tilt_zoom_allowed,
901                            base::OnceClosure initialized_callback)
902     : ExecutionContextLifecycleObserver(context),
903       stream_track_(track),
904       service_(context),
905       pan_tilt_zoom_permission_(pan_tilt_zoom_allowed
906                                     ? mojom::blink::PermissionStatus::GRANTED
907                                     : mojom::blink::PermissionStatus::ASK),
908       permission_service_(context),
909       permission_observer_receiver_(this, context),
910       capabilities_(MediaTrackCapabilities::Create()),
911       settings_(MediaTrackSettings::Create()),
912       photo_settings_(PhotoSettings::Create()) {
913   DCHECK(stream_track_);
914   DCHECK(!service_.is_bound());
915   DCHECK(!permission_service_.is_bound());
916 
917   // This object may be constructed over an ExecutionContext that has already
918   // been detached. In this case the ImageCapture service will not be available.
919   if (!DomWindow())
920     return;
921 
922   DomWindow()->GetBrowserInterfaceBroker().GetInterface(
923       service_.BindNewPipeAndPassReceiver(
924           context->GetTaskRunner(TaskType::kDOMManipulation)));
925 
926   service_.set_disconnect_handler(WTF::Bind(
927       &ImageCapture::OnServiceConnectionError, WrapWeakPersistent(this)));
928 
929   // Launch a retrieval of the current photo state, which arrive asynchronously
930   // to avoid blocking the main UI thread.
931   service_->GetPhotoState(
932       stream_track_->Component()->Source()->Id(),
933       WTF::Bind(&ImageCapture::SetPanTiltZoomSettingsFromTrack,
934                 WrapPersistent(this), std::move(initialized_callback)));
935 
936   ConnectToPermissionService(
937       context, permission_service_.BindNewPipeAndPassReceiver(
938                    context->GetTaskRunner(TaskType::kMiscPlatformAPI)));
939 
940   mojo::PendingRemote<mojom::blink::PermissionObserver> observer;
941   permission_observer_receiver_.Bind(
942       observer.InitWithNewPipeAndPassReceiver(),
943       context->GetTaskRunner(TaskType::kMiscPlatformAPI));
944   permission_service_->AddPermissionObserver(
945       CreateVideoCapturePermissionDescriptor(/*pan_tilt_zoom=*/true),
946       pan_tilt_zoom_permission_, std::move(observer));
947 }
948 
OnPermissionStatusChange(mojom::blink::PermissionStatus status)949 void ImageCapture::OnPermissionStatusChange(
950     mojom::blink::PermissionStatus status) {
951   pan_tilt_zoom_permission_ = status;
952 }
953 
HasPanTiltZoomPermissionGranted() const954 bool ImageCapture::HasPanTiltZoomPermissionGranted() const {
955   if (!RuntimeEnabledFeatures::MediaCapturePanTiltEnabled())
956     return false;
957 
958   return pan_tilt_zoom_permission_ == mojom::blink::PermissionStatus::GRANTED;
959 }
960 
HasZoomPermissionGranted() const961 bool ImageCapture::HasZoomPermissionGranted() const {
962   return pan_tilt_zoom_permission_ == mojom::blink::PermissionStatus::GRANTED;
963 }
964 
OnMojoGetPhotoState(ScriptPromiseResolver * resolver,PromiseResolverFunction resolve_function,bool trigger_take_photo,media::mojom::blink::PhotoStatePtr photo_state)965 void ImageCapture::OnMojoGetPhotoState(
966     ScriptPromiseResolver* resolver,
967     PromiseResolverFunction resolve_function,
968     bool trigger_take_photo,
969     media::mojom::blink::PhotoStatePtr photo_state) {
970   DCHECK(service_requests_.Contains(resolver));
971 
972   if (photo_state.is_null()) {
973     resolver->Reject(MakeGarbageCollected<DOMException>(
974         DOMExceptionCode::kUnknownError, "platform error"));
975     service_requests_.erase(resolver);
976     return;
977   }
978 
979   if (TrackIsInactive(*stream_track_)) {
980     resolver->Reject(MakeGarbageCollected<DOMException>(
981         DOMExceptionCode::kOperationError,
982         "The associated Track is in an invalid state."));
983     service_requests_.erase(resolver);
984     return;
985   }
986 
987   photo_settings_ = PhotoSettings::Create();
988   photo_settings_->setImageHeight(photo_state->height->current);
989   photo_settings_->setImageWidth(photo_state->width->current);
990   // TODO(mcasas): collect the remaining two entries https://crbug.com/732521.
991 
992   photo_capabilities_ = MakeGarbageCollected<PhotoCapabilities>();
993   photo_capabilities_->setRedEyeReduction(
994       ToString(photo_state->red_eye_reduction));
995   if (photo_state->height->min != 0 || photo_state->height->max != 0) {
996     photo_capabilities_->setImageHeight(
997         ToMediaSettingsRange(*photo_state->height));
998   }
999   if (photo_state->width->min != 0 || photo_state->width->max != 0) {
1000     photo_capabilities_->setImageWidth(
1001         ToMediaSettingsRange(*photo_state->width));
1002   }
1003 
1004 #ifdef USE_BLINK_V8_BINDING_NEW_IDL_DICTIONARY
1005   WTF::Vector<V8FillLightMode> fill_light_mode;
1006 #else
1007   WTF::Vector<WTF::String> fill_light_mode;
1008 #endif
1009   for (const auto& mode : photo_state->fill_light_mode) {
1010     fill_light_mode.push_back(ToV8FillLightMode(mode));
1011   }
1012   if (!fill_light_mode.IsEmpty())
1013     photo_capabilities_->setFillLightMode(fill_light_mode);
1014 
1015   // Update the local track photo_state cache.
1016   UpdateMediaTrackCapabilities(base::DoNothing(), std::move(photo_state));
1017 
1018   if (trigger_take_photo) {
1019     service_->TakePhoto(
1020         stream_track_->Component()->Source()->Id(),
1021         WTF::Bind(&ImageCapture::OnMojoTakePhoto, WrapPersistent(this),
1022                   WrapPersistent(resolver)));
1023     return;
1024   }
1025 
1026   std::move(resolve_function).Run(resolver);
1027   service_requests_.erase(resolver);
1028 }
1029 
OnMojoSetOptions(ScriptPromiseResolver * resolver,bool trigger_take_photo,bool result)1030 void ImageCapture::OnMojoSetOptions(ScriptPromiseResolver* resolver,
1031                                     bool trigger_take_photo,
1032                                     bool result) {
1033   DCHECK(service_requests_.Contains(resolver));
1034   TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
1035                        "ImageCapture::OnMojoSetOptions",
1036                        TRACE_EVENT_SCOPE_PROCESS);
1037 
1038   if (!result) {
1039     resolver->Reject(MakeGarbageCollected<DOMException>(
1040         DOMExceptionCode::kUnknownError, "setOptions failed"));
1041     service_requests_.erase(resolver);
1042     return;
1043   }
1044 
1045   auto resolver_cb =
1046       WTF::Bind(&ImageCapture::ResolveWithNothing, WrapPersistent(this));
1047 
1048   // Retrieve the current device status after setting the options.
1049   service_->GetPhotoState(
1050       stream_track_->Component()->Source()->Id(),
1051       WTF::Bind(&ImageCapture::OnMojoGetPhotoState, WrapPersistent(this),
1052                 WrapPersistent(resolver), WTF::Passed(std::move(resolver_cb)),
1053                 trigger_take_photo));
1054 }
1055 
OnMojoTakePhoto(ScriptPromiseResolver * resolver,media::mojom::blink::BlobPtr blob)1056 void ImageCapture::OnMojoTakePhoto(ScriptPromiseResolver* resolver,
1057                                    media::mojom::blink::BlobPtr blob) {
1058   DCHECK(service_requests_.Contains(resolver));
1059   TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
1060                        "ImageCapture::OnMojoTakePhoto",
1061                        TRACE_EVENT_SCOPE_PROCESS);
1062 
1063   // TODO(mcasas): Should be using a mojo::StructTraits.
1064   if (blob->data.IsEmpty()) {
1065     resolver->Reject(MakeGarbageCollected<DOMException>(
1066         DOMExceptionCode::kUnknownError, "platform error"));
1067   } else {
1068     resolver->Resolve(
1069         Blob::Create(blob->data.data(), blob->data.size(), blob->mime_type));
1070   }
1071   service_requests_.erase(resolver);
1072 }
1073 
UpdateMediaTrackCapabilities(base::OnceClosure initialized_callback,media::mojom::blink::PhotoStatePtr photo_state)1074 void ImageCapture::UpdateMediaTrackCapabilities(
1075     base::OnceClosure initialized_callback,
1076     media::mojom::blink::PhotoStatePtr photo_state) {
1077   if (!photo_state) {
1078     std::move(initialized_callback).Run();
1079     return;
1080   }
1081 
1082   WTF::Vector<WTF::String> supported_white_balance_modes;
1083   supported_white_balance_modes.ReserveInitialCapacity(
1084       photo_state->supported_white_balance_modes.size());
1085   for (const auto& supported_mode : photo_state->supported_white_balance_modes)
1086     supported_white_balance_modes.push_back(ToString(supported_mode));
1087   if (!supported_white_balance_modes.IsEmpty()) {
1088     capabilities_->setWhiteBalanceMode(
1089         std::move(supported_white_balance_modes));
1090     settings_->setWhiteBalanceMode(
1091         ToString(photo_state->current_white_balance_mode));
1092   }
1093 
1094   WTF::Vector<WTF::String> supported_exposure_modes;
1095   supported_exposure_modes.ReserveInitialCapacity(
1096       photo_state->supported_exposure_modes.size());
1097   for (const auto& supported_mode : photo_state->supported_exposure_modes)
1098     supported_exposure_modes.push_back(ToString(supported_mode));
1099   if (!supported_exposure_modes.IsEmpty()) {
1100     capabilities_->setExposureMode(std::move(supported_exposure_modes));
1101     settings_->setExposureMode(ToString(photo_state->current_exposure_mode));
1102   }
1103 
1104   WTF::Vector<WTF::String> supported_focus_modes;
1105   supported_focus_modes.ReserveInitialCapacity(
1106       photo_state->supported_focus_modes.size());
1107   for (const auto& supported_mode : photo_state->supported_focus_modes)
1108     supported_focus_modes.push_back(ToString(supported_mode));
1109   if (!supported_focus_modes.IsEmpty()) {
1110     capabilities_->setFocusMode(std::move(supported_focus_modes));
1111     settings_->setFocusMode(ToString(photo_state->current_focus_mode));
1112   }
1113 
1114   HeapVector<Member<Point2D>> current_points_of_interest;
1115   if (!photo_state->points_of_interest.IsEmpty()) {
1116     for (const auto& point : photo_state->points_of_interest) {
1117       Point2D* web_point = Point2D::Create();
1118       web_point->setX(point->x);
1119       web_point->setY(point->y);
1120       current_points_of_interest.push_back(web_point);
1121     }
1122   }
1123   settings_->setPointsOfInterest(current_points_of_interest);
1124 
1125   if (photo_state->exposure_compensation->max !=
1126       photo_state->exposure_compensation->min) {
1127     capabilities_->setExposureCompensation(
1128         ToMediaSettingsRange(*photo_state->exposure_compensation));
1129     settings_->setExposureCompensation(
1130         photo_state->exposure_compensation->current);
1131   }
1132   if (photo_state->exposure_time->max != photo_state->exposure_time->min) {
1133     capabilities_->setExposureTime(
1134         ToMediaSettingsRange(*photo_state->exposure_time));
1135     settings_->setExposureTime(photo_state->exposure_time->current);
1136   }
1137   if (photo_state->color_temperature->max !=
1138       photo_state->color_temperature->min) {
1139     capabilities_->setColorTemperature(
1140         ToMediaSettingsRange(*photo_state->color_temperature));
1141     settings_->setColorTemperature(photo_state->color_temperature->current);
1142   }
1143   if (photo_state->iso->max != photo_state->iso->min) {
1144     capabilities_->setIso(ToMediaSettingsRange(*photo_state->iso));
1145     settings_->setIso(photo_state->iso->current);
1146   }
1147 
1148   if (photo_state->brightness->max != photo_state->brightness->min) {
1149     capabilities_->setBrightness(
1150         ToMediaSettingsRange(*photo_state->brightness));
1151     settings_->setBrightness(photo_state->brightness->current);
1152   }
1153   if (photo_state->contrast->max != photo_state->contrast->min) {
1154     capabilities_->setContrast(ToMediaSettingsRange(*photo_state->contrast));
1155     settings_->setContrast(photo_state->contrast->current);
1156   }
1157   if (photo_state->saturation->max != photo_state->saturation->min) {
1158     capabilities_->setSaturation(
1159         ToMediaSettingsRange(*photo_state->saturation));
1160     settings_->setSaturation(photo_state->saturation->current);
1161   }
1162   if (photo_state->sharpness->max != photo_state->sharpness->min) {
1163     capabilities_->setSharpness(ToMediaSettingsRange(*photo_state->sharpness));
1164     settings_->setSharpness(photo_state->sharpness->current);
1165   }
1166 
1167   if (photo_state->focus_distance->max != photo_state->focus_distance->min) {
1168     capabilities_->setFocusDistance(
1169         ToMediaSettingsRange(*photo_state->focus_distance));
1170     settings_->setFocusDistance(photo_state->focus_distance->current);
1171   }
1172 
1173   if (HasPanTiltZoomPermissionGranted()) {
1174     if (photo_state->pan->max != photo_state->pan->min) {
1175       capabilities_->setPan(ToMediaSettingsRange(*photo_state->pan));
1176       settings_->setPan(photo_state->pan->current);
1177     }
1178     if (photo_state->tilt->max != photo_state->tilt->min) {
1179       capabilities_->setTilt(ToMediaSettingsRange(*photo_state->tilt));
1180       settings_->setTilt(photo_state->tilt->current);
1181     }
1182   }
1183   if (HasZoomPermissionGranted()) {
1184     if (photo_state->zoom->max != photo_state->zoom->min) {
1185       capabilities_->setZoom(ToMediaSettingsRange(*photo_state->zoom));
1186       settings_->setZoom(photo_state->zoom->current);
1187     }
1188   }
1189 
1190   if (photo_state->supports_torch)
1191     capabilities_->setTorch(photo_state->supports_torch);
1192   if (photo_state->supports_torch)
1193     settings_->setTorch(photo_state->torch);
1194 
1195   std::move(initialized_callback).Run();
1196 }
1197 
OnServiceConnectionError()1198 void ImageCapture::OnServiceConnectionError() {
1199   service_.reset();
1200   for (ScriptPromiseResolver* resolver : service_requests_) {
1201     resolver->Reject(MakeGarbageCollected<DOMException>(
1202         DOMExceptionCode::kNotFoundError, kNoServiceError));
1203   }
1204   service_requests_.clear();
1205 }
1206 
ResolveWithNothing(ScriptPromiseResolver * resolver)1207 void ImageCapture::ResolveWithNothing(ScriptPromiseResolver* resolver) {
1208   DCHECK(resolver);
1209   resolver->Resolve();
1210 }
1211 
ResolveWithPhotoSettings(ScriptPromiseResolver * resolver)1212 void ImageCapture::ResolveWithPhotoSettings(ScriptPromiseResolver* resolver) {
1213   DCHECK(resolver);
1214   resolver->Resolve(photo_settings_);
1215 }
1216 
ResolveWithPhotoCapabilities(ScriptPromiseResolver * resolver)1217 void ImageCapture::ResolveWithPhotoCapabilities(
1218     ScriptPromiseResolver* resolver) {
1219   DCHECK(resolver);
1220   resolver->Resolve(photo_capabilities_);
1221 }
1222 
IsPageVisible()1223 bool ImageCapture::IsPageVisible() {
1224   return DomWindow() ? DomWindow()->document()->IsPageVisible() : false;
1225 }
1226 
Trace(Visitor * visitor) const1227 void ImageCapture::Trace(Visitor* visitor) const {
1228   visitor->Trace(stream_track_);
1229   visitor->Trace(service_);
1230   visitor->Trace(permission_service_);
1231   visitor->Trace(permission_observer_receiver_);
1232   visitor->Trace(capabilities_);
1233   visitor->Trace(settings_);
1234   visitor->Trace(photo_settings_);
1235   visitor->Trace(current_constraints_);
1236   visitor->Trace(photo_capabilities_);
1237   visitor->Trace(service_requests_);
1238   EventTargetWithInlineData::Trace(visitor);
1239   ExecutionContextLifecycleObserver::Trace(visitor);
1240 }
1241 
1242 }  // namespace blink
1243