// 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/files/file_util.h"
#include "base/path_service.h"
#include "build/build_config.h"
#include "cc/test/pixel_comparator.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/common/content_paths.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_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "gpu/config/gpu_finch_features.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
#endif

// TODO(crbug.com/40625383): Move the baselines to skia gold for easier
//   rebaselining when all platforms are supported.

// To rebaseline this test on all platforms:
// 1. Run a CQ+1 dry run.
// 2. Click the failing bots for android, windows, mac, and linux.
// 3. Find the failing content_browsertests step.
// 4. Click the "Deterministic failure" link for the failing test case.
// 5. Copy the "Actual pixels" data url and paste into browser.
// 6. Save the image into your chromium checkout in content/test/data/forms/.

namespace content {

class FormControlsBrowserTest : public ContentBrowserTest {
 public:
  void SetUp() override {
    EnablePixelOutput(/*force_device_scale_factor=*/1.f);
    ContentBrowserTest::SetUp();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // The --disable-lcd-text flag helps text render more similarly on
    // different bots and platform.
    command_line->AppendSwitch(switches::kDisableLCDText);

    // This is required to allow dark mode to be used on some platforms.
    command_line->AppendSwitch(switches::kForceDarkMode);
  }

  void RunTest(const std::string& screenshot_filename,
               const std::string& body_html,
               int screenshot_width,
               int screenshot_height) {
    base::ScopedAllowBlockingForTesting allow_blocking;

    std::string platform_suffix;
#if BUILDFLAG(IS_MAC)
    platform_suffix = "_mac";
#elif BUILDFLAG(IS_WIN)
    platform_suffix = "_win";
#elif BUILDFLAG(IS_LINUX)
    platform_suffix = "_linux";
#elif BUILDFLAG(IS_CHROMEOS)
    platform_suffix = "_chromeos";
#elif BUILDFLAG(IS_ANDROID)
    int sdk_int = base::android::BuildInfo::GetInstance()->sdk_int();
    if (sdk_int >= base::android::SDK_VERSION_T) {
      platform_suffix = "_android_T";
    } else {
      platform_suffix = "_android";
    }
#elif BUILDFLAG(IS_FUCHSIA)
    platform_suffix = "_fuchsia";
#elif BUILDFLAG(IS_IOS)
    platform_suffix = "_ios";
#endif

    base::FilePath dir_test_data;
    ASSERT_TRUE(base::PathService::Get(DIR_TEST_DATA, &dir_test_data));
    base::FilePath golden_filepath =
        dir_test_data.AppendASCII("forms").AppendASCII(screenshot_filename +
                                                       ".png");

    base::FilePath golden_filepath_platform =
        golden_filepath.InsertBeforeExtensionASCII(platform_suffix);
    if (base::PathExists(golden_filepath_platform)) {
      golden_filepath = golden_filepath_platform;
    }

    ASSERT_TRUE(
        NavigateToURL(shell()->web_contents(),
                      GURL("data:text/html,<!DOCTYPE html>" + body_html)));

#if BUILDFLAG(IS_APPLE)
    // This fuzzy pixel comparator handles several mac behaviors:
    // - Different font rendering after 10.14
    // - Slight differences in radio and checkbox rendering in 10.15
    // TODO(wangxianzhu): Tighten these parameters.
    auto comparator = cc::FuzzyPixelComparator()
                          .DiscardAlpha()
                          .SetErrorPixelsPercentageLimit(26.f)
                          .SetAvgAbsErrorLimit(20.f)
                          .SetAbsErrorLimit(120);
#elif BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || (OS_LINUX) || \
    BUILDFLAG(IS_FUCHSIA)
    // Different versions of android may have slight differences in rendering.
    // Some versions have more significant differences than others, which are
    // tracked separately in separate baseline image files. The less significant
    // differences are accommodated for with this fuzzy pixel comparator.
    // This also applies to different versions of other OSes.
    auto comparator = cc::FuzzyPixelComparator()
                          .DiscardAlpha()
                          .SetErrorPixelsPercentageLimit(11.f)
                          .SetAvgAbsErrorLimit(5.f)
                          .SetAbsErrorLimit(140);
#else
    cc::AlphaDiscardingExactPixelComparator comparator;
#endif
    EXPECT_TRUE(CompareWebContentsOutputToReference(
        shell()->web_contents(), golden_filepath,
        gfx::Size(screenshot_width, screenshot_height), comparator));
  }

