910e62b5创建于 1月15日历史提交
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <vector>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "content/public/browser/overlay_window.h"
#include "content/public/browser/video_picture_in_picture_window_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_switches.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_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/device/public/cpp/test/scoped_pressure_manager_overrider.h"
#include "services/device/public/mojom/pressure_update.mojom.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"

namespace content {

using device::mojom::PressureData;
using device::mojom::PressureSource;
using device::mojom::PressureState;
using device::mojom::PressureUpdate;

namespace {

bool SupportsSharedWorker() {
  return base::FeatureList::IsEnabled(blink::features::kSharedWorker);
}

class TestVideoOverlayWindow : public VideoOverlayWindow {
 public:
  TestVideoOverlayWindow() = default;
  ~TestVideoOverlayWindow() override = default;

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

  bool IsActive() const override { return false; }
  void Close() override {}
  void ShowInactive() override {}
  void Hide() override {}
  bool IsVisible() const override { return true; }
  gfx::Rect GetBounds() override { return gfx::Rect(size_); }
  void UpdateNaturalSize(const gfx::Size& natural_size) override {
    size_ = natural_size;
  }
  void SetPlaybackState(PlaybackState playback_state) override {}
  void SetPlayPauseButtonVisibility(bool is_visible) override {}
  void SetSkipAdButtonVisibility(bool is_visible) override {}
  void SetNextTrackButtonVisibility(bool is_visible) override {}
  void SetPreviousTrackButtonVisibility(bool is_visible) override {}
  void SetHidePictureInPictureButtonVisibility(bool is_visible) override {}
  void SetMicrophoneMuted(bool muted) override {}
  void SetCameraState(bool turned_on) override {}
  void SetToggleMicrophoneButtonVisibility(bool is_visible) override {}
  void SetToggleCameraButtonVisibility(bool is_visible) override {}
  void SetHangUpButtonVisibility(bool is_visible) override {}
  void SetNextSlideButtonVisibility(bool is_visible) override {}
  void SetPreviousSlideButtonVisibility(bool is_visible) override {}
  void SetMediaPosition(const media_session::MediaPosition&) override {}
  void SetSourceTitle(const std::u16string& source_title) override {}
  void SetFaviconImages(
      const std::vector<media_session::MediaImage>& images) override {}
  void SetSurfaceId(const viz::SurfaceId& surface_id) override {}

 private:
  gfx::Size size_;
};

class TestContentBrowserClient : public ContentBrowserTestContentBrowserClient {
 public:
  std::unique_ptr<VideoOverlayWindow> CreateWindowForVideoPictureInPicture(
      VideoPictureInPictureWindowController* controller) override {
    return std::make_unique<TestVideoOverlayWindow>();
  }
};

class TestWebContentsDelegate : public WebContentsDelegate {
 public:
  PictureInPictureResult EnterPictureInPicture(
      WebContents* web_contents) override {
    window_controller_ = PictureInPictureWindowController::
        GetOrCreateVideoPictureInPictureController(web_contents);
    return PictureInPictureResult::kSuccess;
  }

  void ExitPictureInPicture() override {
    window_controller_->Close(false /* should_pause_video */);
    window_controller_ = nullptr;
  }

 private:
  raw_ptr<VideoPictureInPictureWindowController> window_controller_;
};

class ComputePressureBrowserTest : public ContentBrowserTest {
 public:
  ComputePressureBrowserTest() {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kUseFakeUIForMediaStream);
  }

  void SetUpOnMainThread() override {
    ContentBrowserTest::SetUpOnMainThread();

    host_resolver()->AddRule("*", "127.0.0.1");
    https_server_.ServeFilesFromSourceDirectory(GetTestDataFilePath());
    SetupCrossSiteRedirector(&https_server_);
    ASSERT_TRUE(https_server_.Start());
    test_url_ =
        https_server_.GetURL("/compute_pressure/deliver_update_test.html");
    ASSERT_TRUE(NavigateToURL(shell(), test_url_));

    content_browser_client_ = std::make_unique<TestContentBrowserClient>();
    shell()->web_contents()->SetDelegate(&web_contents_delegate_);
  }

 protected:
  device::ScopedPressureManagerOverrider pressure_manager_overrider_;
  TestWebContentsDelegate web_contents_delegate_;
  std::unique_ptr<TestContentBrowserClient> content_browser_client_;
  net::EmbeddedTestServer https_server_ =
      net::EmbeddedTestServer(net::EmbeddedTestServer::TYPE_HTTPS);
  GURL test_url_;
};

}  // namespace

