tab_capturer.cc (10924B)
1 /* 2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include "tab_capturer.h" 12 13 #include "desktop_device_info.h" 14 #include "modules/desktop_capture/desktop_capture_options.h" 15 #include "modules/desktop_capture/desktop_frame.h" 16 #include "mozilla/Logging.h" 17 #include "mozilla/SpinEventLoopUntil.h" 18 #include "mozilla/TaskQueue.h" 19 #include "mozilla/dom/BrowsingContext.h" 20 #include "mozilla/dom/ImageBitmap.h" 21 #include "mozilla/dom/Promise.h" 22 #include "mozilla/dom/PromiseNativeHandler.h" 23 #include "mozilla/dom/WindowGlobalParent.h" 24 #include "mozilla/gfx/2D.h" 25 #include "nsThreadUtils.h" 26 #include "rtc_base/checks.h" 27 #include "rtc_base/logging.h" 28 29 mozilla::LazyLogModule gTabShareLog("TabShare"); 30 #define LOG_FUNC_IMPL(level) \ 31 MOZ_LOG( \ 32 gTabShareLog, level, \ 33 ("TabCapturerWebrtc %p: %s id=%" PRIu64, this, __func__, mBrowserId)) 34 #define LOG_FUNC() LOG_FUNC_IMPL(LogLevel::Debug) 35 #define LOG_FUNCV() LOG_FUNC_IMPL(LogLevel::Verbose) 36 37 using namespace mozilla::dom; 38 39 namespace mozilla { 40 41 class CaptureFrameRequest { 42 using CapturePromise = TabCapturerWebrtc::CapturePromise; 43 44 public: 45 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CaptureFrameRequest) 46 47 CaptureFrameRequest() : mCaptureTime(TimeStamp::Now()) {} 48 49 operator MozPromiseRequestHolder<CapturePromise>&() { return mRequest; } 50 51 void Complete() { mRequest.Complete(); } 52 void Disconnect() { mRequest.Disconnect(); } 53 bool Exists() { return mRequest.Exists(); } 54 55 protected: 56 virtual ~CaptureFrameRequest() { MOZ_RELEASE_ASSERT(!Exists()); } 57 58 public: 59 const TimeStamp mCaptureTime; 60 61 private: 62 MozPromiseRequestHolder<CapturePromise> mRequest; 63 }; 64 65 TabCapturerWebrtc::TabCapturerWebrtc( 66 SourceId aSourceId, nsCOMPtr<nsISerialEventTarget> aCaptureThread) 67 : mBrowserId(aSourceId), 68 mMainThreadWorker( 69 TaskQueue::Create(do_AddRef(GetMainThreadSerialEventTarget()), 70 "TabCapturerWebrtc::mMainThreadWorker")), 71 mCallbackWorker(TaskQueue::Create(aCaptureThread.forget(), 72 "TabCapturerWebrtc::mCallbackWorker")) { 73 RTC_DCHECK_RUN_ON(&mControlChecker); 74 MOZ_ASSERT(aSourceId != 0); 75 mCallbackChecker.Detach(); 76 77 LOG_FUNC(); 78 } 79 80 // static 81 std::unique_ptr<webrtc::DesktopCapturer> TabCapturerWebrtc::Create( 82 SourceId aSourceId, nsCOMPtr<nsISerialEventTarget> aCaptureThread) { 83 return std::unique_ptr<webrtc::DesktopCapturer>( 84 new TabCapturerWebrtc(aSourceId, std::move(aCaptureThread))); 85 } 86 87 TabCapturerWebrtc::~TabCapturerWebrtc() { 88 RTC_DCHECK_RUN_ON(&mCallbackChecker); 89 LOG_FUNC(); 90 91 // mMainThreadWorker handles frame capture requests async. Since we're in the 92 // dtor, no more frame capture requests can be made through CaptureFrame(). It 93 // can be shut down now. 94 mMainThreadWorker->BeginShutdown(); 95 96 // There may still be async frame capture requests in flight, waiting to be 97 // reported to mCallback on mCallbackWorker. Disconnect them (must be done on 98 // mCallbackWorker) and shut down mCallbackWorker to ensure nothing more can 99 // get queued to it. 100 MOZ_ALWAYS_SUCCEEDS( 101 mCallbackWorker->Dispatch(NS_NewRunnableFunction(__func__, [this] { 102 RTC_DCHECK_RUN_ON(&mCallbackChecker); 103 for (const auto& req : mRequests) { 104 DisconnectRequest(req); 105 } 106 mCallbackWorker->BeginShutdown(); 107 }))); 108 109 // Block until the workers have run all pending tasks. We must do this for two 110 // reasons: 111 // - All runnables dispatched to mMainThreadWorker and mCallbackWorker capture 112 // the raw pointer `this` as they rely on `this` outliving the worker 113 // TaskQueues. 114 // - mCallback is only guaranteed to outlive `this`. No calls can be made to 115 // it after the dtor is finished. 116 117 // Spin the underlying thread of mCallbackWorker, which we are currently on, 118 // until it is empty. We have no other way of waiting for mCallbackWorker to 119 // become empty while blocking the current call. 120 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( 121 "~TabCapturerWebrtc"_ns, [&] { return mCallbackWorker->IsEmpty(); }); 122 123 // No need to await shutdown since it was shut down synchronously above. 124 mMainThreadWorker->AwaitIdle(); 125 } 126 127 bool TabCapturerWebrtc::GetSourceList( 128 webrtc::DesktopCapturer::SourceList* aSources) { 129 MOZ_LOG(gTabShareLog, LogLevel::Debug, 130 ("TabShare: GetSourceList, result %zu", aSources->size())); 131 // XXX UI 132 return true; 133 } 134 135 bool TabCapturerWebrtc::SelectSource(webrtc::DesktopCapturer::SourceId) { 136 MOZ_ASSERT_UNREACHABLE("Source is passed through ctor for constness"); 137 return true; 138 } 139 140 bool TabCapturerWebrtc::FocusOnSelectedSource() { return true; } 141 142 void TabCapturerWebrtc::Start(webrtc::DesktopCapturer::Callback* aCallback) { 143 RTC_DCHECK_RUN_ON(&mCallbackChecker); 144 RTC_DCHECK(!mCallback); 145 RTC_DCHECK(aCallback); 146 147 LOG_FUNC(); 148 149 mCallback = aCallback; 150 } 151 152 void TabCapturerWebrtc::CaptureFrame() { 153 RTC_DCHECK_RUN_ON(&mCallbackChecker); 154 LOG_FUNCV(); 155 if (mRequests.GetSize() > 2) { 156 // Allow two async capture requests in flight 157 OnCaptureFrameFailure(); 158 return; 159 } 160 161 auto request = MakeRefPtr<CaptureFrameRequest>(); 162 InvokeAsync(mMainThreadWorker, __func__, [this] { return CaptureFrameNow(); }) 163 ->Then(mCallbackWorker, __func__, 164 [this, request](CapturePromise::ResolveOrRejectValue&& aValue) { 165 if (!CompleteRequest(request)) { 166 // Request was disconnected or overrun. Failure has already 167 // been reported to the callback elsewhere. 168 return; 169 } 170 171 if (aValue.IsReject()) { 172 OnCaptureFrameFailure(); 173 return; 174 } 175 176 OnCaptureFrameSuccess(std::move(aValue.ResolveValue())); 177 }) 178 ->Track(*request); 179 mRequests.PushFront(request.forget()); 180 } 181 182 void TabCapturerWebrtc::OnCaptureFrameSuccess( 183 UniquePtr<dom::ImageBitmapCloneData> aData) { 184 RTC_DCHECK_RUN_ON(&mCallbackChecker); 185 MOZ_DIAGNOSTIC_ASSERT(aData); 186 LOG_FUNCV(); 187 webrtc::DesktopSize size(aData->mPictureRect.Width(), 188 aData->mPictureRect.Height()); 189 webrtc::DesktopRect rect = webrtc::DesktopRect::MakeSize(size); 190 std::unique_ptr<webrtc::DesktopFrame> frame( 191 new webrtc::BasicDesktopFrame(size)); 192 193 gfx::DataSourceSurface::ScopedMap map(aData->mSurface, 194 gfx::DataSourceSurface::READ); 195 if (!map.IsMapped()) { 196 OnCaptureFrameFailure(); 197 return; 198 } 199 frame->CopyPixelsFrom(map.GetData(), map.GetStride(), rect); 200 201 mCallback->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS, 202 std::move(frame)); 203 } 204 205 void TabCapturerWebrtc::OnCaptureFrameFailure() { 206 RTC_DCHECK_RUN_ON(&mCallbackChecker); 207 LOG_FUNC(); 208 mCallback->OnCaptureResult(webrtc::DesktopCapturer::Result::ERROR_TEMPORARY, 209 nullptr); 210 } 211 212 bool TabCapturerWebrtc::IsOccluded(const webrtc::DesktopVector& aPos) { 213 return false; 214 } 215 216 class TabCapturedHandler final : public PromiseNativeHandler { 217 public: 218 NS_DECL_ISUPPORTS 219 220 using CapturePromise = TabCapturerWebrtc::CapturePromise; 221 222 static void Create(Promise* aPromise, 223 MozPromiseHolder<CapturePromise> aHolder) { 224 MOZ_ASSERT(aPromise); 225 MOZ_ASSERT(NS_IsMainThread()); 226 227 RefPtr<TabCapturedHandler> handler = 228 new TabCapturedHandler(std::move(aHolder)); 229 aPromise->AppendNativeHandler(handler); 230 } 231 232 void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 233 ErrorResult& aRv) override { 234 MOZ_ASSERT(NS_IsMainThread()); 235 if (NS_WARN_IF(!aValue.isObject())) { 236 mHolder.Reject(NS_ERROR_UNEXPECTED, __func__); 237 return; 238 } 239 240 RefPtr<ImageBitmap> bitmap; 241 if (NS_WARN_IF(NS_FAILED( 242 UNWRAP_OBJECT(ImageBitmap, &aValue.toObject(), bitmap)))) { 243 mHolder.Reject(NS_ERROR_UNEXPECTED, __func__); 244 return; 245 } 246 247 UniquePtr<ImageBitmapCloneData> data = bitmap->ToCloneData(); 248 if (!data) { 249 mHolder.Reject(NS_ERROR_UNEXPECTED, __func__); 250 return; 251 } 252 253 mHolder.Resolve(std::move(data), __func__); 254 } 255 256 void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 257 ErrorResult& aRv) override { 258 MOZ_ASSERT(NS_IsMainThread()); 259 mHolder.Reject(aRv.StealNSResult(), __func__); 260 } 261 262 private: 263 explicit TabCapturedHandler(MozPromiseHolder<CapturePromise> aHolder) 264 : mHolder(std::move(aHolder)) {} 265 266 ~TabCapturedHandler() = default; 267 268 MozPromiseHolder<CapturePromise> mHolder; 269 }; 270 271 NS_IMPL_ISUPPORTS0(TabCapturedHandler) 272 273 bool TabCapturerWebrtc::CompleteRequest(CaptureFrameRequest* aRequest) { 274 RTC_DCHECK_RUN_ON(&mCallbackChecker); 275 if (!aRequest->Exists()) { 276 // Request was disconnected or overrun. mCallback has already been notified. 277 return false; 278 } 279 while (CaptureFrameRequest* req = mRequests.Peek()) { 280 if (req->mCaptureTime > aRequest->mCaptureTime) { 281 break; 282 } 283 // Pop the request before calling the callback, in case it could mutate 284 // mRequests, now or in the future. 285 RefPtr<CaptureFrameRequest> dropMe = mRequests.Pop(); 286 req->Complete(); 287 if (req->mCaptureTime < aRequest->mCaptureTime) { 288 OnCaptureFrameFailure(); 289 } 290 } 291 MOZ_DIAGNOSTIC_ASSERT(!aRequest->Exists()); 292 return true; 293 } 294 295 void TabCapturerWebrtc::DisconnectRequest(CaptureFrameRequest* aRequest) { 296 RTC_DCHECK_RUN_ON(&mCallbackChecker); 297 LOG_FUNCV(); 298 aRequest->Disconnect(); 299 OnCaptureFrameFailure(); 300 } 301 302 auto TabCapturerWebrtc::CaptureFrameNow() -> RefPtr<CapturePromise> { 303 MOZ_ASSERT(mMainThreadWorker->IsOnCurrentThread()); 304 LOG_FUNCV(); 305 306 WindowGlobalParent* wgp = nullptr; 307 RefPtr<BrowsingContext> context = 308 BrowsingContext::GetCurrentTopByBrowserId(mBrowserId); 309 if (context) { 310 wgp = context->Canonical()->GetCurrentWindowGlobal(); 311 } 312 if (!wgp) { 313 // If we can't access the window, we just won't capture anything 314 return CapturePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); 315 } 316 317 // XXX This would be more efficient if we used CrossProcessPaint directly and 318 // returned a surface. 319 RefPtr<Promise> promise = 320 wgp->DrawSnapshot(nullptr, 1.0, "white"_ns, false, IgnoreErrors()); 321 if (!promise) { 322 return CapturePromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 323 } 324 325 MozPromiseHolder<CapturePromise> holder; 326 RefPtr<CapturePromise> p = holder.Ensure(__func__); 327 TabCapturedHandler::Create(promise, std::move(holder)); 328 return p; 329 } 330 331 } // namespace mozilla