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

#include "content/browser/font_access/font_access_manager.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/sequence_bound.h"
#include "build/build_config.h"
#include "content/browser/font_access/font_access_test_utils.h"
#include "content/browser/font_access/font_enumeration_cache.h"
#include "content/browser/font_access/font_enumeration_data_source.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/storage_partition.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/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/font_access/font_access.mojom.h"

namespace content {

// This class exists so that tests can be written without enabling
// the kFontAccess feature flag. This will be redundant once the flag
// goes away.
class FontAccessManagerBrowserBase : public ContentBrowserTest {
 public:
  FontAccessManagerBrowserBase() {
    scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
  }

  void TearDownOnMainThread() override {
    font_access_manager()->SkipPrivacyChecksForTesting(false);
  }

  RenderFrameHost* main_rfh() {
    return shell()->web_contents()->GetPrimaryMainFrame();
  }

  FontAccessManager* font_access_manager() {
    auto* storage_partition =
        static_cast<StoragePartitionImpl*>(main_rfh()->GetStoragePartition());
    return storage_partition->GetFontAccessManager();
  }

  // Must be called before the StoragePartition's FontAccessManager is accessed.
  //
  // This method replaces the StoragePartition's FontAccessManager. This leads
  // to confusion if the old FontAccessManager is already in use, either due to
  // a font_access_manager() call, or due to JavaScript connecting to the Font
  // Access API.
  void OverrideFontAccessLocale(std::string locale) {
    base::SequenceBound<FontEnumerationCache> font_enumeration_cache =
        FontEnumerationCache::CreateForTesting(
            base::ThreadPool::CreateSequencedTaskRunner(
                {base::MayBlock(), base::TaskPriority::BEST_EFFORT}),
            FontEnumerationDataSource::Create(), std::move(locale));

    auto* storage_partition =
        static_cast<StoragePartitionImpl*>(main_rfh()->GetStoragePartition());
    storage_partition->SetFontAccessManagerForTesting(
        FontAccessManager::CreateForTesting(std::move(font_enumeration_cache)));
  }

 protected:
  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
  std::unique_ptr<FontEnumerationCache> enumeration_cache_;
};

class FontAccessManagerBrowserTest : public FontAccessManagerBrowserBase {
 public:
  FontAccessManagerBrowserTest() {
    std::vector<base::test::FeatureRef> enabled_features({
        blink::features::kFontAccess,
    });
    scoped_feature_list_->InitWithFeatures(std::move(enabled_features),
                                           /*disabled_features=*/{});
  }
};

// Disabled test: https://crbug.com/1224238
IN_PROC_BROWSER_TEST_F(FontAccessManagerBrowserBase,
                       DISABLED_RendererInterfaceIsBound) {
  ASSERT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));
  // This tests that the renderer interface is bound even if the kFontAccess
  // feature flag is disabled.

  RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
      shell()->web_contents()->GetPrimaryMainFrame());
  mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib =
      rfh->browser_interface_broker_receiver_for_testing();
  blink::mojom::BrowserInterfaceBroker* broker = bib.internal_state()->impl();

  mojo::Remote<blink::mojom::FontAccessManager> manager_remote;
  broker->GetInterface(manager_remote.BindNewPipeAndPassReceiver());

  EXPECT_TRUE(manager_remote.is_bound() && manager_remote.is_connected())
      << "FontAccessManager remote is expected to be bound and connected.";
}

IN_PROC_BROWSER_TEST_F(FontAccessManagerBrowserTest, EnumerationTest) {
  ASSERT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));
  font_access_manager()->SkipPrivacyChecksForTesting(true);

  EvalJsResult result = EvalJs(shell(),
                               "(async () => {"
                               "  const fonts = await self.queryLocalFonts();"
                               "  return fonts.length;"
                               "})()");

  if (FontEnumerationDataSource::IsOsSupported()) {
    EXPECT_LT(0, result.ExtractInt())
        << "Enumeration should return at least one font on supported OS.";
  } else {
    EXPECT_TRUE(!result.is_ok());
  }
}

IN_PROC_BROWSER_TEST_F(FontAccessManagerBrowserTest,
                       EnumerationTestWithInvalidSelect) {
  ASSERT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));
  font_access_manager()->SkipPrivacyChecksForTesting(true);

  EvalJsResult result = EvalJs(shell(),
                               "(async () => {"
                               "  const fonts = await self.queryLocalFonts({"
                               "      postscriptNames: ['invalid-query']"
                               "  });"
                               "  return fonts.length;"
                               "})()");

  if (FontEnumerationDataSource::IsOsSupported()) {
    EXPECT_EQ(0, result.ExtractInt())
        << "Enumeration should return no fonts for an invalid postscriptName.";
  } else {
    EXPECT_TRUE(!result.is_ok());
  }
}

#if BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_F(FontAccessManagerBrowserTest, LocaleTest) {
  ASSERT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));
  OverrideFontAccessLocale("zh-cn");
  font_access_manager()->SkipPrivacyChecksForTesting(true);

  EvalJsResult result =
      EvalJs(shell(),
             "(async () => {"
             "  let fullName = '';"
             "  const fonts = await self.queryLocalFonts();"
             "  for (const item of fonts) {"
             "    if (item.postscriptName == 'MicrosoftYaHei') {"
             "      fullName = item.fullName;"
             "      break;"
             "    }"
             "  }"
             "  return fullName;"
             "})()");
  std::string ms_yahei_utf8 = "微软雅黑";
  EXPECT_EQ(ms_yahei_utf8, result);
}

IN_PROC_BROWSER_TEST_F(FontAccessManagerBrowserTest, UnlocalizedFamilyTest) {
  ASSERT_TRUE(NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html")));
  OverrideFontAccessLocale("zh-cn");
  font_access_manager()->SkipPrivacyChecksForTesting(true);

  EvalJsResult result =
      EvalJs(shell(),
             "(async () => {"
             "  let family = '';"
             "  const fonts = await self.queryLocalFonts();"
             "  for (const item of fonts) {"
             "    if (item.postscriptName == 'MicrosoftYaHei') {"
             "      family = item.family;"
             "      break;"
             "    }"
             "  }"
             "  return family;"
             "})()");
  std::string unlocalized_family = "Microsoft YaHei";
  EXPECT_EQ(unlocalized_family, result);
}
#endif  // BUILDFLAG(IS_WIN)

}  // namespace content