IN_PROC_BROWSER_TEST_F(ComputePressureBrowserTest, DeliverUpdate) {
  // Start PressureObserver in frame, dedicated worker and shared worker.
  ASSERT_TRUE(ExecJs(shell(), "observer.observe('cpu');"));
  ASSERT_TRUE(ExecJs(shell(), "startDedicatedWorker();"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "startSharedWorker();"));
  }

  // Deliver update.
  const base::TimeTicks time = base::TimeTicks::Now();
  auto data = PressureData::New(/*cpu_utilization=*/0.30,
                                device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update(PressureSource::kCpu, std::move(data), time);
  pressure_manager_overrider_.UpdateClients(std::move(update));

  ASSERT_TRUE(ExecJs(shell(), "datasets.frame.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.frame.samples.length;"));
  ASSERT_EQ("nominal", EvalJs(shell(), "datasets.frame.samples[0].state;"));
  ASSERT_TRUE(ExecJs(shell(), "datasets.dedicatedWorker.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.dedicatedWorker.samples.length;"));
  ASSERT_EQ("nominal",
            EvalJs(shell(), "datasets.dedicatedWorker.samples[0].state;"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "datasets.sharedWorker.waitForUpdates(1);"));
    ASSERT_EQ(1, EvalJs(shell(), "datasets.sharedWorker.samples.length;"));
    ASSERT_EQ("nominal",
              EvalJs(shell(), "datasets.sharedWorker.samples[0].state;"));
  }
}

IN_PROC_BROWSER_TEST_F(ComputePressureBrowserTest, DeliverUpdateForSameOrigin) {
  // Start PressureObserver in frame, dedicated worker and shared worker.
  ASSERT_TRUE(ExecJs(shell(), "observer.observe('cpu');"));
  ASSERT_TRUE(ExecJs(shell(), "startDedicatedWorker();"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "startSharedWorker();"));
  }

  // Focus on same-origin iframe, observers can still receive updates.
  ASSERT_TRUE(ExecJs(shell(), "same_origin_iframe.focus();"));

  // Deliver update.
  const base::TimeTicks time = base::TimeTicks::Now();
  auto data = PressureData::New(/*cpu_utilization=*/0.30,
                                device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update(PressureSource::kCpu, std::move(data), time);
  pressure_manager_overrider_.UpdateClients(std::move(update));

  ASSERT_TRUE(ExecJs(shell(), "datasets.frame.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.frame.samples.length;"));
  ASSERT_EQ("nominal", EvalJs(shell(), "datasets.frame.samples[0].state;"));
  ASSERT_TRUE(ExecJs(shell(), "datasets.dedicatedWorker.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.dedicatedWorker.samples.length;"));
  ASSERT_EQ("nominal",
            EvalJs(shell(), "datasets.dedicatedWorker.samples[0].state;"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "datasets.sharedWorker.waitForUpdates(1);"));
    ASSERT_EQ(1, EvalJs(shell(), "datasets.sharedWorker.samples.length;"));
    ASSERT_EQ("nominal",
              EvalJs(shell(), "datasets.sharedWorker.samples[0].state;"));
  }
}

IN_PROC_BROWSER_TEST_F(ComputePressureBrowserTest, NoUpdateForCrossOrigin) {
  // Start PressureObserver in frame, dedicated worker and shared worker.
  ASSERT_TRUE(ExecJs(shell(), "observer.observe('cpu');"));
  ASSERT_TRUE(ExecJs(shell(), "startDedicatedWorker();"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "startSharedWorker();"));
  }

  // Focus on cross-origin iframe, observers can not receive updates.
  ASSERT_TRUE(ExecJs(shell(), "cross_origin_iframe.focus();"));

  // Deliver update.
  const base::TimeTicks time1 = base::TimeTicks::Now();
  auto data1 = PressureData::New(
      /*cpu_utilization=*/0.30, device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update1(PressureSource::kCpu, std::move(data1), time1);
  pressure_manager_overrider_.UpdateClients(std::move(update1));

  // Focus on main frame, observers can receive updates again.
  ASSERT_TRUE(ExecJs(shell(), "parent.focus();"));

  // Deliver update.
  const base::TimeTicks time2 = time1 + base::Seconds(2);
  auto data2 = PressureData::New(
      /*cpu_utilization=*/0.70, device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update2(PressureSource::kCpu, std::move(data2), time2);
  pressure_manager_overrider_.UpdateClients(std::move(update2));

  ASSERT_TRUE(ExecJs(shell(), "datasets.frame.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.frame.samples.length;"));
  ASSERT_EQ("fair", EvalJs(shell(), "datasets.frame.samples[0].state;"));
  ASSERT_TRUE(ExecJs(shell(), "datasets.dedicatedWorker.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.dedicatedWorker.samples.length;"));
  ASSERT_EQ("fair",
            EvalJs(shell(), "datasets.dedicatedWorker.samples[0].state;"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "datasets.sharedWorker.waitForUpdates(1);"));
    ASSERT_EQ(1, EvalJs(shell(), "datasets.sharedWorker.samples.length;"));
    ASSERT_EQ("fair",
              EvalJs(shell(), "datasets.sharedWorker.samples[0].state;"));
  }
}

