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

#include "weblayer/browser/browser_main_parts_impl.h"

#include "base/base_switches.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/task/current_thread.h"
#include "base/task/task_traits.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "cc/base/switches.h"
#include "components/captive_portal/core/buildflags.h"
#include "components/performance_manager/embedder/performance_manager_lifetime.h"
#include "components/performance_manager/embedder/performance_manager_registry.h"
#include "components/prefs/pref_service.h"
#include "components/startup_metric_utils/browser/startup_metric_utils.h"
#include "components/subresource_filter/content/browser/ruleset_service.h"
#include "components/translate/core/browser/translate_download_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/main_function_params.h"
#include "content/public/common/page_visibility_state.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/url_constants.h"
#include "ui/base/resource/resource_bundle.h"
#include "weblayer/browser/browser_process.h"
#include "weblayer/browser/cookie_settings_factory.h"
#include "weblayer/browser/feature_list_creator.h"
#include "weblayer/browser/heavy_ad_service_factory.h"
#include "weblayer/browser/host_content_settings_map_factory.h"
#include "weblayer/browser/i18n_util.h"
#include "weblayer/browser/no_state_prefetch/no_state_prefetch_link_manager_factory.h"
#include "weblayer/browser/no_state_prefetch/no_state_prefetch_manager_factory.h"
#include "weblayer/browser/permissions/weblayer_permissions_client.h"
#include "weblayer/browser/stateful_ssl_host_state_delegate_factory.h"
#include "weblayer/browser/subresource_filter_profile_context_factory.h"
#include "weblayer/browser/translate_accept_languages_factory.h"
#include "weblayer/browser/translate_ranker_factory.h"
#include "weblayer/browser/webui/web_ui_controller_factory.h"
#include "weblayer/grit/weblayer_resources.h"
#include "weblayer/public/main.h"

#if defined(OS_ANDROID)
#include "base/command_line.h"
#include "components/crash/content/browser/child_exit_observer_android.h"
#include "components/crash/content/browser/child_process_crash_observer_android.h"
#include "components/crash/core/common/crash_key.h"
#include "components/javascript_dialogs/android/app_modal_dialog_view_android.h"  // nogncheck
#include "components/javascript_dialogs/app_modal_dialog_manager.h"  // nogncheck
#include "components/metrics/metrics_service.h"
#include "components/variations/variations_ids_provider.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "net/android/network_change_notifier_factory_android.h"
#include "net/base/network_change_notifier.h"
#include "weblayer/browser/android/metrics/uma_utils.h"
#include "weblayer/browser/android/metrics/weblayer_metrics_service_client.h"
#include "weblayer/browser/java/jni/MojoInterfaceRegistrar_jni.h"
#include "weblayer/browser/media/local_presentation_manager_factory.h"
#include "weblayer/browser/media/media_router_factory.h"
#include "weblayer/browser/webapps/weblayer_webapps_client.h"
#include "weblayer/browser/weblayer_factory_impl_android.h"
#include "weblayer/common/features.h"
#endif

#if defined(USE_AURA) && defined(USE_X11)
#include "ui/base/ui_base_features.h"
#include "ui/events/devices/x11/touch_factory_x11.h"  // nogncheck
#endif
// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is
// complete.
#if defined(USE_AURA) && (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
#include "ui/base/ime/init/input_method_initializer.h"
#endif

#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
#include "weblayer/browser/captive_portal_service_factory.h"
#endif

namespace weblayer {

namespace {

// Indexes and publishes the subresource filter ruleset data from resources in
// the resource bundle.
void PublishSubresourceFilterRulesetFromResourceBundle() {
  // First obtain the version of the ruleset data from the manifest.
  std::string ruleset_manifest_string =
      ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
          IDR_SUBRESOURCE_FILTER_UNINDEXED_RULESET_MANIFEST_JSON);
  auto ruleset_manifest = base::JSONReader::Read(ruleset_manifest_string);
  DCHECK(ruleset_manifest);
  std::string* content_version = ruleset_manifest->FindStringKey("version");

