tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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