IN_PROC_BROWSER_TEST_F(ComputePressureBrowserTest, DeliverDataForPiP) {
  // Play video.
  ASSERT_TRUE(ExecJs(shell(), "video.play();"));
  // Make video in Picture-in-Picture.
  ASSERT_TRUE(ExecJs(shell(), "video.requestPictureInPicture();"));
  EXPECT_TRUE(shell()->web_contents()->HasPictureInPictureVideo());

  // Start PressureObserver in frame, dedicated worker and shared worker.
  ASSERT_TRUE(ExecJs(shell(), "observer.observe('cpu');"));
  ASSERT_TRUE(ExecJs(shell(), "startDedicatedWorker();"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "startSharedWorker();"));
  }

  // Focus on cross-origin iframe, observers can not receive updates by
  // default. If the frame is same origin with initiators of active
  // Picture-in-Picture sessions, observers can receive updates.
  ASSERT_TRUE(ExecJs(shell(), "cross_origin_iframe.focus();"));

  // Deliver update.
  const base::TimeTicks time1 = base::TimeTicks::Now();
  auto data1 = PressureData::New(
      /*cpu_utilization=*/0.30, device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update1(PressureSource::kCpu, std::move(data1), time1);
  pressure_manager_overrider_.UpdateClients(std::move(update1));

  ASSERT_TRUE(ExecJs(shell(), "datasets.frame.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.frame.samples.length;"));
  ASSERT_EQ("nominal", EvalJs(shell(), "datasets.frame.samples[0].state;"));
  ASSERT_TRUE(ExecJs(shell(), "datasets.dedicatedWorker.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.dedicatedWorker.samples.length;"));
  ASSERT_EQ("nominal",
            EvalJs(shell(), "datasets.dedicatedWorker.samples[0].state;"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "datasets.sharedWorker.waitForUpdates(1);"));
    ASSERT_EQ(1, EvalJs(shell(), "datasets.sharedWorker.samples.length;"));
    ASSERT_EQ("nominal",
              EvalJs(shell(), "datasets.sharedWorker.samples[0].state;"));
  }

  // Exit Picture-in-Picture, so observers can not receive updates.
  ASSERT_TRUE(ExecJs(shell(), "document.exitPictureInPicture();"));
  EXPECT_FALSE(shell()->web_contents()->HasPictureInPictureVideo());

  // Deliver update.
  const base::TimeTicks time2 = time1 + base::Seconds(2);
  auto data2 = PressureData::New(
      /*cpu_utilization=*/0.85, device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update2(PressureSource::kCpu, std::move(data2), time2);
  pressure_manager_overrider_.UpdateClients(std::move(update2));

  // Focus on main frame, observers can receive updates again.
  ASSERT_TRUE(ExecJs(shell(), "parent.focus();"));

  // Deliver update.
  const base::TimeTicks time3 = time2 + base::Seconds(2);
  auto data3 = PressureData::New(
      /*cpu_utilization=*/0.85, device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update3(PressureSource::kCpu, std::move(data3), time3);
  pressure_manager_overrider_.UpdateClients(std::move(update3));

  ASSERT_TRUE(ExecJs(shell(), "datasets.frame.waitForUpdates(2);"));
  ASSERT_EQ(2, EvalJs(shell(), "datasets.frame.samples.length;"));
  ASSERT_EQ("serious", EvalJs(shell(), "datasets.frame.samples[1].state;"));
  ASSERT_TRUE(ExecJs(shell(), "datasets.dedicatedWorker.waitForUpdates(2);"));
  ASSERT_EQ(2, EvalJs(shell(), "datasets.dedicatedWorker.samples.length;"));
  ASSERT_EQ("serious",
            EvalJs(shell(), "datasets.dedicatedWorker.samples[1].state;"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "datasets.sharedWorker.waitForUpdates(2);"));
    ASSERT_EQ(2, EvalJs(shell(), "datasets.sharedWorker.samples.length;"));
    ASSERT_EQ("serious",
              EvalJs(shell(), "datasets.sharedWorker.samples[1].state;"));
  }
}

