blob: 36a08c28067a589aa512a3e52ee736b73387dbbd [file] [log] [blame]
// Copyright 2013 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.
package org.chromium.android_webview.test;
import android.support.test.filters.MediumTest;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.android_webview.test.util.JavascriptEventObserver;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.browser.test.util.DOMUtils;
import org.chromium.content.browser.test.util.TouchCommon;
import org.chromium.content_public.browser.WebContents;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeoutException;
/**
* Test the fullscreen API (WebChromeClient::onShow/HideCustomView).
*
* <p>Fullscreen support follows a different code path depending on whether
* the element is a video or not, so we test we both. For non-video elements
* we pick a div containing a video and custom html controls since this is a
* very common use case.
*/
@DisableIf.Build(sdk_is_greater_than = 22, message = "crbug.com/615483,615184")
public class AwContentsClientFullScreenTest extends AwTestBase {
private static final String VIDEO_TEST_URL =
"file:///android_asset/full_screen_video_test.html";
private static final String VIDEO_INSIDE_DIV_TEST_URL =
"file:///android_asset/full_screen_video_inside_div_test.html";
// These values must be kept in sync with the strings in
// full_screen_video_test.html, full_screen_video_inside_div_test.html and
// full_screen_video.js.
private static final String VIDEO_ID = "video";
private static final String CUSTOM_PLAY_CONTROL_ID = "playControl";
private static final String CUSTOM_FULLSCREEN_CONTROL_ID = "fullscreenControl";
private static final String FULLSCREEN_ERROR_OBSERVER = "javaFullScreenErrorObserver";
private FullScreenVideoTestAwContentsClient mContentsClient;
private ContentViewCore mContentViewCore;
private AwTestContainerView mTestContainerView;
@Override
protected void setUp() throws Exception {
super.setUp();
mContentsClient = new FullScreenVideoTestAwContentsClient(getActivity(),
isHardwareAcceleratedTest());
mTestContainerView =
createAwTestContainerViewOnMainSync(mContentsClient);
mContentViewCore = mTestContainerView.getContentViewCore();
enableJavaScriptOnUiThread(mTestContainerView.getAwContents());
mTestContainerView.getAwContents().getSettings().setFullscreenSupported(true);
}
/*
@MediumTest
@Feature({"AndroidWebView"})
@DisableHardwareAccelerationForTest
*/
@DisabledTest(message = "crbug.com/618749")
public void testFullscreenVideoInSoftwareModeDoesNotDeadlock() throws Throwable {
// Although fullscreen video is not supported without hardware acceleration
// we should not deadlock if apps try to use it.
loadTestPageAndClickFullscreen(VIDEO_TEST_URL);
mContentsClient.waitForCustomViewShown();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mContentsClient.getExitCallback().onCustomViewHidden();
}
});
mContentsClient.waitForCustomViewHidden();
}
@MediumTest
@Feature({"AndroidWebView"})
@DisableHardwareAccelerationForTest
public void testFullscreenForNonVideoElementIsSupportedInSoftwareMode() throws Throwable {
// Fullscreen for non-video elements is supported and works as expected. Note that
// this test is the same as testOnShowAndHideCustomViewWithCallback_videoInsideDiv below.
doTestOnShowAndHideCustomViewWithCallback(VIDEO_INSIDE_DIV_TEST_URL);
}
/*
@MediumTest
@Feature({"AndroidWebView"})
*/
@DisabledTest(message = "crbug.com/618749")
public void testOnShowAndHideCustomViewWithCallback_video() throws Throwable {
doTestOnShowAndHideCustomViewWithCallback(VIDEO_TEST_URL);
}
/*
@MediumTest
@Feature({"AndroidWebView"})
*/
@DisabledTest(message = "crbug.com/618749")
public void testOnShowAndHideCustomViewWithCallback_videoInsideDiv() throws Throwable {
doTestOnShowAndHideCustomViewWithCallback(VIDEO_INSIDE_DIV_TEST_URL);
}
public void doTestOnShowAndHideCustomViewWithCallback(String videoTestUrl) throws Throwable {
doOnShowAndHideCustomViewTest(videoTestUrl, new Runnable() {
@Override
public void run() {
mContentsClient.getExitCallback().onCustomViewHidden();
}
});
}
/*
@MediumTest
@Feature({"AndroidWebView"})
*/
@DisabledTest(message = "crbug.com/618749")
public void testOnShowAndHideCustomViewWithJavascript_video() throws Throwable {
doTestOnShowAndHideCustomViewWithJavascript(VIDEO_TEST_URL);
}
/*
@MediumTest
@Feature({"AndroidWebView"})
*/
@DisabledTest(message = "crbug.com/618749")
public void testOnShowAndHideCustomViewWithJavascript_videoInsideDiv()
throws Throwable {
doTestOnShowAndHideCustomViewWithJavascript(VIDEO_INSIDE_DIV_TEST_URL);
}
public void doTestOnShowAndHideCustomViewWithJavascript(String videoTestUrl) throws Throwable {
doOnShowAndHideCustomViewTest(videoTestUrl, new Runnable() {
@Override
public void run() {
DOMUtils.exitFullscreen(mContentViewCore.getWebContents());
}
});
}
/*
@MediumTest
@Feature({"AndroidWebView"})
@ParameterizedTest.Set // crbug.com/616501
*/
@DisabledTest(message = "crbug.com/618749")
public void testOnShowAndHideCustomViewWithBackKey_video() throws Throwable {
doTestOnShowAndHideCustomViewWithBackKey(VIDEO_TEST_URL);
}
/*
@MediumTest
@Feature({"AndroidWebView"})
*/
@DisabledTest(message = "crbug.com/618749")
public void testOnShowAndHideCustomViewWithBackKey_videoInsideDiv()
throws Throwable {
doTestOnShowAndHideCustomViewWithBackKey(VIDEO_INSIDE_DIV_TEST_URL);
}
public void doTestOnShowAndHideCustomViewWithBackKey(String videoTestUrl) throws Throwable {
doOnShowCustomViewTest(videoTestUrl);
// The key event should not be propagated to mTestContainerView (the original container
// view).
mTestContainerView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
fail("mTestContainerView received key event");
return false;
}
});
sendKeys(KeyEvent.KEYCODE_BACK);
mContentsClient.waitForCustomViewHidden();
assertFalse(mContentsClient.wasOnUnhandledKeyUpEventCalled());
assertWaitForIsEmbedded();
}
@MediumTest
@Feature({"AndroidWebView"})
@RetryOnFailure
public void testExitFullscreenEndsIfAppInvokesCallbackFromOnHideCustomView() throws Throwable {
mContentsClient.setOnHideCustomViewRunnable(new Runnable() {
@Override
public void run() {
mContentsClient.getExitCallback().onCustomViewHidden();
}
});
doTestOnShowAndHideCustomViewWithCallback(VIDEO_TEST_URL);
}
@MediumTest
@Feature({"AndroidWebView"})
public void testOnShowCustomViewAndPlayWithHtmlControl_video() throws Throwable {
doTestOnShowCustomViewAndPlayWithHtmlControl(VIDEO_TEST_URL);
}
@MediumTest
@Feature({"AndroidWebView"})
public void testOnShowCustomViewAndPlayWithHtmlControl_videoInsideDiv() throws Throwable {
doTestOnShowCustomViewAndPlayWithHtmlControl(VIDEO_INSIDE_DIV_TEST_URL);
}
public void doTestOnShowCustomViewAndPlayWithHtmlControl(String videoTestUrl) throws Throwable {
doOnShowCustomViewTest(videoTestUrl);
assertTrue(DOMUtils.isMediaPaused(getWebContentsOnUiThread(), VIDEO_ID));
tapPlayButton();
DOMUtils.waitForMediaPlay(getWebContentsOnUiThread(), VIDEO_ID);
}
@MediumTest
@Feature({"AndroidWebView"})
public void testFullscreenNotSupported_video() throws Throwable {
doTestFullscreenNotSupported(VIDEO_TEST_URL);
}
@MediumTest
@Feature({"AndroidWebView"})
public void testFullscreenNotSupported_videoInsideDiv() throws Throwable {
doTestFullscreenNotSupported(VIDEO_INSIDE_DIV_TEST_URL);
}
public void doTestFullscreenNotSupported(String videoTestUrl) throws Throwable {
mTestContainerView.getAwContents().getSettings().setFullscreenSupported(false);
final JavascriptEventObserver fullscreenErrorObserver = registerObserver(
FULLSCREEN_ERROR_OBSERVER);
loadTestPageAndClickFullscreen(videoTestUrl);
assertTrue(fullscreenErrorObserver.waitForEvent(WAIT_TIMEOUT_MS));
assertFalse(mContentsClient.wasCustomViewShownCalled());
}
@MediumTest
@Feature({"AndroidWebView"})
public void testPowerSaveBlockerIsEnabledDuringFullscreenPlayback_video()
throws Throwable {
doTestPowerSaveBlockerIsEnabledDuringFullscreenPlayback(VIDEO_TEST_URL);
}
@MediumTest
@Feature({"AndroidWebView"})
public void testPowerSaveBlockerIsEnabledDuringFullscreenPlayback_videoInsideDiv()
throws Throwable {
doTestPowerSaveBlockerIsEnabledDuringFullscreenPlayback(VIDEO_INSIDE_DIV_TEST_URL);
}
public void doTestPowerSaveBlockerIsEnabledDuringFullscreenPlayback(String videoTestUrl)
throws Throwable {
// Enter fullscreen.
doOnShowCustomViewTest(videoTestUrl);
View customView = mContentsClient.getCustomView();
// No power save blocker is active before playback starts.
assertKeepScreenOnActive(customView, false);
// Play and verify that there is an active power save blocker.
tapPlayButton();
assertWaitForKeepScreenOnActive(customView, true);
// Stop the video and verify that the power save blocker is gone.
DOMUtils.pauseMedia(getWebContentsOnUiThread(), VIDEO_ID);
assertWaitForKeepScreenOnActive(customView, false);
}
@MediumTest
@Feature({"AndroidWebView"})
public void testPowerSaveBlockerIsEnabledDuringEmbeddedPlayback()
throws Throwable {
assertFalse(DOMUtils.isFullscreen(getWebContentsOnUiThread()));
loadTestPage(VIDEO_INSIDE_DIV_TEST_URL);
// No power save blocker is active before playback starts.
assertKeepScreenOnActive(mTestContainerView, false);
// Play and verify that there is an active power save blocker.
tapPlayButton();
assertWaitForKeepScreenOnActive(mTestContainerView, true);
// Stop the video and verify that the power save blocker is gone.
DOMUtils.pauseMedia(getWebContentsOnUiThread(), VIDEO_ID);
assertWaitForKeepScreenOnActive(mTestContainerView, false);
}
@MediumTest
@Feature({"AndroidWebView"})
@RetryOnFailure
public void testPowerSaveBlockerIsTransferredToFullscreen()
throws Throwable {
assertFalse(DOMUtils.isFullscreen(getWebContentsOnUiThread()));
loadTestPage(VIDEO_INSIDE_DIV_TEST_URL);
// Play and verify that there is an active power save blocker.
tapPlayButton();
assertWaitForKeepScreenOnActive(mTestContainerView, true);
// Enter fullscreen and verify that the power save blocker is
// still there.
DOMUtils.clickNode(mContentViewCore, CUSTOM_FULLSCREEN_CONTROL_ID);
mContentsClient.waitForCustomViewShown();
assertKeepScreenOnActive(mTestContainerView, true);
// Pause the video and the power save blocker is gone.
DOMUtils.pauseMedia(getWebContentsOnUiThread(), VIDEO_ID);
assertWaitForKeepScreenOnActive(mTestContainerView, false);
// Exit fullscreen and the power save blocker is still gone.
DOMUtils.exitFullscreen(getWebContentsOnUiThread());
mContentsClient.waitForCustomViewHidden();
assertKeepScreenOnActive(mTestContainerView, false);
}
@MediumTest
@Feature({"AndroidWebView"})
@RetryOnFailure
public void testPowerSaveBlockerIsTransferredToEmbedded()
throws Throwable {
// Enter fullscreen.
doOnShowCustomViewTest(VIDEO_INSIDE_DIV_TEST_URL);
View customView = mContentsClient.getCustomView();
// Play and verify that there is an active power save blocker
// in fullscreen.
tapPlayButton();
assertWaitForKeepScreenOnActive(customView, true);
// Exit fullscreen and verify that the power save blocker is
// still there.
DOMUtils.exitFullscreen(getWebContentsOnUiThread());
mContentsClient.waitForCustomViewHidden();
assertKeepScreenOnActive(customView, true);
}
private void tapPlayButton() throws Exception {
String testUrl = mTestContainerView.getAwContents().getUrl();
if (VIDEO_TEST_URL.equals(testUrl)
&& DOMUtils.isFullscreen(getWebContentsOnUiThread())) {
// The VIDEO_TEST_URL page goes fullscreen on the <video> element. In fullscreen
// the button with id CUSTOM_PLAY_CONTROL_ID will not be visible, but the standard
// html5 video controls are. The standard html5 controls are shadow html elements
// without any ids so it is difficult to retrieve its precise location. However,
// a large play control is rendered in the center of the custom view
// (containing the fullscreen <video>) so we just rely on that fact here.
TouchCommon.singleClickView(mContentsClient.getCustomView());
} else {
DOMUtils.clickNode(mContentViewCore, CUSTOM_PLAY_CONTROL_ID);
}
}
/**
* Asserts that the keep screen on property in the given {@code view} is active as
* {@code expected}. It also verifies that it is only active when the video is playing.
*/
private void assertWaitForKeepScreenOnActive(final View view, final boolean expected) {
// We need to poll because it takes time to synchronize the state between the android
// views and Javascript.
CriteriaHelper.pollInstrumentationThread(new Criteria() {
@Override
public boolean isSatisfied() {
try {
return getKeepScreenOnOnInstrumentationThread(view) == expected
&& DOMUtils.isMediaPaused(getWebContentsOnUiThread(), VIDEO_ID)
!= expected;
} catch (InterruptedException | TimeoutException e) {
fail(e.getMessage());
return false;
}
}
});
}
private void assertKeepScreenOnActive(final View view, final boolean expected)
throws Exception {
assertTrue(getKeepScreenOnOnInstrumentationThread(view) == expected
&& DOMUtils.isMediaPaused(getWebContentsOnUiThread(), VIDEO_ID) != expected);
}
private boolean getKeepScreenOnOnInstrumentationThread(final View view) {
try {
return runTestOnUiThreadAndGetResult(new Callable<Boolean>() {
@Override
public Boolean call() {
return getKeepScreenOnOnUiThread(view);
}
});
} catch (Exception e) {
fail(e.getMessage());
return false;
}
}
private boolean getKeepScreenOnOnUiThread(View view) {
// The power save blocker is added to the container view.
// Search the view hierarchy for it.
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
if (getKeepScreenOnOnUiThread(viewGroup.getChildAt(i))) {
return true;
}
}
}
return view.getKeepScreenOn();
}
private void assertWaitForIsFullscreen() {
// We need to poll because the Javascript state is updated asynchronously
CriteriaHelper.pollInstrumentationThread(new Criteria() {
@Override
public boolean isSatisfied() {
try {
return DOMUtils.isFullscreen(getWebContentsOnUiThread());
} catch (InterruptedException | TimeoutException e) {
fail(e.getMessage());
return false;
}
}
});
}
private void assertWaitForIsEmbedded() {
// We need to poll because the Javascript state is updated asynchronously
CriteriaHelper.pollInstrumentationThread(new Criteria() {
@Override
public boolean isSatisfied() {
try {
return !DOMUtils.isFullscreen(getWebContentsOnUiThread());
} catch (InterruptedException | TimeoutException e) {
fail(e.getMessage());
return false;
}
}
});
// TODO: Test that inline video is actually displayed.
}
private JavascriptEventObserver registerObserver(final String observerName) throws Throwable {
final JavascriptEventObserver observer = new JavascriptEventObserver();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
observer.register(mContentViewCore, observerName);
}
});
return observer;
}
private void doOnShowAndHideCustomViewTest(String videoTestUrl, final Runnable existFullscreen)
throws Throwable {
doOnShowCustomViewTest(videoTestUrl);
runTestOnUiThread(existFullscreen);
mContentsClient.waitForCustomViewHidden();
assertWaitForIsEmbedded();
}
private void doOnShowCustomViewTest(String videoTestUrl) throws Exception {
loadTestPageAndClickFullscreen(videoTestUrl);
mContentsClient.waitForCustomViewShown();
assertWaitForIsFullscreen();
}
private void loadTestPageAndClickFullscreen(String videoTestUrl) throws Exception {
loadTestPage(videoTestUrl);
DOMUtils.clickNode(mContentViewCore, CUSTOM_FULLSCREEN_CONTROL_ID);
}
private void loadTestPage(String videoTestUrl) throws Exception {
loadUrlSync(mTestContainerView.getAwContents(),
mContentsClient.getOnPageFinishedHelper(), videoTestUrl);
// As we are loading a non-trivial page, let's wait until we have something displayed.
waitForVisualStateCallback(mTestContainerView.getAwContents());
}
private WebContents getWebContentsOnUiThread() {
try {
return runTestOnUiThreadAndGetResult(new Callable<WebContents>() {
@Override
public WebContents call() throws Exception {
return mContentViewCore.getWebContents();
}
});
} catch (Exception e) {
fail(e.getMessage());
return null;
}
}
}