  // Check if the test can run on the current system.
  bool SkipTestForOldAndroidVersions() const {
#if BUILDFLAG(IS_ANDROID)
    // Lower versions of android running on older devices, ex Nexus 5, render
    // form controls with a too large of a difference -- >20% error -- to
    // pixel compare.
    if (base::android::BuildInfo::GetInstance()->sdk_int() <
        base::android::SDK_VERSION_OREO) {
      return true;
    }
#endif  // BUILDFLAG(IS_ANDROID)
    return false;
  }
};

// Checkbox renders differently on Android x86. crbug.com/1238283
// Checkbox renders differently on Windows. See: crbug.com/377986468
#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86)
#define MAYBE_Checkbox DISABLED_Checkbox
#elif BUILDFLAG(IS_WIN)
#define MAYBE_Checkbox DISABLED_Checkbox
#else
#define MAYBE_Checkbox Checkbox
#endif

IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, MAYBE_Checkbox) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_checkbox",
          "<input type=checkbox>"
          "<input type=checkbox checked>"
          "<input type=checkbox disabled>"
          "<input type=checkbox checked disabled>"
          "<input type=checkbox id=\"indeterminate\">"
          "<script>"
          "  document.getElementById('indeterminate').indeterminate = true"
          "</script>",
          /* screenshot_width */ 130,
          /* screenshot_height */ 40);
}

IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, Radio) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_radio",
          "<input type=radio>"
          "<input type=radio checked>"
          "<input type=radio disabled>"
          "<input type=radio checked disabled>"
          "<input type=radio id=\"indeterminate\">"
          "<script>"
          "  document.getElementById('indeterminate').indeterminate = true"
          "</script>",
          /* screenshot_width */ 140,
          /* screenshot_height */ 40);
}

#if BUILDFLAG(IS_MAC)
#define MAYBE_DarkModeTextSelection DISABLED_DarkModeTextSelection
#else
#define MAYBE_DarkModeTextSelection DarkModeTextSelection
#endif
IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, MAYBE_DarkModeTextSelection) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_dark_mode_text_selection",
          "<meta name=\"color-scheme\" content=\"dark\">"
          "<div id=\"target\">This is some basic text that we are going to "
          "select.</div>"
          "<script>"
          "  let container = document.getElementById('target');"
          "  container.focus();"
          "  let targetText = container.firstChild;"
          "  let selectionRange = window.getSelection();"
          "  selectionRange.setBaseAndExtent(targetText, 5, targetText, 35);"
          "</script>",
          /* screenshot_width */ 400,
          /* screenshot_height */ 40);
}

IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, Input) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_input",
          "<style>body {margin: 8px} input {width: 150px; "
          "margin-bottom: 18px}</style>"
          "<input type=\"text\" /><br>"
          "<input type=\"number\" /><br>"
          "<input type=\"search\" /><br>"
          "<input type=\"email\" /><br>"
          "<input type=\"password\" /><br>"
          "<!-- border -->"
          "<input type=\"text\" style=\"border: 3px solid lime;\"/><br>"
          "<!-- shadow -->"
          "<input type=\"text\" style=\"box-shadow: 4px 4px 10px "
          "rgba(255,0,0,0.5), inset 4px 4px 4px rgba(0,255,0,0.5);\"/><br>"
          "<!-- disabled -->"
          "<input type=\"text\" disabled/>",
          /* screenshot_width */ 200,
          /* screenshot_height */ 330);
}

// Renders differently on Windows. See: crbug.com/377986468
#if (BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS))
#define MAYBE_Textarea DISABLED_Textarea
#elif BUILDFLAG(IS_WIN)
#define MAYBE_Textarea DISABLED_Textarea
#else
#define MAYBE_Textarea Textarea
#endif
IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, MAYBE_Textarea) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_textarea",
          R"HTML(
           <style>
             body {margin: 8px} textarea {width: 150px; margin-bottom: 18px}
           </style>
           <textarea></textarea><br>
           <textarea style="border: 3px solid lime"></textarea><br>
           <!-- shadow -->
           <textarea style="box-shadow: 4px 4px 10px rgba(255,0,0,0.5),
            inset 4px 4px 4px rgba(0,255,0,0.5);"></textarea><br>
           <!-- disabled -->
           <textarea disabled></textarea>)HTML",
          /* screenshot_width */ 200,
          /* screenshot_height */ 260);
}

IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, Button) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_button",
          R"HTML(
            <style>body {margin: 8px} input {margin-bottom: 18px;}</style>
            <input type="button" value="button"/><br>
            <input type="submit" /><br>
            <input type="reset" /><br>
            <input type="file" /><br>
            <!-- border -->
            <input type="button" value="button"
             style="border: 3px solid lime;"/><br>
            <!-- shadow -->
            <input type="button" value="button"
             style="box-shadow: 4px 4px 10px
             rgba(255,0,0,0.5), inset 4px 4px 4px rgba(0,255,0,0.5);"/><br>
            <!-- disabled -->
            <input type="button" value="button" disabled/>)HTML",
          /* screenshot_width */ 200,
          /* screenshot_height */ 300);
}

