summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source_unittest.cc
blob: 13c693f8e07f12911f7899721912f6c37be50309 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/modules/mediacapturefromelement/html_video_element_capturer_source.h"

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "cc/paint/paint_canvas.h"
#include "cc/paint/paint_flags.h"
#include "media/base/limits.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/public/platform/web_media_player.h"
#include "third_party/blink/public/platform/web_rect.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"

using base::test::RunOnceClosure;
using ::testing::_;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::SaveArg;

namespace blink {

namespace {

// An almost empty WebMediaPlayer to override paint() method.
class MockWebMediaPlayer : public WebMediaPlayer {
 public:
  MockWebMediaPlayer() {}
  ~MockWebMediaPlayer() override = default;

  LoadTiming Load(LoadType, const WebMediaPlayerSource&, CorsMode) override {
    return LoadTiming::kImmediate;
  }
  void Play() override {}
  void Pause() override {}
  void Seek(double seconds) override {}
  void SetRate(double) override {}
  void SetVolume(double) override {}
  void SetLatencyHint(double) override {}
  void SetPreservesPitch(bool) override {}
  void OnRequestPictureInPicture() override {}
  void OnPictureInPictureAvailabilityChanged(bool available) override {}
  WebTimeRanges Buffered() const override { return WebTimeRanges(); }
  WebTimeRanges Seekable() const override { return WebTimeRanges(); }
  void SetSinkId(const WebString& sinkId,
                 WebSetSinkIdCompleteCallback) override {}
  bool HasVideo() const override { return true; }
  bool HasAudio() const override { return false; }
  gfx::Size NaturalSize() const override { return size_; }
  gfx::Size VisibleSize() const override { return size_; }
  bool Paused() const override { return false; }
  bool Seeking() const override { return false; }
  double Duration() const override { return 0.0; }
  double CurrentTime() const override { return 0.0; }
  bool IsEnded() const override { return false; }
  NetworkState GetNetworkState() const override { return kNetworkStateEmpty; }
  ReadyState GetReadyState() const override { return kReadyStateHaveNothing; }
  SurfaceLayerMode GetVideoSurfaceLayerMode() const override {
    return SurfaceLayerMode::kNever;
  }
  WebString GetErrorMessage() const override { return WebString(); }

  bool DidLoadingProgress() override { return true; }
  bool WouldTaintOrigin() const override { return would_taint_origin_; }
  double MediaTimeForTimeValue(double timeValue) const override { return 0.0; }
  unsigned DecodedFrameCount() const override { return 0; }
  unsigned DroppedFrameCount() const override { return 0; }
  unsigned CorruptedFrameCount() const override { return 0; }
  uint64_t AudioDecodedByteCount() const override { return 0; }
  uint64_t VideoDecodedByteCount() const override { return 0; }

  void SetWouldTaintOrigin(bool taint) { would_taint_origin_ = taint; }

  void Paint(cc::PaintCanvas* canvas,
             const WebRect& rect,
             cc::PaintFlags&,
             int already_uploaded_id,
             VideoFrameUploadMetadata* out_metadata) override {
    // We could fill in |canvas| with a meaningful pattern in ARGB and verify
    // that is correctly captured (as I420) by HTMLVideoElementCapturerSource
    // but I don't think that'll be easy/useful/robust, so just let go here.
    return;
  }
  bool IsOpaque() const override { return is_video_opaque_; }
  bool HasAvailableVideoFrame() const override { return true; }

  base::WeakPtr<WebMediaPlayer> AsWeakPtr() override {
    return weak_factory_.GetWeakPtr();
  }

  bool is_video_opaque_ = true;
  gfx::Size size_ = gfx::Size(16, 10);
  bool would_taint_origin_ = false;

  base::WeakPtrFactory<MockWebMediaPlayer> weak_factory_{this};
};

}  // namespace

class HTMLVideoElementCapturerSourceTest : public testing::TestWithParam<bool> {
 public:
  HTMLVideoElementCapturerSourceTest()
      : web_media_player_(new MockWebMediaPlayer()),
        html_video_capturer_(new HtmlVideoElementCapturerSource(
            web_media_player_->AsWeakPtr(),
            scheduler::GetSingleThreadTaskRunnerForTesting(),
            scheduler::GetSingleThreadTaskRunnerForTesting())) {}

  // Necessary callbacks and MOCK_METHODS for them.
  MOCK_METHOD2(DoOnDeliverFrame,
               void(scoped_refptr<media::VideoFrame>, base::TimeTicks));
  void OnDeliverFrame(scoped_refptr<media::VideoFrame> video_frame,
                      base::TimeTicks estimated_capture_time) {
    DoOnDeliverFrame(std::move(video_frame), estimated_capture_time);
  }

