// Copyright 2019 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/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "media/base/media_switches.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "url/gurl.h"

namespace policy {

namespace {
const char kAutoplayTestPageURL[] = "/media/autoplay_iframe.html";
const char kUnifiedAutoplayTestPageURL[] = "/media/unified_autoplay.html";
}  // namespace

class AutoplayPolicyTest : public PolicyTest {
 public:
  AutoplayPolicyTest() {
#if BUILDFLAG(IS_ANDROID)
    scoped_feature_list_.InitAndEnableFeature(media::kAutoplayPoliciesAndroid);
#endif
    // Start two embedded test servers on different ports. This will ensure
    // the test works correctly with cross origin iframes and site-per-process.
    embedded_test_server2()->AddDefaultHandlers(GetChromeTestDataDir());
    EXPECT_TRUE(embedded_test_server()->Start());
    EXPECT_TRUE(embedded_test_server2()->Start());
  }

  void NavigateToTestPage(const std::string& main_origin = std::string(),
                          const std::string& subframe_origin = std::string()) {
    GURL origin =
        main_origin.empty()
            ? embedded_test_server()->GetURL(kAutoplayTestPageURL)
            : embedded_test_server()->GetURL(main_origin, kAutoplayTestPageURL);
    ASSERT_TRUE(NavigateToUrl(origin, this));

    // Navigate the subframe to the test page but on the second origin.
    GURL origin2 = subframe_origin.empty()
                       ? embedded_test_server2()->GetURL(kAutoplayTestPageURL)
                       : embedded_test_server()->GetURL(subframe_origin,
                                                        kAutoplayTestPageURL);
    std::string script = base::StringPrintf(
        "setTimeout(\""
        "document.getElementById('subframe').src='%s';"
        "\",0)",
        origin2.spec().c_str());
    content::TestNavigationObserver load_observer(GetWebContents());
    EXPECT_TRUE(ExecJs(GetWebContents(), script,
                       content::EXECUTE_SCRIPT_NO_USER_GESTURE));
    load_observer.Wait();
  }

  net::EmbeddedTestServer* embedded_test_server2() {
    return &embedded_test_server2_;
  }

  bool TryAutoplay(content::RenderFrameHost* rfh) {
    return content::EvalJs(rfh, "tryPlayback();",
                           content::EXECUTE_SCRIPT_NO_USER_GESTURE)
        .ExtractBool();
  }

  content::WebContents* GetWebContents() {
    return chrome_test_utils::GetActiveWebContents(this);
  }

  content::RenderFrameHost* GetPrimaryMainFrame() {
    return GetWebContents()->GetPrimaryMainFrame();
  }

  content::RenderFrameHost* GetChildFrame() {
    return ChildFrameAt(GetPrimaryMainFrame(), 0);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;

  // Second instance of embedded test server to provide a second test origin.
  net::EmbeddedTestServer embedded_test_server2_;
};

IN_PROC_BROWSER_TEST_F(AutoplayPolicyTest, AutoplayAllowedByPolicy) {
  NavigateToTestPage();

  // Check that autoplay was not allowed.
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Update policy to allow autoplay.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowed, base::Value(true));
  UpdateProviderPolicy(policies);

  // Check that autoplay was allowed by policy.
  NavigateToTestPage();
  EXPECT_TRUE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_TRUE(TryAutoplay(GetChildFrame()));
}

IN_PROC_BROWSER_TEST_F(AutoplayPolicyTest, CrossOriginIframe) {
  NavigateToTestPage("foo.com", "bar.com");

  // Check that autoplay was not allowed.
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Update policy to allow autoplay.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowed, base::Value(true));
  UpdateProviderPolicy(policies);

  // Check that autoplay was allowed by policy.
  NavigateToTestPage("foo.com", "bar.com");
  EXPECT_TRUE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_TRUE(TryAutoplay(GetChildFrame()));
}

// Flaky on Linux. See: crbug.com/1189597.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_AutoplayAllowlist_Allowed DISABLED_AutoplayAllowlist_Allowed
#else
#define MAYBE_AutoplayAllowlist_Allowed AutoplayAllowlist_Allowed
#endif
IN_PROC_BROWSER_TEST_F(AutoplayPolicyTest, MAYBE_AutoplayAllowlist_Allowed) {
  NavigateToTestPage();

  // Check that autoplay was not allowed.
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Create a test allowlist with our origin.
  base::ListValue allowlist;
  allowlist.Append(embedded_test_server()->GetURL("/").spec());

  // Update policy to allow autoplay for our test origin.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowlist,
            base::Value(std::move(allowlist)));
  UpdateProviderPolicy(policies);

  // Check that autoplay was allowed by policy.
  NavigateToTestPage();
  EXPECT_TRUE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_TRUE(TryAutoplay(GetChildFrame()));
}

