summaryrefslogtreecommitdiffstats
path: root/dom/media/systemservices/video_engine/tab_capturer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/systemservices/video_engine/tab_capturer.cc')
-rw-r--r--dom/media/systemservices/video_engine/tab_capturer.cc331
1 files changed, 331 insertions, 0 deletions
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<CapturePromise>&() { 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<CapturePromise> mRequest;
+};
+
+TabCapturerWebrtc::TabCapturerWebrtc(
+ SourceId aSourceId, nsCOMPtr<nsISerialEventTarget> 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<webrtc::DesktopCapturer> TabCapturerWebrtc::Create(
+ SourceId aSourceId, nsCOMPtr<nsISerialEventTarget> aCaptureThread) {
+ return std::unique_ptr<webrtc::DesktopCapturer>(
+ 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<ProcessFailureBehavior::IgnoreAndContinue>(
+ "~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<CaptureFrameRequest>();
+ 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<dom::ImageBitmapCloneData> 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<webrtc::DesktopFrame> 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<CapturePromise> aHolder) {
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<TabCapturedHandler> handler =
+ new TabCapturedHandler(std::move(aHolder));
+ aPromise->AppendNativeHandler(handler);
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (NS_WARN_IF(!aValue.isObject())) {
+ mHolder.Reject(NS_ERROR_UNEXPECTED, __func__);
+ return;
+ }
+
+ RefPtr<ImageBitmap> bitmap;
+ if (NS_WARN_IF(NS_FAILED(
+ UNWRAP_OBJECT(ImageBitmap, &aValue.toObject(), bitmap)))) {
+ mHolder.Reject(NS_ERROR_UNEXPECTED, __func__);
+ return;
+ }
+
+ UniquePtr<ImageBitmapCloneData> data = bitmap->ToCloneData();
+ if (!data) {
+ mHolder.Reject(NS_ERROR_UNEXPECTED, __func__);
+ return;
+ }
+
+ mHolder.Resolve(std::move(data), __func__);
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ mHolder.Reject(aRv.StealNSResult(), __func__);
+ }
+
+ private:
+ explicit TabCapturedHandler(MozPromiseHolder<CapturePromise> aHolder)
+ : mHolder(std::move(aHolder)) {}
+
+ ~TabCapturedHandler() = default;
+
+ MozPromiseHolder<CapturePromise> 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<CaptureFrameRequest> 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<CapturePromise> {
+ MOZ_ASSERT(mMainThreadWorker->IsOnCurrentThread());
+ LOG_FUNCV();
+
+ WindowGlobalParent* wgp = nullptr;
+ RefPtr<BrowsingContext> 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> promise =
+ wgp->DrawSnapshot(nullptr, 1.0, "white"_ns, false, IgnoreErrors());
+ if (!promise) {
+ return CapturePromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ MozPromiseHolder<CapturePromise> holder;
+ RefPtr<CapturePromise> p = holder.Ensure(__func__);
+ TabCapturedHandler::Create(promise, std::move(holder));
+ return p;
+}
+
+} // namespace mozilla