/* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "tab_capturer.h" #include "desktop_device_info.h" #include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_frame.h" #include "mozilla/Logging.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/gfx/2D.h" #include "mozilla/SpinEventLoopUntil.h" #include "mozilla/TaskQueue.h" #include "nsThreadUtils.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" mozilla::LazyLogModule gTabShareLog("TabShare"); #define LOG_FUNC_IMPL(level) \ MOZ_LOG( \ gTabShareLog, level, \ ("TabCapturerWebrtc %p: %s id=%" PRIu64, this, __func__, mBrowserId)) #define LOG_FUNC() LOG_FUNC_IMPL(LogLevel::Debug) #define LOG_FUNCV() LOG_FUNC_IMPL(LogLevel::Verbose) using namespace mozilla::dom; namespace mozilla { class CaptureFrameRequest { using CapturePromise = TabCapturerWebrtc::CapturePromise; public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CaptureFrameRequest) CaptureFrameRequest() : mCaptureTime(TimeStamp::Now()) {} operator MozPromiseRequestHolder&() { return mRequest; } void Complete() { mRequest.Complete(); } void Disconnect() { mRequest.Disconnect(); } bool Exists() { return mRequest.Exists(); } protected: virtual ~CaptureFrameRequest() { MOZ_RELEASE_ASSERT(!Exists()); } public: const TimeStamp mCaptureTime; private: MozPromiseRequestHolder mRequest; }; TabCapturerWebrtc::TabCapturerWebrtc( SourceId aSourceId, nsCOMPtr aCaptureThread) : mBrowserId(aSourceId), mMainThreadWorker( TaskQueue::Create(do_AddRef(GetMainThreadSerialEventTarget()), "TabCapturerWebrtc::mMainThreadWorker")), mCallbackWorker(TaskQueue::Create(aCaptureThread.forget(), "TabCapturerWebrtc::mCallbackWorker")) { RTC_DCHECK_RUN_ON(&mControlChecker); MOZ_ASSERT(aSourceId != 0); mCallbackChecker.Detach(); LOG_FUNC(); } // static std::unique_ptr TabCapturerWebrtc::Create( SourceId aSourceId, nsCOMPtr aCaptureThread) { return std::unique_ptr( new TabCapturerWebrtc(aSourceId, std::move(aCaptureThread))); } TabCapturerWebrtc::~TabCapturerWebrtc() { RTC_DCHECK_RUN_ON(&mCallbackChecker); LOG_FUNC(); // mMainThreadWorker handles frame capture requests async. Since we're in the // dtor, no more frame capture requests can be made through CaptureFrame(). It // can be shut down now. mMainThreadWorker->BeginShutdown(); // There may still be async frame capture requests in flight, waiting to be // reported to mCallback on mCallbackWorker. Disconnect them (must be done on // mCallbackWorker) and shut down mCallbackWorker to ensure nothing more can // get queued to it. MOZ_ALWAYS_SUCCEEDS( mCallbackWorker->Dispatch(NS_NewRunnableFunction(__func__, [this] { RTC_DCHECK_RUN_ON(&mCallbackChecker); for (const auto& req : mRequests) { DisconnectRequest(req); } mCallbackWorker->BeginShutdown(); }))); // Block until the workers have run all pending tasks. We must do this for two // reasons: // - All runnables dispatched to mMainThreadWorker and mCallbackWorker capture // the raw pointer `this` as they rely on `this` outliving the worker // TaskQueues. // - mCallback is only guaranteed to outlive `this`. No calls can be made to // it after the dtor is finished. // Spin the underlying thread of mCallbackWorker, which we are currently on, // until it is empty. We have no other way of waiting for mCallbackWorker to // become empty while blocking the current call. SpinEventLoopUntil( "~TabCapturerWebrtc"_ns, [&] { return mCallbackWorker->IsEmpty(); }); // No need to await shutdown since it was shut down synchronously above. mMainThreadWorker->AwaitIdle(); } bool TabCapturerWebrtc::GetSourceList( webrtc::DesktopCapturer::SourceList* aSources) { MOZ_LOG(gTabShareLog, LogLevel::Debug, ("TabShare: GetSourceList, result %zu", aSources->size())); // XXX UI return true; } bool TabCapturerWebrtc::SelectSource(webrtc::DesktopCapturer::SourceId) { MOZ_ASSERT_UNREACHABLE("Source is passed through ctor for constness"); return true; } bool TabCapturerWebrtc::FocusOnSelectedSource() { return true; } void TabCapturerWebrtc::Start(webrtc::DesktopCapturer::Callback* aCallback) { RTC_DCHECK_RUN_ON(&mCallbackChecker); RTC_DCHECK(!mCallback); RTC_DCHECK(aCallback); LOG_FUNC(); mCallback = aCallback; } void TabCapturerWebrtc::CaptureFrame() { RTC_DCHECK_RUN_ON(&mCallbackChecker); LOG_FUNCV(); if (mRequests.GetSize() > 2) { // Allow two async capture requests in flight OnCaptureFrameFailure(); return; } auto request = MakeRefPtr(); InvokeAsync(mMainThreadWorker, __func__, [this] { return CaptureFrameNow(); }) ->Then(mCallbackWorker, __func__, [this, request](CapturePromise::ResolveOrRejectValue&& aValue) { if (!CompleteRequest(request)) { // Request was disconnected or overrun. Failure has already // been reported to the callback elsewhere. return; } if (aValue.IsReject()) { OnCaptureFrameFailure(); return; } OnCaptureFrameSuccess(std::move(aValue.ResolveValue())); }) ->Track(*request); mRequests.PushFront(request.forget()); } void TabCapturerWebrtc::OnCaptureFrameSuccess( UniquePtr aData) { RTC_DCHECK_RUN_ON(&mCallbackChecker); MOZ_DIAGNOSTIC_ASSERT(aData); LOG_FUNCV(); webrtc::DesktopSize size(aData->mPictureRect.Width(), aData->mPictureRect.Height()); webrtc::DesktopRect rect = webrtc::DesktopRect::MakeSize(size); std::unique_ptr frame( new webrtc::BasicDesktopFrame(size)); gfx::DataSourceSurface::ScopedMap map(aData->mSurface, gfx::DataSourceSurface::READ); if (!map.IsMapped()) { OnCaptureFrameFailure(); return; } frame->CopyPixelsFrom(map.GetData(), map.GetStride(), rect); mCallback->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS, std::move(frame)); } void TabCapturerWebrtc::OnCaptureFrameFailure() { RTC_DCHECK_RUN_ON(&mCallbackChecker); LOG_FUNC(); mCallback->OnCaptureResult(webrtc::DesktopCapturer::Result::ERROR_TEMPORARY, nullptr); } bool TabCapturerWebrtc::IsOccluded(const webrtc::DesktopVector& aPos) { return false; } class TabCapturedHandler final : public PromiseNativeHandler { public: NS_DECL_ISUPPORTS using CapturePromise = TabCapturerWebrtc::CapturePromise; static void Create(Promise* aPromise, MozPromiseHolder aHolder) { MOZ_ASSERT(aPromise); MOZ_ASSERT(NS_IsMainThread()); RefPtr handler = new TabCapturedHandler(std::move(aHolder)); aPromise->AppendNativeHandler(handler); } void ResolvedCallback(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) override { MOZ_ASSERT(NS_IsMainThread()); if (NS_WARN_IF(!aValue.isObject())) { mHolder.Reject(NS_ERROR_UNEXPECTED, __func__); return; } RefPtr bitmap; if (NS_WARN_IF(NS_FAILED( UNWRAP_OBJECT(ImageBitmap, &aValue.toObject(), bitmap)))) { mHolder.Reject(NS_ERROR_UNEXPECTED, __func__); return; } UniquePtr data = bitmap->ToCloneData(); if (!data) { mHolder.Reject(NS_ERROR_UNEXPECTED, __func__); return; } mHolder.Resolve(std::move(data), __func__); } void RejectedCallback(JSContext* aCx, JS::Handle aValue, ErrorResult& aRv) override { MOZ_ASSERT(NS_IsMainThread()); mHolder.Reject(aRv.StealNSResult(), __func__); } private: explicit TabCapturedHandler(MozPromiseHolder aHolder) : mHolder(std::move(aHolder)) {} ~TabCapturedHandler() = default; MozPromiseHolder mHolder; }; NS_IMPL_ISUPPORTS0(TabCapturedHandler) bool TabCapturerWebrtc::CompleteRequest(CaptureFrameRequest* aRequest) { RTC_DCHECK_RUN_ON(&mCallbackChecker); if (!aRequest->Exists()) { // Request was disconnected or overrun. mCallback has already been notified. return false; } while (CaptureFrameRequest* req = mRequests.Peek()) { if (req->mCaptureTime > aRequest->mCaptureTime) { break; } // Pop the request before calling the callback, in case it could mutate // mRequests, now or in the future. RefPtr dropMe = mRequests.Pop(); req->Complete(); if (req->mCaptureTime < aRequest->mCaptureTime) { OnCaptureFrameFailure(); } } MOZ_DIAGNOSTIC_ASSERT(!aRequest->Exists()); return true; } void TabCapturerWebrtc::DisconnectRequest(CaptureFrameRequest* aRequest) { RTC_DCHECK_RUN_ON(&mCallbackChecker); LOG_FUNCV(); aRequest->Disconnect(); OnCaptureFrameFailure(); } auto TabCapturerWebrtc::CaptureFrameNow() -> RefPtr { MOZ_ASSERT(mMainThreadWorker->IsOnCurrentThread()); LOG_FUNCV(); WindowGlobalParent* wgp = nullptr; RefPtr context = BrowsingContext::GetCurrentTopByBrowserId(mBrowserId); if (context) { wgp = context->Canonical()->GetCurrentWindowGlobal(); } if (!wgp) { // If we can't access the window, we just won't capture anything return CapturePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); } // XXX This would be more efficient if we used CrossProcessPaint directly and // returned a surface. RefPtr promise = wgp->DrawSnapshot(nullptr, 1.0, "white"_ns, false, IgnoreErrors()); if (!promise) { return CapturePromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } MozPromiseHolder holder; RefPtr p = holder.Ensure(__func__); TabCapturedHandler::Create(promise, std::move(holder)); return p; } } // namespace mozilla