1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/modules/remoteplayback/remote_playback.h"
6 
7 #include "testing/gmock/include/gmock/gmock.h"
8 #include "testing/gtest/include/gtest/gtest.h"
9 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
10 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
11 #include "third_party/blink/renderer/bindings/modules/v8/v8_remote_playback_availability_callback.h"
12 #include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
13 #include "third_party/blink/renderer/core/frame/local_frame.h"
14 #include "third_party/blink/renderer/core/html/media/html_media_element.h"
15 #include "third_party/blink/renderer/core/html/media/html_video_element.h"
16 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
17 #include "third_party/blink/renderer/modules/presentation/presentation_controller.h"
18 #include "third_party/blink/renderer/modules/remoteplayback/html_media_element_remote_playback.h"
19 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
20 #include "third_party/blink/renderer/platform/heap/heap.h"
21 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
22 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
23 
24 namespace blink {
25 
26 namespace {
27 
28 class MockFunction : public ScriptFunction {
29  public:
Create(ScriptState * script_state)30   static testing::StrictMock<MockFunction>* Create(ScriptState* script_state) {
31     return MakeGarbageCollected<testing::StrictMock<MockFunction>>(
32         script_state);
33   }
34 
Bind()35   v8::Local<v8::Function> Bind() { return BindToV8Function(); }
36 
37   MOCK_METHOD1(Call, ScriptValue(ScriptValue));
38 
39  protected:
MockFunction(ScriptState * script_state)40   explicit MockFunction(ScriptState* script_state)
41       : ScriptFunction(script_state) {}
42 };
43 
44 class MockEventListenerForRemotePlayback : public NativeEventListener {
45  public:
46   MOCK_METHOD2(Invoke, void(ExecutionContext* executionContext, Event*));
47 };
48 
49 class MockPresentationController final : public PresentationController {
50  public:
MockPresentationController(LocalFrame & frame)51   explicit MockPresentationController(LocalFrame& frame)
52       : PresentationController(frame) {}
53   ~MockPresentationController() override = default;
54 
55   MOCK_METHOD1(AddAvailabilityObserver,
56                void(PresentationAvailabilityObserver*));
57   MOCK_METHOD1(RemoveAvailabilityObserver,
58                void(PresentationAvailabilityObserver*));
59 };
60 }  // namespace
61 
62 class RemotePlaybackTest : public testing::Test,
63                            private ScopedRemotePlaybackBackendForTest {
64  public:
RemotePlaybackTest()65   RemotePlaybackTest() : ScopedRemotePlaybackBackendForTest(true) {}
66 
67  protected:
CancelPrompt(RemotePlayback & remote_playback)68   void CancelPrompt(RemotePlayback& remote_playback) {
69     remote_playback.PromptCancelled();
70   }
71 
SetState(RemotePlayback & remote_playback,mojom::blink::PresentationConnectionState state)72   void SetState(RemotePlayback& remote_playback,
73                 mojom::blink::PresentationConnectionState state) {
74     remote_playback.StateChanged(state);
75   }
76 
IsListening(RemotePlayback & remote_playback)77   bool IsListening(RemotePlayback& remote_playback) {
78     return remote_playback.is_listening_;
79   }
80 };
81 
TEST_F(RemotePlaybackTest,PromptCancelledRejectsWithNotAllowedError)82 TEST_F(RemotePlaybackTest, PromptCancelledRejectsWithNotAllowedError) {
83   V8TestingScope scope;
84 
85   auto page_holder = std::make_unique<DummyPageHolder>();
86 
87   auto* element =
88       MakeGarbageCollected<HTMLVideoElement>(page_holder->GetDocument());
89   RemotePlayback& remote_playback = RemotePlayback::From(*element);
90 
91   auto* resolve = MockFunction::Create(scope.GetScriptState());
92   auto* reject = MockFunction::Create(scope.GetScriptState());
93 
94   EXPECT_CALL(*resolve, Call(testing::_)).Times(0);
95   EXPECT_CALL(*reject, Call(testing::_)).Times(1);
96 
97   LocalFrame::NotifyUserActivation(&page_holder->GetFrame());
98   remote_playback.prompt(scope.GetScriptState())
99       .Then(resolve->Bind(), reject->Bind());
100   CancelPrompt(remote_playback);
101 
102   // Runs pending promises.
103   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
104 
105   // Verify mock expectations explicitly as the mock objects are garbage
106   // collected.
107   testing::Mock::VerifyAndClear(resolve);
108   testing::Mock::VerifyAndClear(reject);
109 }
110 
TEST_F(RemotePlaybackTest,PromptConnectedRejectsWhenCancelled)111 TEST_F(RemotePlaybackTest, PromptConnectedRejectsWhenCancelled) {
112   V8TestingScope scope;
113 
114   auto page_holder = std::make_unique<DummyPageHolder>();
115 
116   auto* element =
117       MakeGarbageCollected<HTMLVideoElement>(page_holder->GetDocument());
118   RemotePlayback& remote_playback = RemotePlayback::From(*element);
119 
120   auto* resolve = MockFunction::Create(scope.GetScriptState());
121   auto* reject = MockFunction::Create(scope.GetScriptState());
122 
123   EXPECT_CALL(*resolve, Call(testing::_)).Times(0);
124   EXPECT_CALL(*reject, Call(testing::_)).Times(1);
125 
126   SetState(remote_playback,
127            mojom::blink::PresentationConnectionState::CONNECTED);
128 
129   LocalFrame::NotifyUserActivation(&page_holder->GetFrame());
130   remote_playback.prompt(scope.GetScriptState())
131       .Then(resolve->Bind(), reject->Bind());
132   CancelPrompt(remote_playback);
133 
134   // Runs pending promises.
135   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
136 
137   // Verify mock expectations explicitly as the mock objects are garbage
138   // collected.
139   testing::Mock::VerifyAndClear(resolve);
140   testing::Mock::VerifyAndClear(reject);
141 }
142 
TEST_F(RemotePlaybackTest,PromptConnectedResolvesWhenDisconnected)143 TEST_F(RemotePlaybackTest, PromptConnectedResolvesWhenDisconnected) {
144   V8TestingScope scope;
145 
146   auto page_holder = std::make_unique<DummyPageHolder>();
147 
148   auto* element =
149       MakeGarbageCollected<HTMLVideoElement>(page_holder->GetDocument());
150   RemotePlayback& remote_playback = RemotePlayback::From(*element);
151 
152   auto* resolve = MockFunction::Create(scope.GetScriptState());
153   auto* reject = MockFunction::Create(scope.GetScriptState());
154 
155   EXPECT_CALL(*resolve, Call(testing::_)).Times(1);
156   EXPECT_CALL(*reject, Call(testing::_)).Times(0);
157 
158   SetState(remote_playback,
159            mojom::blink::PresentationConnectionState::CONNECTED);
160 
161   LocalFrame::NotifyUserActivation(&page_holder->GetFrame());
162   remote_playback.prompt(scope.GetScriptState())
163       .Then(resolve->Bind(), reject->Bind());
164 
165   SetState(remote_playback, mojom::blink::PresentationConnectionState::CLOSED);
166 
167   // Runs pending promises.
168   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
169 
170   // Verify mock expectations explicitly as the mock objects are garbage
171   // collected.
172   testing::Mock::VerifyAndClear(resolve);
173   testing::Mock::VerifyAndClear(reject);
174 }
175 
TEST_F(RemotePlaybackTest,StateChangeEvents)176 TEST_F(RemotePlaybackTest, StateChangeEvents) {
177   V8TestingScope scope;
178 
179   auto page_holder = std::make_unique<DummyPageHolder>();
180 
181   auto* element =
182       MakeGarbageCollected<HTMLVideoElement>(page_holder->GetDocument());
183   RemotePlayback& remote_playback = RemotePlayback::From(*element);
184 
185   auto* connecting_handler = MakeGarbageCollected<
186       testing::StrictMock<MockEventListenerForRemotePlayback>>();
187   auto* connect_handler = MakeGarbageCollected<
188       testing::StrictMock<MockEventListenerForRemotePlayback>>();
189   auto* disconnect_handler = MakeGarbageCollected<
190       testing::StrictMock<MockEventListenerForRemotePlayback>>();
191 
192   remote_playback.addEventListener(event_type_names::kConnecting,
193                                    connecting_handler);
194   remote_playback.addEventListener(event_type_names::kConnect, connect_handler);
195   remote_playback.addEventListener(event_type_names::kDisconnect,
196                                    disconnect_handler);
197 
198   // Verify a state changes when a route is connected and closed.
199   EXPECT_CALL(*connecting_handler, Invoke(testing::_, testing::_)).Times(1);
200   EXPECT_CALL(*connect_handler, Invoke(testing::_, testing::_)).Times(1);
201   EXPECT_CALL(*disconnect_handler, Invoke(testing::_, testing::_)).Times(1);
202 
203   SetState(remote_playback,
204            mojom::blink::PresentationConnectionState::CONNECTING);
205   SetState(remote_playback,
206            mojom::blink::PresentationConnectionState::CONNECTING);
207   SetState(remote_playback,
208            mojom::blink::PresentationConnectionState::CONNECTED);
209   SetState(remote_playback,
210            mojom::blink::PresentationConnectionState::CONNECTED);
211   SetState(remote_playback, mojom::blink::PresentationConnectionState::CLOSED);
212   SetState(remote_playback, mojom::blink::PresentationConnectionState::CLOSED);
213 
214   // Verify mock expectations explicitly as the mock objects are garbage
215   // collected.
216   testing::Mock::VerifyAndClear(connecting_handler);
217   testing::Mock::VerifyAndClear(connect_handler);
218   testing::Mock::VerifyAndClear(disconnect_handler);
219 
220   // Verify a state changes when a route is connected and terminated.
221   EXPECT_CALL(*connecting_handler, Invoke(testing::_, testing::_)).Times(1);
222   EXPECT_CALL(*connect_handler, Invoke(testing::_, testing::_)).Times(1);
223   EXPECT_CALL(*disconnect_handler, Invoke(testing::_, testing::_)).Times(1);
224 
225   SetState(remote_playback,
226            mojom::blink::PresentationConnectionState::CONNECTING);
227   SetState(remote_playback,
228            mojom::blink::PresentationConnectionState::CONNECTED);
229   SetState(remote_playback,
230            mojom::blink::PresentationConnectionState::TERMINATED);
231 
232   // Verify mock expectations explicitly as the mock objects are garbage
233   // collected.
234   testing::Mock::VerifyAndClear(connecting_handler);
235   testing::Mock::VerifyAndClear(connect_handler);
236   testing::Mock::VerifyAndClear(disconnect_handler);
237 
238   // Verify we can connect after a route termination.
239   EXPECT_CALL(*connecting_handler, Invoke(testing::_, testing::_)).Times(1);
240   SetState(remote_playback,
241            mojom::blink::PresentationConnectionState::CONNECTING);
242   testing::Mock::VerifyAndClear(connecting_handler);
243 }
244 
TEST_F(RemotePlaybackTest,DisableRemotePlaybackRejectsPromptWithInvalidStateError)245 TEST_F(RemotePlaybackTest,
246        DisableRemotePlaybackRejectsPromptWithInvalidStateError) {
247   V8TestingScope scope;
248 
249   auto page_holder = std::make_unique<DummyPageHolder>();
250 
251   auto* element =
252       MakeGarbageCollected<HTMLVideoElement>(page_holder->GetDocument());
253   RemotePlayback& remote_playback = RemotePlayback::From(*element);
254 
255   MockFunction* resolve = MockFunction::Create(scope.GetScriptState());
256   MockFunction* reject = MockFunction::Create(scope.GetScriptState());
257 
258   EXPECT_CALL(*resolve, Call(testing::_)).Times(0);
259   EXPECT_CALL(*reject, Call(testing::_)).Times(1);
260 
261   LocalFrame::NotifyUserActivation(&page_holder->GetFrame());
262   remote_playback.prompt(scope.GetScriptState())
263       .Then(resolve->Bind(), reject->Bind());
264   HTMLMediaElementRemotePlayback::SetBooleanAttribute(
265       *element, html_names::kDisableremoteplaybackAttr, true);
266 
267   // Runs pending promises.
268   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
269 
270   // Verify mock expectations explicitly as the mock objects are garbage
271   // collected.
272   testing::Mock::VerifyAndClear(resolve);
273   testing::Mock::VerifyAndClear(reject);
274 }
275 
TEST_F(RemotePlaybackTest,DisableRemotePlaybackCancelsAvailabilityCallbacks)276 TEST_F(RemotePlaybackTest, DisableRemotePlaybackCancelsAvailabilityCallbacks) {
277   V8TestingScope scope;
278 
279   auto page_holder = std::make_unique<DummyPageHolder>();
280 
281   auto* element =
282       MakeGarbageCollected<HTMLVideoElement>(page_holder->GetDocument());
283   RemotePlayback& remote_playback = RemotePlayback::From(*element);
284 
285   MockFunction* callback_function =
286       MockFunction::Create(scope.GetScriptState());
287   V8RemotePlaybackAvailabilityCallback* availability_callback =
288       V8RemotePlaybackAvailabilityCallback::Create(callback_function->Bind());
289 
290   // The initial call upon registering will not happen as it's posted on the
291   // message loop.
292   EXPECT_CALL(*callback_function, Call(testing::_)).Times(0);
293 
294   MockFunction* resolve = MockFunction::Create(scope.GetScriptState());
295   MockFunction* reject = MockFunction::Create(scope.GetScriptState());
296 
297   EXPECT_CALL(*resolve, Call(testing::_)).Times(1);
298   EXPECT_CALL(*reject, Call(testing::_)).Times(0);
299 
300   remote_playback
301       .watchAvailability(scope.GetScriptState(), availability_callback)
302       .Then(resolve->Bind(), reject->Bind());
303 
304   HTMLMediaElementRemotePlayback::SetBooleanAttribute(
305       *element, html_names::kDisableremoteplaybackAttr, true);
306 
307   // Runs pending promises.
308   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
309 
310   // Verify mock expectations explicitly as the mock objects are garbage
311   // collected.
312   testing::Mock::VerifyAndClear(resolve);
313   testing::Mock::VerifyAndClear(reject);
314   testing::Mock::VerifyAndClear(callback_function);
315 }
316 
TEST_F(RemotePlaybackTest,PromptThrowsWhenBackendDisabled)317 TEST_F(RemotePlaybackTest, PromptThrowsWhenBackendDisabled) {
318   ScopedRemotePlaybackBackendForTest remote_playback_backend(false);
319   V8TestingScope scope;
320 
321   auto page_holder = std::make_unique<DummyPageHolder>();
322 
323   auto* element =
324       MakeGarbageCollected<HTMLVideoElement>(page_holder->GetDocument());
325   RemotePlayback& remote_playback = RemotePlayback::From(*element);
326 
327   auto* resolve = MockFunction::Create(scope.GetScriptState());
328   auto* reject = MockFunction::Create(scope.GetScriptState());
329 
330   EXPECT_CALL(*resolve, Call(testing::_)).Times(0);
331   EXPECT_CALL(*reject, Call(testing::_)).Times(1);
332 
333   LocalFrame::NotifyUserActivation(&page_holder->GetFrame());
334   remote_playback.prompt(scope.GetScriptState())
335       .Then(resolve->Bind(), reject->Bind());
336 
337   // Runs pending promises.
338   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
339 
340   // Verify mock expectations explicitly as the mock objects are garbage
341   // collected.
342   testing::Mock::VerifyAndClear(resolve);
343   testing::Mock::VerifyAndClear(reject);
344 }
345 
TEST_F(RemotePlaybackTest,WatchAvailabilityWorksWhenBackendDisabled)346 TEST_F(RemotePlaybackTest, WatchAvailabilityWorksWhenBackendDisabled) {
347   ScopedRemotePlaybackBackendForTest remote_playback_backend(false);
348   V8TestingScope scope;
349 
350   auto page_holder = std::make_unique<DummyPageHolder>();
351 
352   auto* element =
353       MakeGarbageCollected<HTMLVideoElement>(page_holder->GetDocument());
354   RemotePlayback& remote_playback = RemotePlayback::From(*element);
355 
356   MockFunction* callback_function =
357       MockFunction::Create(scope.GetScriptState());
358   V8RemotePlaybackAvailabilityCallback* availability_callback =
359       V8RemotePlaybackAvailabilityCallback::Create(callback_function->Bind());
360 
361   // The initial call upon registering will not happen as it's posted on the
362   // message loop.
363   EXPECT_CALL(*callback_function, Call(testing::_)).Times(0);
364 
365   MockFunction* resolve = MockFunction::Create(scope.GetScriptState());
366   MockFunction* reject = MockFunction::Create(scope.GetScriptState());
367 
368   EXPECT_CALL(*resolve, Call(testing::_)).Times(1);
369   EXPECT_CALL(*reject, Call(testing::_)).Times(0);
370 
371   remote_playback
372       .watchAvailability(scope.GetScriptState(), availability_callback)
373       .Then(resolve->Bind(), reject->Bind());
374 
375   // Runs pending promises.
376   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
377 
378   // Verify mock expectations explicitly as the mock objects are garbage
379   // collected.
380   testing::Mock::VerifyAndClear(resolve);
381   testing::Mock::VerifyAndClear(reject);
382   testing::Mock::VerifyAndClear(callback_function);
383 }
384 
TEST_F(RemotePlaybackTest,IsListening)385 TEST_F(RemotePlaybackTest, IsListening) {
386   V8TestingScope scope;
387 
388   auto page_holder = std::make_unique<DummyPageHolder>();
389 
390   auto* element =
391       MakeGarbageCollected<HTMLVideoElement>(page_holder->GetDocument());
392   RemotePlayback& remote_playback = RemotePlayback::From(*element);
393 
394   LocalFrame& frame = page_holder->GetFrame();
395   MockPresentationController* mock_controller =
396       MakeGarbageCollected<MockPresentationController>(frame);
397   Supplement<LocalFrame>::ProvideTo(
398       frame, static_cast<PresentationController*>(mock_controller));
399 
400   EXPECT_CALL(*mock_controller,
401               AddAvailabilityObserver(testing::Eq(&remote_playback)))
402       .Times(2);
403   EXPECT_CALL(*mock_controller,
404               RemoveAvailabilityObserver(testing::Eq(&remote_playback)))
405       .Times(2);
406 
407   MockFunction* callback_function =
408       MockFunction::Create(scope.GetScriptState());
409   V8RemotePlaybackAvailabilityCallback* availability_callback =
410       V8RemotePlaybackAvailabilityCallback::Create(callback_function->Bind());
411 
412   // The initial call upon registering will not happen as it's posted on the
413   // message loop.
414   EXPECT_CALL(*callback_function, Call(testing::_)).Times(2);
415 
416   remote_playback.watchAvailability(scope.GetScriptState(),
417                                     availability_callback);
418 
419   ASSERT_TRUE(remote_playback.Urls().IsEmpty());
420   ASSERT_FALSE(IsListening(remote_playback));
421 
422   remote_playback.SourceChanged(WebURL(KURL("http://www.example.com")), true);
423   ASSERT_EQ((size_t)1, remote_playback.Urls().size());
424   ASSERT_TRUE(IsListening(remote_playback));
425   remote_playback.AvailabilityChanged(mojom::ScreenAvailability::AVAILABLE);
426 
427   remote_playback.cancelWatchAvailability(scope.GetScriptState());
428   ASSERT_EQ((size_t)1, remote_playback.Urls().size());
429   ASSERT_FALSE(IsListening(remote_playback));
430 
431   remote_playback.watchAvailability(scope.GetScriptState(),
432                                     availability_callback);
433   ASSERT_EQ((size_t)1, remote_playback.Urls().size());
434   ASSERT_TRUE(IsListening(remote_playback));
435   remote_playback.AvailabilityChanged(mojom::ScreenAvailability::AVAILABLE);
436 
437   remote_playback.SourceChanged(WebURL(), false);
438   ASSERT_TRUE(remote_playback.Urls().IsEmpty());
439   ASSERT_FALSE(IsListening(remote_playback));
440 
441   remote_playback.SourceChanged(WebURL(KURL("@$@#@#")), true);
442   ASSERT_TRUE(remote_playback.Urls().IsEmpty());
443   ASSERT_FALSE(IsListening(remote_playback));
444 
445   // Runs pending promises.
446   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
447 
448   // Verify mock expectations explicitly as the mock objects are garbage
449   // collected.
450   testing::Mock::VerifyAndClear(callback_function);
451   testing::Mock::VerifyAndClear(mock_controller);
452 }
453 
454 }  // namespace blink
455