chromium/ash/webui/shimless_rma/backend/external_app_dialog.cc

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

#include "ash/webui/shimless_rma/backend/external_app_dialog.h"

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

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "base/check_op.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "components/input/native_web_keyboard_event.h"
#include "components/permissions/permission_request_manager.h"
#include "content/public/browser/console_message.h"
#include "content/public/browser/file_select_listener.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/ui_base_types.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/controls/webview/web_dialog_view.h"
#include "ui/views/widget/widget.h"
#include "ui/web_dialogs/web_dialog_web_contents_delegate.h"

namespace content {
class NavigationHandle;
}  // namespace content

namespace ash::shimless_rma {

namespace {

ExternalAppDialog* g_instance = nullptr;
base::RepeatingCallback<void(const ExternalAppDialog::InitParams&)>
    g_mock_show_function;

constexpr double kRelativeScreenWidth = 0.9;
constexpr double kRelativeScreenHeight = 0.8;

class WebContentsHandler
    : public ui::WebDialogWebContentsDelegate::WebContentsHandler {
 public:
  WebContentsHandler();
  WebContentsHandler(const WebContentsHandler&) = delete;
  WebContentsHandler& operator=(const WebContentsHandler&) = delete;
  ~WebContentsHandler() override;

  // WebDialogWebContentsDelegate::WebContentsHandler overrides:
  content::WebContents* OpenURLFromTab(
      content::BrowserContext* context,
      content::WebContents* source,
      const content::OpenURLParams& params,
      base::OnceCallback<void(content::NavigationHandle&)>
          navigation_handle_callback) override;
  void AddNewContents(content::BrowserContext* context,
                      content::WebContents* source,
                      std::unique_ptr<content::WebContents> new_contents,
                      const GURL& target_url,
                      WindowOpenDisposition disposition,
                      const blink::mojom::WindowFeatures& window_features,
                      bool user_gesture) override;
  void RunFileChooser(content::RenderFrameHost* render_frame_host,
                      scoped_refptr<content::FileSelectListener> listener,
                      const blink::mojom::FileChooserParams& params) override;
};

WebContentsHandler::WebContentsHandler() = default;

WebContentsHandler::~WebContentsHandler() = default;

content::WebContents* WebContentsHandler::OpenURLFromTab(
    content::BrowserContext* context,
    content::WebContents* source,
    const content::OpenURLParams& params,
    base::OnceCallback<void(content::NavigationHandle&)>
        navigation_handle_callback) {
  return nullptr;
}

void WebContentsHandler::AddNewContents(
    content::BrowserContext* context,
    content::WebContents* source,
    std::unique_ptr<content::WebContents> new_contents,
    const GURL& target_url,
    WindowOpenDisposition disposition,
    const blink::mojom::WindowFeatures& window_features,
    bool user_gesture) {}

void WebContentsHandler::RunFileChooser(
    content::RenderFrameHost* render_frame_host,
    scoped_refptr<content::FileSelectListener> listener,
    const blink::mojom::FileChooserParams& params) {
  listener->FileSelectionCanceled();
}

std::string_view ConsoleMessageLevelToString(
    blink::mojom::ConsoleMessageLevel level) {
  switch (level) {
    case blink::mojom::ConsoleMessageLevel::kVerbose:
      return "VERBOSE";
    case blink::mojom::ConsoleMessageLevel::kInfo:
      return "INFO";
    case blink::mojom::ConsoleMessageLevel::kWarning:
      return "WARNING";
    case blink::mojom::ConsoleMessageLevel::kError:
      return "ERROR";
  }
}

}  // namespace

ExternalAppDialog::InitParams::InitParams() = default;

ExternalAppDialog::InitParams::~InitParams() = default;

// static
void ExternalAppDialog::Show(const InitParams& params) {
  if (g_instance) {
    LOG(ERROR) << "Can only show one ExternalAppDialog";
    return;
  }
  if (g_mock_show_function) {
    g_mock_show_function.Run(params);
    return;
  }
  new ExternalAppDialog(params);
}

// static
content::WebContents* ExternalAppDialog::GetWebContents() {
  return g_instance ? g_instance->web_dialog_view_->web_contents() : nullptr;
}

// static
void ExternalAppDialog::SetMockShowForTesting(
    base::RepeatingCallback<void(const InitParams& params)> callback) {
  g_mock_show_function = callback;
}

// static
void ExternalAppDialog::CloseForTesting() {
  if (g_instance) {
    g_instance->widget_->Close();
  }
}

ExternalAppDialog::ExternalAppDialog(const InitParams& params)
    : content::WebContentsObserver(nullptr),
      on_console_log_(params.on_console_log) {
  CHECK_EQ(g_instance, nullptr);
  g_instance = this;

  shimless_rma_delegate_ = params.shimless_rma_delegate;

  set_can_close(true);
  set_can_resize(false);
  set_center_dialog_title_text(true);
  set_close_dialog_on_escape(false);
  set_dialog_content_url(params.content_url);
  set_dialog_modal_type(ui::mojom::ModalType::kSystem);
  set_dialog_title(base::UTF8ToUTF16(params.app_name));

  views::Widget::InitParams widget_params{
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET};
  widget_params.z_order = ui::ZOrderLevel::kFloatingWindow;
  web_dialog_view_ = new views::WebDialogView(
      params.context, this, std::make_unique<WebContentsHandler>());
  widget_params.delegate = web_dialog_view_;
  widget_params.parent =
      ash::Shell::GetContainer(ash::Shell::GetPrimaryRootWindow(),
                               ash::kShellWindowId_LockSystemModalContainer);

  widget_ = new views::Widget;
  widget_->Init(std::move(widget_params));
  widget_->Show();
}

ExternalAppDialog::~ExternalAppDialog() {
  CHECK_EQ(g_instance, this);
  g_instance = nullptr;
}

void ExternalAppDialog::GetDialogSize(gfx::Size* size) const {
  gfx::Size screen_size =
      display::Screen::GetScreen()->GetPrimaryDisplay().size();
  *size = gfx::Size(kRelativeScreenWidth * screen_size.width(),
                    kRelativeScreenHeight * screen_size.height());
}

void ExternalAppDialog::OnLoadingStateChanged(content::WebContents* source) {
  if (has_web_content_setup_) {
    return;
  }

  permissions::PermissionRequestManager::CreateForWebContents(source);
  content::WebContentsObserver::Observe(source);
  has_web_content_setup_ = true;
}

void ExternalAppDialog::RequestMediaAccessPermission(
    content::WebContents* web_contents,
    const content::MediaStreamRequest& request,
    content::MediaResponseCallback callback) {
  if (!shimless_rma_delegate_) {
    LOG(WARNING) << "Invalid Shimless RMA Delegate";
    std::move(callback).Run(
        blink::mojom::StreamDevicesSet(),
        blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED,
        /*ui=*/nullptr);
    return;
  }
  shimless_rma_delegate_->ProcessMediaAccessRequest(
      web_contents, request, std::move(callback), /*extension=*/nullptr);
}

void ExternalAppDialog::OnDidAddMessageToConsole(
    content::RenderFrameHost* source_frame,
    blink::mojom::ConsoleMessageLevel log_level,
    const std::u16string& message,
    int32_t line_no,
    const std::u16string& source_id,
    const std::optional<std::u16string>& untrusted_stack_trace) {
  if (ash::features::IsShimlessRMA3pDiagnosticsDevModeEnabled()) {
    LOG(WARNING) << "[CONSOLE " << ConsoleMessageLevelToString(log_level)
                 << "] \"" << message << "\", source: " << source_id << " ("
                 << line_no << ")";
  }
  if (on_console_log_) {
    on_console_log_.Run(content::ConsoleMessageLevelToLogSeverity(log_level),
                        message, line_no, source_id);
  }
}

}  // namespace ash::shimless_rma