chromium/content/browser/renderer_host/compositor_impl_android_browsertest.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/android/application_status_listener.h"
#include "base/android/build_info.h"
#include "base/base_switches.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "components/viz/common/gpu/raster_context_provider.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/browser/renderer_host/compositor_impl_android.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/gpu_stream_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/gpu_browsertest_helpers.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "gpu/config/gpu_finch_features.h"
#include "gpu/ipc/client/gpu_channel_host.h"
#include "media/base/media_switches.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "ui/android/window_android.h"
#include "ui/gfx/android/android_surface_control_compat.h"
#include "url/gurl.h"

namespace content {

namespace {

class CompositorImplBrowserTest : public ContentBrowserTest {
 public:
  CompositorImplBrowserTest() {}

  CompositorImplBrowserTest(const CompositorImplBrowserTest&) = delete;
  CompositorImplBrowserTest& operator=(const CompositorImplBrowserTest&) =
      delete;

  virtual std::string GetTestUrl() { return "/title1.html"; }

 protected:
  void SetUpOnMainThread() override {
    ASSERT_TRUE(embedded_test_server()->Start());
    net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
    https_server.ServeFilesFromSourceDirectory(GetTestDataFilePath());
    ASSERT_TRUE(https_server.Start());
    GURL http_url(embedded_test_server()->GetURL(GetTestUrl()));
    ASSERT_TRUE(NavigateToURL(shell(), http_url));
  }

  ui::WindowAndroid* window() const {
    return web_contents()->GetTopLevelNativeWindow();
  }

  CompositorImpl* compositor_impl() const {
    return static_cast<CompositorImpl*>(window()->GetCompositor());
  }

  WebContentsImpl* web_contents() const {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }

  RenderWidgetHostViewAndroid* render_widget_host_view_android() const {
    return static_cast<RenderWidgetHostViewAndroid*>(
        web_contents()->GetRenderWidgetHostView());
  }
};

class CompositorImplLowEndBrowserTest : public CompositorImplBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kEnableLowEndDeviceMode);
    command_line->AppendSwitch(switches::kInProcessGPU);
    content::ContentBrowserTest::SetUpCommandLine(command_line);
  }
};

// RunLoop implementation that calls glFlush() every second until it observes
// OnContextLost().
class ContextLostRunLoop : public viz::ContextLostObserver {
 public:
  explicit ContextLostRunLoop(viz::RasterContextProvider* context_provider)
      : context_provider_(context_provider) {
    context_provider_->AddObserver(this);
  }

  ContextLostRunLoop(const ContextLostRunLoop&) = delete;
  ContextLostRunLoop& operator=(const ContextLostRunLoop&) = delete;

  ~ContextLostRunLoop() override { context_provider_->RemoveObserver(this); }

  void RunUntilContextLost() {
    CheckForContextLoss();
    run_loop_.Run();
  }

  void CheckForContextLoss() {
    if (did_lose_context_) {
      run_loop_.Quit();
      return;
    }
    context_provider_->RasterInterface()->Flush();
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&ContextLostRunLoop::CheckForContextLoss,
                       base::Unretained(this)),
        base::Seconds(1));
  }

 private:
  // viz::LostContextProvider:
  void OnContextLost() override { did_lose_context_ = true; }

  const raw_ptr<viz::RasterContextProvider> context_provider_;
  bool did_lose_context_ = false;
  base::RunLoop run_loop_;
};

// RunLoop implementation that runs until it observes a swap with size.
class CompositorSwapRunLoop {
 public:
  CompositorSwapRunLoop(CompositorImpl* compositor) : compositor_(compositor) {
    static_cast<Compositor*>(compositor_)
        ->SetDidSwapBuffersCallbackEnabled(true);
    compositor_->SetSwapCompletedWithSizeCallbackForTesting(base::BindRepeating(
        &CompositorSwapRunLoop::DidSwap, base::Unretained(this)));
  }

  CompositorSwapRunLoop(const CompositorSwapRunLoop&) = delete;
  CompositorSwapRunLoop& operator=(const CompositorSwapRunLoop&) = delete;

  ~CompositorSwapRunLoop() {
    compositor_->SetSwapCompletedWithSizeCallbackForTesting(base::DoNothing());
  }

  void RunUntilSwap() { run_loop_.Run(); }

