1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "ImageCapture.h"
8 #include "mozilla/dom/BlobEvent.h"
9 #include "mozilla/dom/DOMException.h"
10 #include "mozilla/dom/Event.h"
11 #include "mozilla/dom/File.h"
12 #include "mozilla/dom/ImageCaptureError.h"
13 #include "mozilla/dom/ImageCaptureErrorEvent.h"
14 #include "mozilla/dom/ImageCaptureErrorEventBinding.h"
15 #include "mozilla/dom/VideoStreamTrack.h"
16 #include "mozilla/dom/Document.h"
17 #include "CaptureTask.h"
18 #include "MediaEngineSource.h"
19 
20 namespace mozilla {
21 
GetICLog()22 LogModule* GetICLog() {
23   static LazyLogModule log("ImageCapture");
24   return log;
25 }
26 
27 namespace dom {
28 
NS_IMPL_CYCLE_COLLECTION_INHERITED(ImageCapture,DOMEventTargetHelper,mTrack)29 NS_IMPL_CYCLE_COLLECTION_INHERITED(ImageCapture, DOMEventTargetHelper, mTrack)
30 
31 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageCapture)
32 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
33 
34 NS_IMPL_ADDREF_INHERITED(ImageCapture, DOMEventTargetHelper)
35 NS_IMPL_RELEASE_INHERITED(ImageCapture, DOMEventTargetHelper)
36 
37 ImageCapture::ImageCapture(VideoStreamTrack* aTrack,
38                            nsPIDOMWindowInner* aOwnerWindow)
39     : DOMEventTargetHelper(aOwnerWindow), mTrack(aTrack) {
40   MOZ_ASSERT(aOwnerWindow);
41   MOZ_ASSERT(aTrack);
42 }
43 
~ImageCapture()44 ImageCapture::~ImageCapture() { MOZ_ASSERT(NS_IsMainThread()); }
45 
Constructor(const GlobalObject & aGlobal,MediaStreamTrack & aTrack,ErrorResult & aRv)46 already_AddRefed<ImageCapture> ImageCapture::Constructor(
47     const GlobalObject& aGlobal, MediaStreamTrack& aTrack, ErrorResult& aRv) {
48   nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
49   if (!win) {
50     aRv.Throw(NS_ERROR_FAILURE);
51     return nullptr;
52   }
53 
54   if (!aTrack.AsVideoStreamTrack()) {
55     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
56     return nullptr;
57   }
58 
59   RefPtr<ImageCapture> object =
60       new ImageCapture(aTrack.AsVideoStreamTrack(), win);
61 
62   return object.forget();
63 }
64 
GetVideoStreamTrack() const65 MediaStreamTrack* ImageCapture::GetVideoStreamTrack() const { return mTrack; }
66 
TakePhotoByMediaEngine()67 nsresult ImageCapture::TakePhotoByMediaEngine() {
68   // Callback for TakPhoto(), it also monitor the principal. If principal
69   // changes, it returns PHOTO_ERROR with security error.
70   class TakePhotoCallback : public MediaEnginePhotoCallback,
71                             public PrincipalChangeObserver<MediaStreamTrack> {
72    public:
73     TakePhotoCallback(VideoStreamTrack* aVideoTrack,
74                       ImageCapture* aImageCapture)
75         : mVideoTrack(aVideoTrack),
76           mImageCapture(aImageCapture),
77           mPrincipalChanged(false) {
78       MOZ_ASSERT(NS_IsMainThread());
79       mVideoTrack->AddPrincipalChangeObserver(this);
80     }
81 
82     void PrincipalChanged(MediaStreamTrack* aMediaStream) override {
83       mPrincipalChanged = true;
84     }
85 
86     nsresult PhotoComplete(already_AddRefed<Blob> aBlob) override {
87       RefPtr<Blob> blob = aBlob;
88 
89       if (mPrincipalChanged) {
90         return PhotoError(NS_ERROR_DOM_SECURITY_ERR);
91       }
92       return mImageCapture->PostBlobEvent(blob);
93     }
94 
95     nsresult PhotoError(nsresult aRv) override {
96       return mImageCapture->PostErrorEvent(ImageCaptureError::PHOTO_ERROR, aRv);
97     }
98 
99    protected:
100     ~TakePhotoCallback() {
101       MOZ_ASSERT(NS_IsMainThread());
102       mVideoTrack->RemovePrincipalChangeObserver(this);
103     }
104 
105     const RefPtr<VideoStreamTrack> mVideoTrack;
106     const RefPtr<ImageCapture> mImageCapture;
107     bool mPrincipalChanged;
108   };
109 
110   RefPtr<MediaEnginePhotoCallback> callback =
111       new TakePhotoCallback(mTrack, this);
112   return mTrack->GetSource().TakePhoto(callback);
113 }
114 
TakePhoto(ErrorResult & aResult)115 void ImageCapture::TakePhoto(ErrorResult& aResult) {
116   // According to spec, MediaStreamTrack.readyState must be "live"; however
117   // gecko doesn't implement it yet (bug 910249). Instead of readyState, we
118   // check MediaStreamTrack.enable before bug 910249 is fixed.
119   // The error code should be INVALID_TRACK, but spec doesn't define it in
120   // ImageCaptureError. So it returns PHOTO_ERROR here before spec updates.
121   if (!mTrack->Enabled()) {
122     PostErrorEvent(ImageCaptureError::PHOTO_ERROR, NS_ERROR_FAILURE);
123     return;
124   }
125 
126   // Try if MediaEngine supports taking photo.
127   nsresult rv = TakePhotoByMediaEngine();
128 
129   // It falls back to MediaTrackGraph image capture if MediaEngine doesn't
130   // support TakePhoto().
131   if (rv == NS_ERROR_NOT_IMPLEMENTED) {
132     IC_LOG(
133         "MediaEngine doesn't support TakePhoto(), it falls back to "
134         "MediaTrackGraph.");
135     RefPtr<CaptureTask> task = new CaptureTask(this);
136 
137     // It adds itself into MediaTrackGraph, so ImageCapture doesn't need to
138     // hold the reference.
139     task->AttachTrack();
140   }
141 }
142 
PostBlobEvent(Blob * aBlob)143 nsresult ImageCapture::PostBlobEvent(Blob* aBlob) {
144   MOZ_ASSERT(NS_IsMainThread());
145   if (!CheckPrincipal()) {
146     // Media is not same-origin, don't allow the data out.
147     return PostErrorEvent(ImageCaptureError::PHOTO_ERROR,
148                           NS_ERROR_DOM_SECURITY_ERR);
149   }
150 
151   BlobEventInit init;
152   init.mBubbles = false;
153   init.mCancelable = false;
154   init.mData = aBlob;
155 
156   RefPtr<BlobEvent> blob_event =
157       BlobEvent::Constructor(this, u"photo"_ns, init);
158 
159   return DispatchTrustedEvent(blob_event);
160 }
161 
PostErrorEvent(uint16_t aErrorCode,nsresult aReason)162 nsresult ImageCapture::PostErrorEvent(uint16_t aErrorCode, nsresult aReason) {
163   MOZ_ASSERT(NS_IsMainThread());
164   nsresult rv = CheckCurrentGlobalCorrectness();
165   NS_ENSURE_SUCCESS(rv, rv);
166 
167   nsString errorMsg;
168   if (NS_FAILED(aReason)) {
169     nsCString name, message;
170     rv = NS_GetNameAndMessageForDOMNSResult(aReason, name, message);
171     if (NS_SUCCEEDED(rv)) {
172       CopyASCIItoUTF16(message, errorMsg);
173     }
174   }
175 
176   RefPtr<ImageCaptureError> error =
177       new ImageCaptureError(this, aErrorCode, errorMsg);
178 
179   ImageCaptureErrorEventInit init;
180   init.mBubbles = false;
181   init.mCancelable = false;
182   init.mImageCaptureError = error;
183 
184   RefPtr<Event> event =
185       ImageCaptureErrorEvent::Constructor(this, u"error"_ns, init);
186 
187   return DispatchTrustedEvent(event);
188 }
189 
CheckPrincipal()190 bool ImageCapture::CheckPrincipal() {
191   MOZ_ASSERT(NS_IsMainThread());
192 
193   nsCOMPtr<nsIPrincipal> principal = mTrack->GetPrincipal();
194 
195   if (!GetOwner()) {
196     return false;
197   }
198   nsCOMPtr<Document> doc = GetOwner()->GetExtantDoc();
199   if (!doc || !principal) {
200     return false;
201   }
202 
203   bool subsumes;
204   if (NS_FAILED(doc->NodePrincipal()->Subsumes(principal, &subsumes))) {
205     return false;
206   }
207 
208   return subsumes;
209 }
210 
211 }  // namespace dom
212 }  // namespace mozilla
213