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