From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../systemservices/video_engine/tab_capturer.cc | 331 +++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 dom/media/systemservices/video_engine/tab_capturer.cc (limited to 'dom/media/systemservices/video_engine/tab_capturer.cc') diff --git a/dom/media/systemservices/video_engine/tab_capturer.cc b/dom/media/systemservices/video_engine/tab_capturer.cc new file mode 100644 index 0000000000..793d965028 --- /dev/null +++ b/dom/media/systemservices/video_engine/tab_capturer.cc @@ -0,0 +1,331 @@ +/* + * 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 -- cgit v1.2.3