// TODO(crbug.com/1160104/#25) This test creates large average_error_rate on
// Android FYI SkiaRenderer Vulkan. Disable it until a resolution for is
// found.
// Also renders differently on Windows. See: crbug.com/377986468
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_ColorInput DISABLED_ColorInput
#elif BUILDFLAG(IS_WIN)
#define MAYBE_ColorInput DISABLED_ColorInput
#else
#define MAYBE_ColorInput ColorInput
#endif
IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, MAYBE_ColorInput) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_color_input",
          R"HTML(
            <style>body {margin: 8px} input {margin-bottom: 18px;}</style>
            <input type="color" /><br>
            <input type="color" value='%2300ff00' /><br>
            <input type="color" list /><br>
            <!-- border -->
            <input type="color" value="%2300ff00"
             style="border: 3px solid lime;"/><br>
            <!-- disabled -->
            <input type="color" disabled/>)HTML",
          /* screenshot_width */ 200,
          /* screenshot_height */ 250);
}

IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, Select) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_select",
          R"HTML(
          <style>
              body {margin: 8px}
              select {margin-bottom: 18px;  width: 170px;}
          </style>
          <select></select><br>
          <select style="color:darkturquoise"></select><br>
          <!-- border -->
          <select style="border: 3px solid lime;"></select><br>
          <!-- shadow -->
          <select style="box-shadow: 4px 4px 10px rgba(255,0,0,0.5),
           inset 4px 4px 4px rgba(0,255,0,0.5);"></select><br>
          <!-- disabled -->
          <select disabled></select><br>)HTML",
          /* screenshot_width */ 200,
          /* screenshot_height */ 200);
}

// Renders differently on Windows. See: crbug.com/377986468
#if BUILDFLAG(IS_WIN)
#define MAYBE_MultiSelect DISABLED_MultiSelect
#else
#define MAYBE_MultiSelect MultiSelect
#endif
IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, MAYBE_MultiSelect) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_multi_select",
          R"HTML(
            <style>
              body {margin: 8px}
              select {margin-bottom: 18px; width: 170px; }
            </style>
            <select multiple autofocus size=5>
             <optgroup label="unstyled select"></optgroup>
          </select> <br>
          <!-- border -->
          <select multiple style="border: 3px solid lime;" size=5>
            <optgroup label="thick lime border"></optgroup>
          </select><br>
          <!-- disabled -->
          <select multiple disabled size=5>
            <optgroup label="disabled select">
            </optgroup>
          </select>)HTML",
          /* screenshot_width */ 200,
          /* screenshot_height */ 330);
}

IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, Progress) {
  if (SkipTestForOldAndroidVersions())
    return;

#if BUILDFLAG(IS_MAC) && !defined(ARCH_CPU_ARM64)
  // The pixel comparison fails on Mac Intel GPUs with Graphite due to MSAA
  // issues.
  // TODO(crbug.com/40940637): Re-enable test if possible.
  if (features::IsSkiaGraphiteEnabled(base::CommandLine::ForCurrentProcess())) {
    return;
  }
#endif

  RunTest("form_controls_browsertest_progress",
          R"HTML(
            <style>
              body {margin: 8px} progress {margin-bottom: 18px}
            </style>
            <progress max="100" value="0"></progress><br>
            <progress max="100" value="5"></progress><br>
            <progress max="100" value="25"></progress><br><br>
            <progress max="100" value="50"></progress><br><br>
            <progress max="100" value="100"></progress><br><br>
            <progress max="100" value="50" style="height:30px"></progress>
          )HTML",
          /* screenshot_width */ 200,
          /* screenshot_height */ 300);
}

IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, Meter) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_meter",
          R"HTML(
            <style>
              body {margin: 8px} meter {margin-bottom: 10px}
            </style>
            <meter min="0" max="100" low="33" high="66" optimum="100"
              value="20"></meter><br>
            <meter min="0" max="100" low="33" high="66" optimum="100"
             value="50"></meter><br>
            <meter min="0" max="100" low="33" high="66" optimum="100"
              value="66"></meter><br>
            <meter min="0" max="100" low="33" high="66" optimum="100"
             value="90"></meter><br>
            <!-- border -->
            <meter style="border-color: %23000000; border-style: solid;
              border-width: 5px;" min="0" max="100" low="30" high="60"
              optimum="100" value="80" ></meter><br>
            <meter style="box-shadow: 4px 4px 10px rgba(255,0,0,0.5),
            inset 4px 4px 4px rgba(0,255,0,0.5);"></meter>)HTML",
          /* screenshot_width */ 150,
          /* screenshot_height */ 200);
}

IN_PROC_BROWSER_TEST_F(FormControlsBrowserTest, Range) {
  if (SkipTestForOldAndroidVersions())
    return;

  RunTest("form_controls_browsertest_range",
          R"HTML(
            <style>
              body {margin: 8px} input {margin-bottom: 18px}
            </style>
            <input type="range"><br>
           )HTML",
          /* screenshot_width */ 150,
          /* screenshot_height */ 150);
}

// TODO(jarhar): Add tests for other elements from
//   https://concrete-hardboard.glitch.me

}  // namespace content