  // Instruct the RulesetService to obtain the unindexed ruleset data from the
  // ResourceBundle and give it the version of that data.
  auto* ruleset_service =
      BrowserProcess::GetInstance()->subresource_filter_ruleset_service();
  subresource_filter::UnindexedRulesetInfo ruleset_info;
  ruleset_info.resource_id = IDR_SUBRESOURCE_FILTER_UNINDEXED_RULESET;
  ruleset_info.content_version = *content_version;
  ruleset_service->IndexAndStoreAndPublishRulesetIfNeeded(ruleset_info);
}

// Instantiates all weblayer KeyedService factories, which is
// especially important for services that should be created at profile
// creation time as compared to lazily on first access.
void EnsureBrowserContextKeyedServiceFactoriesBuilt() {
#if BUILDFLAG(ENABLE_CAPTIVE_PORTAL_DETECTION)
  CaptivePortalServiceFactory::GetInstance();
#endif
  HeavyAdServiceFactory::GetInstance();
  HostContentSettingsMapFactory::GetInstance();
  StatefulSSLHostStateDelegateFactory::GetInstance();
  CookieSettingsFactory::GetInstance();
  TranslateAcceptLanguagesFactory::GetInstance();
  TranslateRankerFactory::GetInstance();
  NoStatePrefetchLinkManagerFactory::GetInstance();
  NoStatePrefetchManagerFactory::GetInstance();
  SubresourceFilterProfileContextFactory::GetInstance();
#if defined(OS_ANDROID)
  if (MediaRouterFactory::IsFeatureEnabled()) {
    LocalPresentationManagerFactory::GetInstance();
    MediaRouterFactory::GetInstance();
  }
#endif
}

void StopMessageLoop(base::OnceClosure quit_closure) {
  for (auto it = content::RenderProcessHost::AllHostsIterator(); !it.IsAtEnd();
       it.Advance()) {
    it.GetCurrentValue()->DisableKeepAliveRefCount();
  }

  std::move(quit_closure).Run();
}

}  // namespace

BrowserMainPartsImpl::BrowserMainPartsImpl(
    MainParams* params,
    const content::MainFunctionParams& main_function_params,
    std::unique_ptr<PrefService> local_state)
    : params_(params),
      main_function_params_(main_function_params),
      local_state_(std::move(local_state)) {}

BrowserMainPartsImpl::~BrowserMainPartsImpl() = default;

int BrowserMainPartsImpl::PreCreateThreads() {
  // Make sure permissions client has been set.
  WebLayerPermissionsClient::GetInstance();
#if defined(OS_ANDROID)
  // The ChildExitObserver needs to be created before any child process is
  // created because it needs to be notified during process creation.
  crash_reporter::ChildExitObserver::Create();
  crash_reporter::ChildExitObserver::GetInstance()->RegisterClient(
      std::make_unique<crash_reporter::ChildProcessCrashObserver>());

  crash_reporter::InitializeCrashKeys();

  // WebLayer initializes the MetricsService once consent is determined.
  // Determining consent is async and potentially slow. VariationsIdsProvider
  // is responsible for updating the X-Client-Data header. To ensure the header
  // is always provided, VariationsIdsProvider is registered now.
  //
  // Chrome registers the VariationsIdsProvider from PreCreateThreads() as well.
  auto* metrics_client = WebLayerMetricsServiceClient::GetInstance();
  metrics_client->GetMetricsService()
      ->synthetic_trial_registry()
      ->AddSyntheticTrialObserver(
          variations::VariationsIdsProvider::GetInstance());
#endif

  return content::RESULT_CODE_NORMAL_EXIT;
}

void BrowserMainPartsImpl::PreMainMessageLoopStart() {
#if defined(USE_AURA) && defined(USE_X11)
  if (!features::IsUsingOzonePlatform())
    ui::TouchFactory::SetTouchDeviceListFromCommandLine();
#endif
}

