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

#include "chrome/browser/extensions/extension_view_host_factory.h"

#include <string>

#include "base/debug/dump_without_crashing.h"
#include "base/notimplemented.h"
#include "chrome/browser/extensions/browser_extension_window_controller.h"
#include "chrome/browser/extensions/extension_view_host.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/common/url_constants.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/keyboard_event_processing_result.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/common/mojom/view_type.mojom.h"

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/ui/browser_web_contents_delegate/browser_web_contents_delegate.h"  // nogncheck
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));

namespace extensions {

namespace {

#if BUILDFLAG(ENABLE_EXTENSIONS)

// Delegate for ExtensionViewHost attached to a specific browser window.
class ExtensionViewHostBrowserDelegate : public ExtensionViewHost::Delegate {
 public:
  explicit ExtensionViewHostBrowserDelegate(BrowserWindowInterface* browser)
      : browser_(browser) {
    DCHECK(browser_);
  }
  ExtensionViewHostBrowserDelegate(const ExtensionViewHostBrowserDelegate&) =
      delete;
  ExtensionViewHostBrowserDelegate& operator=(
      const ExtensionViewHostBrowserDelegate&) = delete;
  ~ExtensionViewHostBrowserDelegate() override = default;

  content::WebContents* OpenURL(
      const content::OpenURLParams& params,
      base::OnceCallback<void(content::NavigationHandle&)>
          navigation_handle_callback) override {
    return browser_->OpenURL(params, std::move(navigation_handle_callback));
  }

  content::KeyboardEventProcessingResult PreHandleKeyboardEvent(
      content::WebContents* source,
      const input::NativeWebKeyboardEvent& event) override {
    return BrowserWebContentsDelegate::From(browser_)->PreHandleKeyboardEvent(
        source, event);
  }

  std::unique_ptr<content::EyeDropper> OpenEyeDropper(
      content::RenderFrameHost* frame,
      content::EyeDropperListener* listener) override {
    return BrowserWebContentsDelegate::From(browser_)->OpenEyeDropper(frame,
                                                                      listener);
  }

  WindowController* GetExtensionWindowController() override {
    return BrowserExtensionWindowController::From(browser_);
  }

 private:
  raw_ptr<BrowserWindowInterface> browser_;
};

// Delegate for ExtensionViewHost attached to a specific tab.
class ExtensionViewHostTabDelegate : public ExtensionViewHost::Delegate {
 public:
  explicit ExtensionViewHostTabDelegate(content::WebContents* web_contents)
      : web_contents_(web_contents) {
    DCHECK(web_contents_);
  }
  ExtensionViewHostTabDelegate(const ExtensionViewHostTabDelegate&) = delete;
  ExtensionViewHostTabDelegate& operator=(const ExtensionViewHostTabDelegate&) =
      delete;
  ~ExtensionViewHostTabDelegate() override = default;

  content::WebContents* OpenURL(
      const content::OpenURLParams& params,
      base::OnceCallback<void(content::NavigationHandle&)>
          navigation_handle_callback) override {
    BrowserWindowInterface* browser = FindBrowser();
    if (browser == nullptr) {
      return nullptr;
    }
    return browser->OpenURL(params, std::move(navigation_handle_callback));
  }

  content::KeyboardEventProcessingResult PreHandleKeyboardEvent(
      content::WebContents* source,
      const input::NativeWebKeyboardEvent& event) override {
    BrowserWindowInterface* browser = FindBrowser();
    if (browser == nullptr) {
      return content::KeyboardEventProcessingResult::NOT_HANDLED;
    }
    return BrowserWebContentsDelegate::From(browser)->PreHandleKeyboardEvent(
        source, event);
  }

  std::unique_ptr<content::EyeDropper> OpenEyeDropper(
      content::RenderFrameHost* frame,
      content::EyeDropperListener* listener) override {
    BrowserWindowInterface* browser = FindBrowser();
    if (browser == nullptr) {
      return nullptr;
    }
    return BrowserWebContentsDelegate::From(browser)->OpenEyeDropper(frame,
                                                                     listener);
  }

  WindowController* GetExtensionWindowController() override {
    BrowserWindowInterface* browser = FindBrowser();
    if (browser == nullptr) {
      return nullptr;
    }
    return BrowserExtensionWindowController::From(browser);
  }

 private:
  BrowserWindowInterface* FindBrowser() const {
    auto* browser = GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
        web_contents_);
    return browser;
  }

  raw_ptr<content::WebContents> web_contents_;
};

#else  // BUILDFLAG(ENABLE_EXTENSIONS)

// Delegate for ExtensionViewHost on Android.
class ExtensionViewHostDelegateAndroid : public ExtensionViewHost::Delegate {
 public:
  ExtensionViewHostDelegateAndroid() = default;
  ExtensionViewHostDelegateAndroid(const ExtensionViewHostDelegateAndroid&) =
      delete;
  ExtensionViewHostDelegateAndroid& operator=(
      const ExtensionViewHostDelegateAndroid&) = delete;
  ~ExtensionViewHostDelegateAndroid() override = default;