  MOCK_METHOD1(DoOnRunning, void(bool));
  void OnRunning(bool state) { DoOnRunning(state); }

  void SetVideoPlayerOpacity(bool opacity) {
    web_media_player_->is_video_opaque_ = opacity;
  }

  void SetVideoPlayerSize(const gfx::Size& size) {
    web_media_player_->size_ = size;
  }

 protected:
  std::unique_ptr<MockWebMediaPlayer> web_media_player_;
  std::unique_ptr<HtmlVideoElementCapturerSource> html_video_capturer_;
};

// Constructs and destructs all objects, in particular |html_video_capturer_|
// and its inner object(s). This is a non trivial sequence.
TEST_F(HTMLVideoElementCapturerSourceTest, ConstructAndDestruct) {}

// Checks that the usual sequence of GetPreferredFormats() ->
// StartCapture() -> StopCapture() works as expected and let it capture two
// frames, that are tested for format vs the expected source opacity.
TEST_P(HTMLVideoElementCapturerSourceTest, GetFormatsAndStartAndStop) {
  InSequence s;
  media::VideoCaptureFormats formats =
      html_video_capturer_->GetPreferredFormats();
  ASSERT_EQ(1u, formats.size());
  EXPECT_EQ(web_media_player_->NaturalSize(), formats[0].frame_size);

  media::VideoCaptureParams params;
  params.requested_format = formats[0];

  EXPECT_CALL(*this, DoOnRunning(true)).Times(1);

  const bool is_video_opaque = GetParam();
  SetVideoPlayerOpacity(is_video_opaque);

  base::RunLoop run_loop;
  base::RepeatingClosure quit_closure = run_loop.QuitClosure();
  scoped_refptr<media::VideoFrame> first_frame;
  scoped_refptr<media::VideoFrame> second_frame;
  EXPECT_CALL(*this, DoOnDeliverFrame(_, _)).WillOnce(SaveArg<0>(&first_frame));
  EXPECT_CALL(*this, DoOnDeliverFrame(_, _))
      .Times(1)
      .WillOnce(DoAll(SaveArg<0>(&second_frame),
                      RunOnceClosure(std::move(quit_closure))));

  html_video_capturer_->StartCapture(
      params,
      WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnDeliverFrame,
                         base::Unretained(this)),
      WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnRunning,
                         base::Unretained(this)));

  run_loop.Run();

  EXPECT_EQ(0u, first_frame->timestamp().InMilliseconds());
  EXPECT_GT(second_frame->timestamp().InMilliseconds(), 30u);
  if (is_video_opaque)
    EXPECT_EQ(media::PIXEL_FORMAT_I420, first_frame->format());
  else
    EXPECT_EQ(media::PIXEL_FORMAT_I420A, first_frame->format());

  html_video_capturer_->StopCapture();
  Mock::VerifyAndClearExpectations(this);
}

INSTANTIATE_TEST_SUITE_P(All,
                         HTMLVideoElementCapturerSourceTest,
                         ::testing::Bool());

// When a new source is created and started, it is stopped in the same task
// when cross-origin data is detected. This test checks that no data is
// delivered in this case.
TEST_F(HTMLVideoElementCapturerSourceTest,
       StartAndStopInSameTaskCaptureZeroFrames) {
  InSequence s;
  media::VideoCaptureFormats formats =
      html_video_capturer_->GetPreferredFormats();
  ASSERT_EQ(1u, formats.size());
  EXPECT_EQ(web_media_player_->NaturalSize(), formats[0].frame_size);

  media::VideoCaptureParams params;
  params.requested_format = formats[0];

  EXPECT_CALL(*this, DoOnRunning(true));
  EXPECT_CALL(*this, DoOnDeliverFrame(_, _)).Times(0);

  html_video_capturer_->StartCapture(
      params,
      WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnDeliverFrame,
                         base::Unretained(this)),
      WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnRunning,
                         base::Unretained(this)));
  html_video_capturer_->StopCapture();
  base::RunLoop().RunUntilIdle();

  Mock::VerifyAndClearExpectations(this);
}