int BrowserMainPartsImpl::PreEarlyInitialization() {
  browser_process_ = std::make_unique<BrowserProcess>(std::move(local_state_));

// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is
// complete.
#if defined(USE_AURA) && (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
  ui::InitializeInputMethodForTesting();
#endif
#if defined(OS_ANDROID)
  net::NetworkChangeNotifier::SetFactory(
      new net::NetworkChangeNotifierFactoryAndroid());

  WebLayerWebappsClient::Create();
#endif

  translate::TranslateDownloadManager* download_manager =
      translate::TranslateDownloadManager::GetInstance();
  download_manager->set_url_loader_factory(
      BrowserProcess::GetInstance()->GetSharedURLLoaderFactory());
  download_manager->set_application_locale(i18n::GetApplicationLocale());

  return content::RESULT_CODE_NORMAL_EXIT;
}

void BrowserMainPartsImpl::PostCreateThreads() {
  performance_manager_lifetime_ =
      std::make_unique<performance_manager::PerformanceManagerLifetime>(
          performance_manager::Decorators::kMinimal, base::DoNothing());
}

int BrowserMainPartsImpl::PreMainMessageLoopRun() {
  FeatureListCreator::GetInstance()->PerformPreMainMessageLoopStartup();

  // It's necessary to have a complete dependency graph of
  // BrowserContextKeyedServices before calling out to the delegate (which
  // will potentially create a profile), so that a profile creation message is
  // properly dispatched to the factories that want to create their services
  // at profile creation time.
  EnsureBrowserContextKeyedServiceFactoriesBuilt();

  params_->delegate->PreMainMessageLoopRun();

  content::WebUIControllerFactory::RegisterFactory(
      WebUIControllerFactory::GetInstance());

  BrowserProcess::GetInstance()->PreMainMessageLoopRun();

  // Publish the ruleset data. On the vast majority of runs this will
  // effectively be a no-op as the version of the data changes at most once per
  // release. Nonetheless, post it as a best-effort task to take it off the
  // critical path of startup. Note that best-effort tasks are guaranteed to
  // execute within a reasonable delay (assuming of course that the app isn't
  // shut down first).
  content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
      ->PostTask(
          FROM_HERE,
          base::BindOnce(&PublishSubresourceFilterRulesetFromResourceBundle));

  if (main_function_params_.ui_task) {
    std::move(*main_function_params_.ui_task).Run();
    delete main_function_params_.ui_task;
    run_message_loop_ = false;
  }

#if defined(OS_ANDROID)
  // On Android, retrieve the application start time from Java and record it. On
  // other platforms, the application start time was already recorded in the
  // constructor of ContentMainDelegateImpl.
  startup_metric_utils::RecordApplicationStartTime(GetApplicationStartTime());
#endif  // defined(OS_ANDROID)
  // Record the time at which the main message loop starts. Must be recorded
  // after application start time (see startup_metric_utils.h).
  startup_metric_utils::RecordBrowserMainMessageLoopStart(
      base::TimeTicks::Now(), /* is_first_run */ false);

#if defined(OS_ANDROID)
  memory_metrics_logger_ = std::make_unique<metrics::MemoryMetricsLogger>();

  // Set the global singleton app modal dialog factory.
  javascript_dialogs::AppModalDialogManager::GetInstance()
      ->SetNativeDialogFactory(base::BindRepeating(
          [](javascript_dialogs::AppModalDialogController* controller)
              -> javascript_dialogs::AppModalDialogView* {
            return new javascript_dialogs::AppModalDialogViewAndroid(
                base::android::AttachCurrentThread(), controller,
                controller->web_contents()->GetTopLevelNativeWindow());
          }));

  Java_MojoInterfaceRegistrar_registerMojoInterfaces(
      base::android::AttachCurrentThread());
#endif

  return content::RESULT_CODE_NORMAL_EXIT;
}

void BrowserMainPartsImpl::WillRunMainMessageLoop(
    std::unique_ptr<base::RunLoop>& run_loop) {
  if (run_message_loop_) {
    // Wrap the method that stops the message loop so we can do other shutdown
    // cleanup inside content.
    params_->delegate->SetMainMessageLoopQuitClosure(
        base::BindOnce(StopMessageLoop, run_loop->QuitClosure()));
  } else {
    run_loop.reset();
  }
}

void BrowserMainPartsImpl::PostMainMessageLoopRun() {
  params_->delegate->PostMainMessageLoopRun();
  browser_process_->StartTearDown();

  performance_manager_lifetime_.reset();
}

}  // namespace weblayer