IN_PROC_BROWSER_TEST_F(AutoplayPolicyTest, AutoplayAllowlist_PatternAllowed) {
  NavigateToTestPage();

  // Check that autoplay was not allowed.
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Create a test allowlist with our origin.
  base::ListValue allowlist;
  allowlist.Append("127.0.0.1:*");

  // Update policy to allow autoplay for our test origin.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowlist,
            base::Value(std::move(allowlist)));
  UpdateProviderPolicy(policies);

  // Check that autoplay was allowed by policy.
  NavigateToTestPage();
  EXPECT_TRUE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_TRUE(TryAutoplay(GetChildFrame()));
}

IN_PROC_BROWSER_TEST_F(AutoplayPolicyTest, AutoplayAllowlist_Missing) {
  NavigateToTestPage();

  // Check that autoplay was not allowed.
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Create a test allowlist with a random origin.
  base::ListValue allowlist;
  allowlist.Append("https://www.example.com");

  // Update policy to allow autoplay for a random origin.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowlist,
            base::Value(std::move(allowlist)));
  UpdateProviderPolicy(policies);

  // Check that autoplay was not allowed.
  NavigateToTestPage();
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));
}

// Flaky on Linux and ChromeOS. See: crbug.com/40745945.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_AutoplayDeniedByPolicy DISABLED_AutoplayDeniedByPolicy
#else
#define MAYBE_AutoplayDeniedByPolicy AutoplayDeniedByPolicy
#endif
IN_PROC_BROWSER_TEST_F(AutoplayPolicyTest, MAYBE_AutoplayDeniedByPolicy) {
  NavigateToTestPage();

  // Check that autoplay was not allowed.
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Update policy to forbid autoplay.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowed, base::Value(false));
  UpdateProviderPolicy(policies);

  // Check that autoplay was not allowed by policy.
  NavigateToTestPage();
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Create a test allowlist with a random origin.
  base::ListValue allowlist;
  allowlist.Append("https://www.example.com");

  // Update policy to allow autoplay for a random origin.
  SetPolicy(&policies, key::kAutoplayAllowlist,
            base::Value(std::move(allowlist)));
  UpdateProviderPolicy(policies);

  // Check that autoplay was not allowed.
  NavigateToTestPage();
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));
}

// Flaky on Linux. See: crbug.com/40745945.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_AutoplayDeniedAllowedWithURL DISABLED_AutoplayDeniedAllowedWithURL
#else
#define MAYBE_AutoplayDeniedAllowedWithURL AutoplayDeniedAllowedWithURL
#endif
IN_PROC_BROWSER_TEST_F(AutoplayPolicyTest, MAYBE_AutoplayDeniedAllowedWithURL) {
  NavigateToTestPage();

  // Check that autoplay was not allowed.
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Update policy to forbid autoplay.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowed, base::Value(false));
  UpdateProviderPolicy(policies);

  // Check that autoplay was not allowed by policy.
  NavigateToTestPage();
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Create a test allowlist with our test origin.
  base::ListValue allowlist;
  allowlist.Append(embedded_test_server()->GetURL("/").spec());

  // Update policy to allow autoplay for our test origin.
  SetPolicy(&policies, key::kAutoplayAllowlist,
            base::Value(std::move(allowlist)));
  UpdateProviderPolicy(policies);

  // Check that autoplay was allowed by policy.
  NavigateToTestPage();
  EXPECT_TRUE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_TRUE(TryAutoplay(GetChildFrame()));
}

// TODO(crbug.com/40742600): Flaky test.
IN_PROC_BROWSER_TEST_F(AutoplayPolicyTest,
                       DISABLED_AutoplayAllowedGlobalAndURL) {
  NavigateToTestPage();

  // Check that autoplay was not allowed.
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Update policy to forbid autoplay.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowed, base::Value(false));
  UpdateProviderPolicy(policies);

  // Check that autoplay was not allowed by policy.
  NavigateToTestPage();
  EXPECT_FALSE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_FALSE(TryAutoplay(GetChildFrame()));

  // Create a test allowlist with our test origin.
  base::ListValue allowlist;
  allowlist.Append(embedded_test_server()->GetURL("/").spec());

  // Update policy to allow autoplay for our test origin.
  SetPolicy(&policies, key::kAutoplayAllowlist,
            base::Value(std::move(allowlist)));
  UpdateProviderPolicy(policies);

  // Check that autoplay was allowed by policy.
  NavigateToTestPage();
  EXPECT_TRUE(TryAutoplay(GetPrimaryMainFrame()));
  EXPECT_TRUE(TryAutoplay(GetChildFrame()));
}

class AutoplayPolicyFencedFrameTest : public AutoplayPolicyTest {
 public:
  AutoplayPolicyFencedFrameTest() = default;
  ~AutoplayPolicyFencedFrameTest() override = default;

  // Prevent additional feature/field trial enablement.
  void SetUpCommandLine(base::CommandLine* command_line) override {}