// Verify that changes in the opacicty of the source WebMediaPlayer are followed
// by corresponding changes in the format of the captured VideoFrame.
TEST_F(HTMLVideoElementCapturerSourceTest, AlphaAndNot) {
  InSequence s;
  media::VideoCaptureFormats formats =
      html_video_capturer_->GetPreferredFormats();
  media::VideoCaptureParams params;
  params.requested_format = formats[0];

  {
    SetVideoPlayerOpacity(false);

    base::RunLoop run_loop;
    base::RepeatingClosure quit_closure = run_loop.QuitClosure();
    scoped_refptr<media::VideoFrame> frame;
    EXPECT_CALL(*this, DoOnRunning(true)).Times(1);
    EXPECT_CALL(*this, DoOnDeliverFrame(_, _))
        .WillOnce(
            DoAll(SaveArg<0>(&frame), RunOnceClosure(std::move(quit_closure))));
    html_video_capturer_->StartCapture(
        params,
        WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnDeliverFrame,
                           base::Unretained(this)),
        WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnRunning,
                           base::Unretained(this)));
    run_loop.Run();

    EXPECT_EQ(media::PIXEL_FORMAT_I420A, frame->format());
  }
  {
    SetVideoPlayerOpacity(true);

    base::RunLoop run_loop;
    base::RepeatingClosure quit_closure = run_loop.QuitClosure();
    scoped_refptr<media::VideoFrame> frame;
    EXPECT_CALL(*this, DoOnDeliverFrame(_, _))
        .WillOnce(
            DoAll(SaveArg<0>(&frame), RunOnceClosure(std::move(quit_closure))));
    run_loop.Run();

    EXPECT_EQ(media::PIXEL_FORMAT_I420, frame->format());
  }
  {
    SetVideoPlayerOpacity(false);

    base::RunLoop run_loop;
    base::RepeatingClosure quit_closure = run_loop.QuitClosure();
    scoped_refptr<media::VideoFrame> frame;
    EXPECT_CALL(*this, DoOnDeliverFrame(_, _))
        .WillOnce(
            DoAll(SaveArg<0>(&frame), RunOnceClosure(std::move(quit_closure))));
    run_loop.Run();

    EXPECT_EQ(media::PIXEL_FORMAT_I420A, frame->format());
  }

  html_video_capturer_->StopCapture();
  Mock::VerifyAndClearExpectations(this);
}

// Verify that changes in the natural size of the source WebMediaPlayer do not
// crash.
// TODO(crbug.com/1817203): Verify that size changes are fully supported.
TEST_F(HTMLVideoElementCapturerSourceTest, SizeChange) {
  InSequence s;
  media::VideoCaptureFormats formats =
      html_video_capturer_->GetPreferredFormats();
  media::VideoCaptureParams params;
  params.requested_format = formats[0];

  {
    SetVideoPlayerSize(gfx::Size(16, 10));

    base::RunLoop run_loop;
    base::RepeatingClosure quit_closure = run_loop.QuitClosure();
    scoped_refptr<media::VideoFrame> frame;
    EXPECT_CALL(*this, DoOnRunning(true)).Times(1);
    EXPECT_CALL(*this, DoOnDeliverFrame(_, _))
        .WillOnce(
            DoAll(SaveArg<0>(&frame), RunOnceClosure(std::move(quit_closure))));
    html_video_capturer_->StartCapture(
        params,
        WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnDeliverFrame,
                           base::Unretained(this)),
        WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnRunning,
                           base::Unretained(this)));
    run_loop.Run();
  }
  {
    SetVideoPlayerSize(gfx::Size(32, 20));

    base::RunLoop run_loop;
    base::RepeatingClosure quit_closure = run_loop.QuitClosure();
    scoped_refptr<media::VideoFrame> frame;
    EXPECT_CALL(*this, DoOnDeliverFrame(_, _))
        .WillOnce(
            DoAll(SaveArg<0>(&frame), RunOnceClosure(std::move(quit_closure))));
    run_loop.Run();
  }

  html_video_capturer_->StopCapture();
  Mock::VerifyAndClearExpectations(this);
}

// Checks that the usual sequence of GetPreferredFormats() ->
// StartCapture() -> StopCapture() works as expected and let it capture two
// frames, that are tested for format vs the expected source opacity.
TEST_F(HTMLVideoElementCapturerSourceTest, TaintedPlayerDoesNotDeliverFrames) {
  InSequence s;
  media::VideoCaptureFormats formats =
      html_video_capturer_->GetPreferredFormats();
  ASSERT_EQ(1u, formats.size());
  EXPECT_EQ(web_media_player_->NaturalSize(), formats[0].frame_size);
  web_media_player_->SetWouldTaintOrigin(true);

  media::VideoCaptureParams params;
  params.requested_format = formats[0];

  EXPECT_CALL(*this, DoOnRunning(true)).Times(1);

  // No frames should be delivered.
  EXPECT_CALL(*this, DoOnDeliverFrame(_, _)).Times(0);
  html_video_capturer_->StartCapture(
      params,
      WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnDeliverFrame,
                         base::Unretained(this)),
      WTF::BindRepeating(&HTMLVideoElementCapturerSourceTest::OnRunning,
                         base::Unretained(this)));

  // Wait for frames to be potentially sent in a follow-up task.
  base::RunLoop().RunUntilIdle();

  html_video_capturer_->StopCapture();
  Mock::VerifyAndClearExpectations(this);
}

}  // namespace blink