// Copyright (c) 2013 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "atom/browser/atom_browser_main_parts.h"

#include "atom/browser/api/atom_api_app.h"
#include "atom/browser/api/trackable_object.h"
#include "atom/browser/atom_browser_client.h"
#include "atom/browser/atom_browser_context.h"
#include "atom/browser/bridge_task_runner.h"
#include "atom/browser/browser.h"
#include "atom/browser/io_thread.h"
#include "atom/browser/javascript_environment.h"
#include "atom/browser/node_debugger.h"
#include "atom/common/api/atom_bindings.h"
#include "atom/common/asar/asar_util.h"
#include "atom/common/node_bindings.h"
#include "base/command_line.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/chrome_net_log_helper.h"
#include "components/net_log/chrome_net_log.h"
#include "components/net_log/net_export_file_writer.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/service_manager_connection.h"
#include "services/device/public/mojom/constants.mojom.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/base/idle/idle.h"
#include "ui/base/l10n/l10n_util.h"

#if defined(USE_X11)
#include "chrome/browser/ui/libgtkui/gtk_util.h"
#include "ui/events/devices/x11/touch_factory_x11.h"
#endif

#if defined(ENABLE_PDF_VIEWER)
#include "atom/browser/atom_web_ui_controller_factory.h"
#endif  // defined(ENABLE_PDF_VIEWER)

#if defined(OS_MACOSX)
#include "atom/browser/ui/cocoa/views_delegate_mac.h"
#else
#include "brightray/browser/views/views_delegate.h"
#endif

// Must be included after all other headers.
#include "atom/common/node_includes.h"