  void NavigateAndCheckAutoplayAllowed(bool expected_result) {
    ASSERT_TRUE(NavigateToUrl(
        embedded_test_server()->GetURL(kUnifiedAutoplayTestPageURL), this));
    // Append a cross origin fenced frame into the primary main frame.
    content::RenderFrameHost* fenced_frame_host =
        fenced_frame_helper_.CreateFencedFrame(
            GetPrimaryMainFrame(),
            embedded_test_server2()->GetURL(kUnifiedAutoplayTestPageURL));
    ASSERT_NE(nullptr, fenced_frame_host);

    // Check that autoplay works as |expected_result|.
    EXPECT_EQ(TryAutoplay(GetPrimaryMainFrame()), expected_result);
    EXPECT_EQ(TryAutoplay(fenced_frame_host), expected_result);
  }

  bool TryAutoplay(content::RenderFrameHost* rfh) {
    return content::EvalJs(rfh, "attemptPlay();",
                           content::EXECUTE_SCRIPT_NO_USER_GESTURE)
        .ExtractBool();
  }

 protected:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(AutoplayPolicyFencedFrameTest, AutoplayAllowedByPolicy) {
  // Check that autoplay was not allowed.
  NavigateAndCheckAutoplayAllowed(false);

  // Update policy to allow autoplay.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowed, base::Value(true));
  UpdateProviderPolicy(policies);

  // Check that autoplay was allowed by policy.
  NavigateAndCheckAutoplayAllowed(true);
}

IN_PROC_BROWSER_TEST_F(AutoplayPolicyFencedFrameTest,
                       AutoplayAllowlist_Allowed) {
  // Check that autoplay was not allowed.
  NavigateAndCheckAutoplayAllowed(false);

  // Create a test allowlist with our origin.
  base::ListValue allowlist;
  allowlist.Append(embedded_test_server()->GetURL("/").spec());

  // Update policy to allow autoplay for our test origin.
  PolicyMap policies;
  SetPolicy(&policies, key::kAutoplayAllowlist,
            base::Value(std::move(allowlist)));
  UpdateProviderPolicy(policies);

  // Check that autoplay was allowed by policy.
  NavigateAndCheckAutoplayAllowed(true);
}

class AutoplayPolicyBypassTest : public AutoplayPolicyTest,
                                 public testing::WithParamInterface<bool> {
 public:
  AutoplayPolicyBypassTest() {
    if (GetParam()) {
      scoped_feature_list_.InitAndEnableFeature(
          media::kAutoplayBypassForMicCamera);
    } else {
      scoped_feature_list_.InitAndDisableFeature(
          media::kAutoplayBypassForMicCamera);
    }
  }

  void GrantPermission(ContentSettingsType type) {
    HostContentSettingsMapFactory::GetForProfile(
        chrome_test_utils::GetProfile(this))
        ->SetContentSettingCustomScope(ContentSettingsPattern::FromURL(
                                           embedded_test_server()->GetURL("/")),
                                       ContentSettingsPattern::Wildcard(), type,
                                       CONTENT_SETTING_ALLOW);
  }

  void SetAutoplayAllowedPolicy(bool enabled) {
    PolicyMap policies;
    SetPolicy(&policies, key::kAutoplayAllowed, base::Value(enabled));
    UpdateProviderPolicy(policies);
  }

  void ExpectAutoplay(bool expected) {
    EXPECT_EQ(expected, TryAutoplay(GetPrimaryMainFrame()));
    EXPECT_EQ(expected, TryAutoplay(GetChildFrame()));
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_P(AutoplayPolicyBypassTest,
                       AutoplayAllowedByCameraPermission) {
  NavigateToTestPage();
  ExpectAutoplay(false);

  GrantPermission(ContentSettingsType::MEDIASTREAM_CAMERA);

  NavigateToTestPage();
  ExpectAutoplay(GetParam());
}

IN_PROC_BROWSER_TEST_P(AutoplayPolicyBypassTest,
                       AutoplayAllowedByMicrophonePermission) {
  NavigateToTestPage();
  ExpectAutoplay(false);

  GrantPermission(ContentSettingsType::MEDIASTREAM_MIC);

  NavigateToTestPage();
  ExpectAutoplay(GetParam());
}

IN_PROC_BROWSER_TEST_P(AutoplayPolicyBypassTest,
                       AutoplayAllowedByPolicyDespiteNoMicCameraPermission) {
  NavigateToTestPage();
  ExpectAutoplay(false);

  SetAutoplayAllowedPolicy(true);

  NavigateToTestPage();
  ExpectAutoplay(true);
}

// this `Bool()` is whether the `media::kAutoplayBypassForMicCamera` is enabled.
INSTANTIATE_TEST_SUITE_P(All, AutoplayPolicyBypassTest, testing::Bool());

}  // namespace policy