  content::WebContents* OpenURL(
      const content::OpenURLParams& params,
      base::OnceCallback<void(content::NavigationHandle&)>
          navigation_handle_callback) override {
    // TODO(cbrug.com/430430820): Investigate and remove if truly unused.
    //  Return nullptr for now. Initial investigations (see crrev.com/c/7214213)
    //  suggest that OpenURL methods within ExtensionViewHost::Delegate
    //  implementations are never actually called. This unblocks Android
    //  extension work while further analysis is conducted.
    base::debug::DumpWithoutCrashing();
    return nullptr;
  }

  content::KeyboardEventProcessingResult PreHandleKeyboardEvent(
      content::WebContents* source,
      const input::NativeWebKeyboardEvent& event) override {
    // TODO(cbrug.com/385987224): Implement this method for Android.
    NOTIMPLEMENTED();
    return content::KeyboardEventProcessingResult::NOT_HANDLED;
  }

  std::unique_ptr<content::EyeDropper> OpenEyeDropper(
      content::RenderFrameHost* frame,
      content::EyeDropperListener* listener) override {
    // TODO(cbrug.com/385987224): Implement this method for Android.
    NOTIMPLEMENTED();
    return nullptr;
  }

  WindowController* GetExtensionWindowController() override {
    // TODO(cbrug.com/385987224): Implement this method for Android.
    NOTIMPLEMENTED();
    return nullptr;
  }
};

#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

// Creates a new ExtensionHost with its associated view, grouping it in the
// appropriate SiteInstance (and therefore process) based on the URL and
// profile.
std::unique_ptr<ExtensionViewHost> CreateViewHostForExtension(
    const Extension* extension,
    const GURL& url,
    Profile* profile,
    mojom::ViewType view_type,
    std::unique_ptr<ExtensionViewHost::Delegate> delegate) {
  DCHECK(profile);
  return std::make_unique<ExtensionViewHost>(extension, profile, url, view_type,
                                             std::move(delegate));
}

// Creates a view host for an extension in an incognito window. Returns NULL
// if the extension is not allowed to run in incognito.
std::unique_ptr<ExtensionViewHost> CreateViewHostForIncognito(
    const Extension* extension,
    const GURL& url,
    Profile* profile,
    mojom::ViewType view_type,
    std::unique_ptr<ExtensionViewHost::Delegate> delegate) {
  DCHECK(extension);
  DCHECK(profile->IsOffTheRecord());

  if (!IncognitoInfo::IsSplitMode(extension)) {
    // If it's not split-mode the host is associated with the original profile.
    Profile* original_profile = profile->GetOriginalProfile();
    return CreateViewHostForExtension(extension, url, original_profile,
                                      view_type, std::move(delegate));
  }

  // Create the host if the extension can run in incognito.
  if (util::IsIncognitoEnabled(extension->id(), profile)) {
    return CreateViewHostForExtension(extension, url, profile, view_type,
                                      std::move(delegate));
  }
  NOTREACHED() << "We shouldn't be trying to create an incognito extension "
                  "view unless it has been enabled for incognito.";
}

std::unique_ptr<ExtensionViewHost> CreateExtensionViewHost(
    const Extension& extension,
    const GURL& url,
    Profile* profile,
    extensions::mojom::ViewType view_type,
    std::unique_ptr<ExtensionViewHost::Delegate> delegate) {
  CHECK(profile);

  if (profile->IsOffTheRecord()) {
    return CreateViewHostForIncognito(&extension, url, profile, view_type,
                                      std::move(delegate));
  }

  return CreateViewHostForExtension(&extension, url, profile, view_type,
                                    std::move(delegate));
}

}  // namespace

// static
std::unique_ptr<ExtensionViewHost> ExtensionViewHostFactory::CreatePopupHost(
    const Extension& extension,
    const GURL& url,
    BrowserWindowInterface* browser) {
  DCHECK(browser);

#if BUILDFLAG(ENABLE_EXTENSIONS)
  auto delegate = std::make_unique<ExtensionViewHostBrowserDelegate>(browser);
#else   // BUILDFLAG(ENABLE_EXTENSIONS)
  auto delegate = std::make_unique<ExtensionViewHostDelegateAndroid>();
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

  return CreateExtensionViewHost(extension, url, browser->GetProfile(),
                                 mojom::ViewType::kExtensionPopup,
                                 std::move(delegate));
}

#if BUILDFLAG(ENABLE_EXTENSIONS)

// static
std::unique_ptr<ExtensionViewHost>
ExtensionViewHostFactory::CreateSidePanelHost(
    const Extension& extension,
    const GURL& url,
    BrowserWindowInterface* browser,
    tabs::TabInterface* tab_interface) {
  DCHECK(browser == nullptr ^ tab_interface == nullptr);

  Profile* profile =
      browser ? browser->GetProfile()
              : tab_interface->GetBrowserWindowInterface()->GetProfile();

  std::unique_ptr<ExtensionViewHost::Delegate> delegate =
      browser ? static_cast<std::unique_ptr<ExtensionViewHost::Delegate>>(
                    std::make_unique<ExtensionViewHostBrowserDelegate>(browser))
              : std::make_unique<ExtensionViewHostTabDelegate>(
                    tab_interface->GetContents());

  return CreateExtensionViewHost(extension, url, profile,
                                 mojom::ViewType::kExtensionSidePanel,
                                 std::move(delegate));
}

#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

}  // namespace extensions