namespace atom {

namespace {

template <typename T>
void Erase(T* container, typename T::iterator iter) {
  container->erase(iter);
}

}  // namespace

// static
AtomBrowserMainParts* AtomBrowserMainParts::self_ = nullptr;

AtomBrowserMainParts::AtomBrowserMainParts(
    const content::MainFunctionParams& params)
    : fake_browser_process_(new BrowserProcess),
      browser_(new Browser),
      node_bindings_(NodeBindings::Create(NodeBindings::BROWSER)),
      atom_bindings_(new AtomBindings(uv_default_loop())),
      gc_timer_(true, true),
      main_function_params_(params) {
  DCHECK(!self_) << "Cannot have two AtomBrowserMainParts";
  self_ = this;
  // Register extension scheme as web safe scheme.
  content::ChildProcessSecurityPolicy::GetInstance()->RegisterWebSafeScheme(
      "chrome-extension");
}

AtomBrowserMainParts::~AtomBrowserMainParts() {
  asar::ClearArchives();
  // Leak the JavascriptEnvironment on exit.
  // This is to work around the bug that V8 would be waiting for background
  // tasks to finish on exit, while somehow it waits forever in Electron, more
  // about this can be found at
  // https://github.com/electron/electron/issues/4767. On the other handle there
  // is actually no need to gracefully shutdown V8 on exit in the main process,
  // we already ensured all necessary resources get cleaned up, and it would
  // make quitting faster.
  ignore_result(js_env_.release());
}

// static
AtomBrowserMainParts* AtomBrowserMainParts::Get() {
  DCHECK(self_);
  return self_;
}

bool AtomBrowserMainParts::SetExitCode(int code) {
  if (!exit_code_)
    return false;

  *exit_code_ = code;
  return true;
}

int AtomBrowserMainParts::GetExitCode() {
  return exit_code_ != nullptr ? *exit_code_ : 0;
}

void AtomBrowserMainParts::RegisterDestructionCallback(
    base::OnceClosure callback) {
  // The destructors should be called in reversed order, so dependencies between
  // JavaScript objects can be correctly resolved.
  // For example WebContentsView => WebContents => Session.
  destructors_.insert(destructors_.begin(), std::move(callback));
}

int AtomBrowserMainParts::PreEarlyInitialization() {
  const int result = brightray::BrowserMainParts::PreEarlyInitialization();
  if (result != content::RESULT_CODE_NORMAL_EXIT)
    return result;

#if defined(OS_POSIX)
  HandleSIGCHLD();
#endif

  return content::RESULT_CODE_NORMAL_EXIT;
}

void AtomBrowserMainParts::PostEarlyInitialization() {
  brightray::BrowserMainParts::PostEarlyInitialization();

  // Temporary set the bridge_task_runner_ as current thread's task runner,
  // so we can fool gin::PerIsolateData to use it as its task runner, instead
  // of getting current message loop's task runner, which is null for now.
  bridge_task_runner_ = new BridgeTaskRunner;
  base::ThreadTaskRunnerHandle handle(bridge_task_runner_);

  // The ProxyResolverV8 has setup a complete V8 environment, in order to
  // avoid conflicts we only initialize our V8 environment after that.
  js_env_.reset(new JavascriptEnvironment);

  node_bindings_->Initialize();

  // Create the global environment.
  node::Environment* env = node_bindings_->CreateEnvironment(
      js_env_->context(), js_env_->platform());
  node_env_.reset(new NodeEnvironment(env));

  // Enable support for v8 inspector
  node_debugger_.reset(new NodeDebugger(env));
  node_debugger_->Start(js_env_->platform());

  // Add Electron extended APIs.
  atom_bindings_->BindTo(js_env_->isolate(), env->process_object());

  // Load everything.
  node_bindings_->LoadEnvironment(env);

  // Wrap the uv loop with global env.
  node_bindings_->set_uv_env(env);

  // We already initialized the feature list in
  // brightray::BrowserMainParts::PreEarlyInitialization(), but
  // the user JS script would not have had a chance to alter the command-line
  // switches at that point. Lets reinitialize it here to pick up the
  // command-line changes.
  base::FeatureList::ClearInstanceForTesting();
  brightray::BrowserMainParts::InitializeFeatureList();
}

int AtomBrowserMainParts::PreCreateThreads() {
  const int result = brightray::BrowserMainParts::PreCreateThreads();
  if (!result) {
    fake_browser_process_->SetApplicationLocale(
        brightray::BrowserClient::Get()->GetApplicationLocale());
  }

#if defined(OS_MACOSX)
  ui::InitIdleMonitor();
#endif

  net_log_ = std::make_unique<net_log::ChromeNetLog>();
  auto& command_line = main_function_params_.command_line;
  // start net log trace if --log-net-log is passed in the command line.
  if (command_line.HasSwitch(network::switches::kLogNetLog)) {
    base::FilePath log_file =
        command_line.GetSwitchValuePath(network::switches::kLogNetLog);
    if (!log_file.empty()) {
      net_log_->StartWritingToFile(
          log_file, GetNetCaptureModeFromCommandLine(command_line),
          command_line.GetCommandLineString(), std::string());
    }
  }
  // Initialize net log file exporter.
  net_log_->net_export_file_writer()->Initialize(
      content::BrowserThread::GetTaskRunnerForThread(
          content::BrowserThread::IO));

  // Manage global state of net and other IO thread related.
  io_thread_ = std::make_unique<IOThread>(net_log_.get());

  return result;
}

void AtomBrowserMainParts::PostDestroyThreads() {
  brightray::BrowserMainParts::PostDestroyThreads();
  io_thread_.reset();
}

void AtomBrowserMainParts::ToolkitInitialized() {
  brightray::BrowserMainParts::ToolkitInitialized();
#if defined(OS_MACOSX)
  views_delegate_.reset(new ViewsDelegateMac);
#else
  views_delegate_.reset(new brightray::ViewsDelegate);
#endif
}

void AtomBrowserMainParts::PreMainMessageLoopRun() {
  js_env_->OnMessageLoopCreated();

  // Run user's main script before most things get initialized, so we can have
  // a chance to setup everything.
  node_bindings_->PrepareMessageLoop();
  node_bindings_->RunMessageLoop();

#if defined(USE_X11)
  ui::TouchFactory::SetTouchDeviceListFromCommandLine();
#endif

  // Start idle gc.
  gc_timer_.Start(FROM_HERE, base::TimeDelta::FromMinutes(1),
                  base::Bind(&v8::Isolate::LowMemoryNotification,
                             base::Unretained(js_env_->isolate())));

#if defined(ENABLE_PDF_VIEWER)
  content::WebUIControllerFactory::RegisterFactory(
      AtomWebUIControllerFactory::GetInstance());
#endif  // defined(ENABLE_PDF_VIEWER)

  brightray::BrowserMainParts::PreMainMessageLoopRun();
  bridge_task_runner_->MessageLoopIsReady();
  bridge_task_runner_ = nullptr;

#if defined(USE_X11)
  libgtkui::GtkInitFromCommandLine(*base::CommandLine::ForCurrentProcess());
#endif

#if !defined(OS_MACOSX)
  // The corresponding call in macOS is in AtomApplicationDelegate.
  Browser::Get()->WillFinishLaunching();
  Browser::Get()->DidFinishLaunching(base::DictionaryValue());
#endif

  // Notify observers that main thread message loop was initialized.
  Browser::Get()->PreMainMessageLoopRun();
}

bool AtomBrowserMainParts::MainMessageLoopRun(int* result_code) {
  exit_code_ = result_code;
  return brightray::BrowserMainParts::MainMessageLoopRun(result_code);
}

void AtomBrowserMainParts::PostMainMessageLoopStart() {
  brightray::BrowserMainParts::PostMainMessageLoopStart();
#if defined(OS_POSIX)
  HandleShutdownSignals();
#endif
}

void AtomBrowserMainParts::PostMainMessageLoopRun() {
  brightray::BrowserMainParts::PostMainMessageLoopRun();

  js_env_->OnMessageLoopDestroying();

#if defined(OS_MACOSX)
  FreeAppDelegate();
#endif

  // Make sure destruction callbacks are called before message loop is
  // destroyed, otherwise some objects that need to be deleted on IO thread
  // won't be freed.
  // We don't use ranged for loop because iterators are getting invalided when
  // the callback runs.
  for (auto iter = destructors_.begin(); iter != destructors_.end();) {
    base::OnceClosure callback = std::move(*iter);
    if (!callback.is_null())
      std::move(callback).Run();
    ++iter;
  }
}

device::mojom::GeolocationControl*
AtomBrowserMainParts::GetGeolocationControl() {
  if (geolocation_control_)
    return geolocation_control_.get();

  auto request = mojo::MakeRequest(&geolocation_control_);
  if (!content::ServiceManagerConnection::GetForProcess())
    return geolocation_control_.get();

  service_manager::Connector* connector =
      content::ServiceManagerConnection::GetForProcess()->GetConnector();
  connector->BindInterface(device::mojom::kServiceName, std::move(request));
  return geolocation_control_.get();
}

}  // namespace atom