 private:
  void DidSwap(const gfx::Size& pixel_size) {
    EXPECT_FALSE(pixel_size.IsEmpty());
    run_loop_.Quit();
  }

  raw_ptr<CompositorImpl> compositor_;
  base::RunLoop run_loop_;
};

IN_PROC_BROWSER_TEST_F(CompositorImplLowEndBrowserTest,
                       CompositorImplDropsResourcesOnBackground) {
  auto* rwhva = render_widget_host_view_android();
  auto* compositor = compositor_impl();
  auto context = GpuBrowsertestCreateContext(
      GpuBrowsertestEstablishGpuChannelSyncRunLoop());
  context->BindToCurrentSequence();

  // Run until we've swapped once. At this point we should have a valid frame.
  CompositorSwapRunLoop(compositor_impl()).RunUntilSwap();
  EXPECT_TRUE(rwhva->HasValidFrame());

  ContextLostRunLoop run_loop(context.get());
  compositor->SetVisibleForTesting(false);
  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
      base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES);
  rwhva->OnRootWindowVisibilityChanged(false);
  rwhva->Hide();

  // Ensure that context is eventually dropped and at that point we do not have
  // a valid frame.
  run_loop.RunUntilContextLost();
  EXPECT_FALSE(rwhva->HasValidFrame());

  // Become visible again:
  compositor->SetVisibleForTesting(true);
  base::android::ApplicationStatusListener::NotifyApplicationStateChange(
      base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
  rwhva->Show();
  rwhva->OnRootWindowVisibilityChanged(true);

  // Wait for a swap after becoming visible.
  CompositorSwapRunLoop(compositor_impl()).RunUntilSwap();
  EXPECT_TRUE(rwhva->HasValidFrame());
}

IN_PROC_BROWSER_TEST_F(CompositorImplBrowserTest,
                       CompositorImplReceivesSwapCallbacks) {
  CompositorSwapRunLoop(compositor_impl()).RunUntilSwap();
}

// This test waits for a presentation feedback token to arrive from the GPU. If
// this test is timing out then it demonstrates a bug.
IN_PROC_BROWSER_TEST_F(CompositorImplBrowserTest,
                       CompositorImplReceivesPresentationTimeCallbacks) {
  // Presentation feedback occurs after the GPU has presented content to the
  // display. This is later than the buffers swap.
  base::RunLoop loop;
  // The callback will cancel the loop used to wait.
  static_cast<content::Compositor*>(compositor_impl())
      ->RequestSuccessfulPresentationTimeForNextFrame(base::BindOnce(
          [](base::OnceClosure quit, const viz::FrameTimingDetails& details) {
            std::move(quit).Run();
          },
          loop.QuitClosure()));
  loop.Run();
}

class CompositorImplBrowserTestRefreshRate
    : public CompositorImplBrowserTest,
      public ui::WindowAndroid::TestHooks {
 public:
  std::string GetTestUrl() override { return "/media/tulip2.webm"; }

  // WindowAndroid::TestHooks impl.
  std::vector<float> GetSupportedRates() override {
    return {120.f, 90.f, 60.f};
  }
  void SetPreferredRate(float refresh_rate) override {
    if (fabs(refresh_rate - expected_refresh_rate_) < 2.f)
      run_loop_->Quit();
  }
  void SetUpCommandLine(base::CommandLine* command_line) override {
    content::ContentBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitchASCII(
        switches::kAutoplayPolicy,
        switches::autoplay::kNoUserGestureRequiredPolicy);
  }

  float expected_refresh_rate_ = 0.f;
  std::unique_ptr<base::RunLoop> run_loop_;
};

IN_PROC_BROWSER_TEST_F(CompositorImplBrowserTestRefreshRate, VideoPreference) {
  if (gfx::SurfaceControl::SupportsSetFrameRate()) {
    // If SurfaceControl allows specifying the frame rate for each Surface, it's
    // done within the GPU process instead of sending the frame preference back
    // to the browser (and so our TestHooks are not consulted).
    GTEST_SKIP();
  }

  window()->SetTestHooks(this);
  expected_refresh_rate_ = 60.f;
  run_loop_ = std::make_unique<base::RunLoop>();
  run_loop_->Run();
  run_loop_.reset();
  window()->SetTestHooks(nullptr);
}

}  // namespace
}  // namespace content