IN_PROC_BROWSER_TEST_F(ComputePressureBrowserTest, DeliverDataForCapturing) {
  // Start capturing.
  ASSERT_TRUE(ExecJs(shell(), "startCapturing();"));

  // Start PressureObserver in frame, dedicated worker and shared worker.
  ASSERT_TRUE(ExecJs(shell(), "observer.observe('cpu');"));
  ASSERT_TRUE(ExecJs(shell(), "startDedicatedWorker();"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "startSharedWorker();"));
  }

  // Focus on cross-origin iframe, observers can not receive updates by
  // default. If the frame is capturing, observers can receive updates.
  ASSERT_TRUE(ExecJs(shell(), "cross_origin_iframe.focus();"));

  // Deliver update.
  const base::TimeTicks time1 = base::TimeTicks::Now();
  auto data1 = PressureData::New(
      /*cpu_utilization=*/0.30, device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update1(PressureSource::kCpu, std::move(data1), time1);
  pressure_manager_overrider_.UpdateClients(std::move(update1));

  ASSERT_TRUE(ExecJs(shell(), "datasets.frame.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.frame.samples.length;"));
  ASSERT_EQ("nominal", EvalJs(shell(), "datasets.frame.samples[0].state;"));
  ASSERT_TRUE(ExecJs(shell(), "datasets.dedicatedWorker.waitForUpdates(1);"));
  ASSERT_EQ(1, EvalJs(shell(), "datasets.dedicatedWorker.samples.length;"));
  ASSERT_EQ("nominal",
            EvalJs(shell(), "datasets.dedicatedWorker.samples[0].state;"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "datasets.sharedWorker.waitForUpdates(1);"));
    ASSERT_EQ(1, EvalJs(shell(), "datasets.sharedWorker.samples.length;"));
    ASSERT_EQ("nominal",
              EvalJs(shell(), "datasets.sharedWorker.samples[0].state;"));
  }

  // Stop capturing.
  ASSERT_TRUE(ExecJs(shell(), "stopCapturing();"));

  // Deliver update.
  const base::TimeTicks time2 = time1 + base::Seconds(2);
  auto data2 = PressureData::New(
      /*cpu_utilization=*/0.70, device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update2(PressureSource::kCpu, std::move(data2), time2);
  pressure_manager_overrider_.UpdateClients(std::move(update2));

  // Focus on main frame, observers can receive updates again.
  ASSERT_TRUE(ExecJs(shell(), "parent.focus();"));

  // Deliver update.
  const base::TimeTicks time3 = time2 + base::Seconds(2);
  auto data3 = PressureData::New(
      /*cpu_utilization=*/0.85, device::mojom::kDefaultOwnContributionEstimate);
  PressureUpdate update3(PressureSource::kCpu, std::move(data3), time3);
  pressure_manager_overrider_.UpdateClients(std::move(update3));

  ASSERT_TRUE(ExecJs(shell(), "datasets.frame.waitForUpdates(2);"));
  ASSERT_EQ(2, EvalJs(shell(), "datasets.frame.samples.length;"));
  ASSERT_EQ("serious", EvalJs(shell(), "datasets.frame.samples[1].state;"));
  ASSERT_TRUE(ExecJs(shell(), "datasets.dedicatedWorker.waitForUpdates(2);"));
  ASSERT_EQ(2, EvalJs(shell(), "datasets.dedicatedWorker.samples.length;"));
  ASSERT_EQ("serious",
            EvalJs(shell(), "datasets.dedicatedWorker.samples[1].state;"));
  if (SupportsSharedWorker()) {
    ASSERT_TRUE(ExecJs(shell(), "datasets.sharedWorker.waitForUpdates(2);"));
    ASSERT_EQ(2, EvalJs(shell(), "datasets.sharedWorker.samples.length;"));
    ASSERT_EQ("serious",
              EvalJs(shell(), "datasets.sharedWorker.samples[1].state;"));
  }
}

}  // namespace content