chromium/chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ui/webui/nearby_share/nearby_share_dialog_ui.h"

#include <string>

#include "base/strings/string_split.h"
#include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
#include "chrome/browser/nearby_sharing/contacts/nearby_share_contact_manager.h"
#include "chrome/browser/nearby_sharing/file_attachment.h"
#include "chrome/browser/nearby_sharing/nearby_per_session_discovery_manager.h"
#include "chrome/browser/nearby_sharing/nearby_share_settings.h"
#include "chrome/browser/nearby_sharing/nearby_sharing_service.h"
#include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
#include "chrome/browser/nearby_sharing/nearby_sharing_service_impl.h"
#include "chrome/browser/nearby_sharing/text_attachment.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
#include "chrome/browser/ui/webui/metrics_handler.h"
#include "chrome/browser/ui/webui/nearby_share/shared_resources.h"
#include "chrome/browser/ui/webui/plural_string_handler.h"
#include "chrome/browser/ui/webui/sanitized_image_source.h"
#include "chrome/browser/ui/webui/webui_util.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/nearby_share_dialog_resources.h"
#include "chrome/grit/nearby_share_dialog_resources_map.h"
#include "chrome/grit/theme_resources.h"
#include "chromeos/components/sharesheet/constants.h"
#include "chromeos/constants/chromeos_features.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/url_util.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/webui/color_change_listener/color_change_handler.h"

namespace nearby_share {

// Keep in sync with //chrome/browser/resources/nearby_share/shared/types.js
enum class CloseReason {
  kUnknown = 0,
  kTransferStarted = 1,
  kTransferSucceeded = 2,
  kCancelled = 3,
  kRejected = 4,
  kMax = kRejected
};

bool NearbyShareDialogUIConfig::IsWebUIEnabled(
    content::BrowserContext* browser_context) {
  if (browser_context->IsOffTheRecord())
    return false;
  return NearbySharingServiceFactory::IsNearbyShareSupportedForBrowserContext(
      browser_context);
}

NearbyShareDialogUI::NearbyShareDialogUI(content::WebUI* web_ui)
    : ui::MojoWebUIController(web_ui, /*enable_chrome_send=*/true) {
  Profile* profile = Profile::FromWebUI(web_ui);
  DCHECK(NearbySharingServiceFactory::IsNearbyShareSupportedForBrowserContext(
      profile));

  nearby_service_ = NearbySharingServiceFactory::GetForBrowserContext(profile);

  content::WebUIDataSource* html_source =
      content::WebUIDataSource::CreateAndAdd(profile,
                                             chrome::kChromeUINearbyShareHost);

  content::URLDataSource::Add(profile,
                              std::make_unique<SanitizedImageSource>(profile));

  webui::SetupWebUIDataSource(html_source,
                              base::make_span(kNearbyShareDialogResources,
                                              kNearbyShareDialogResourcesSize),
                              IDR_NEARBY_SHARE_DIALOG_NEARBY_SHARE_DIALOG_HTML);

  // To use lottie, the worker-src CSP needs to be updated for the web ui that
  // is using it. Since as of now there are only a couple of webuis using
  // lottie animations, this update has to be performed manually. As the usage
  // increases, set this as the default so manual override is no longer
  // required.
  html_source->OverrideContentSecurityPolicy(
      network::mojom::CSPDirectiveName::WorkerSrc,
      "worker-src blob: chrome://resources 'self';");

  html_source->OverrideContentSecurityPolicy(
      network::mojom::CSPDirectiveName::TrustedTypes,
      "trusted-types static-types "
      // Required by lottie.
      "cros-lottie-worker-script-loader "
      "lottie-worker-script-loader webui-test-script "
      // Required by parse-html-subset.
      "parse-html-subset sanitize-inner-html "
      // Required by lit-html.
      "lit-html "
      // Required by polymer.
      "polymer-html-literal polymer-template-event-attribute-policy;");

  html_source->AddBoolean(
      "isOnePageOnboardingEnabled",
      base::FeatureList::IsEnabled(features::kNearbySharingOnePageOnboarding));
  RegisterNearbySharedStrings(html_source);
  html_source->UseStringsJs();

  // Register callback to handle "cancel-button-event" from nearby_*.html files.
  web_ui->RegisterMessageCallback(
      "close", base::BindRepeating(&NearbyShareDialogUI::HandleClose,
                                   base::Unretained(this)));

  auto plural_string_handler = std::make_unique<PluralStringHandler>();
  plural_string_handler->AddLocalizedString(
      "nearbyShareContactVisibilityNumUnreachable",
      IDS_NEARBY_CONTACT_VISIBILITY_NUM_UNREACHABLE_PH);
  web_ui->AddMessageHandler(std::move(plural_string_handler));
  // Add the metrics handler to write uma stats.
  web_ui->AddMessageHandler(std::make_unique<MetricsHandler>());

  const GURL& url = web_ui->GetWebContents()->GetVisibleURL();
  SetAttachmentFromQueryParameter(url);
}

NearbyShareDialogUI::~NearbyShareDialogUI() = default;

void NearbyShareDialogUI::SetAttachments(
    std::vector<std::unique_ptr<Attachment>> attachments) {
  attachments_ = std::move(attachments);
}

void NearbyShareDialogUI::SetWebView(views::WebView* web_view) {
  CHECK(web_view);
  web_view_ = web_view;
  web_view_->GetWebContents()->SetDelegate(this);
}

void NearbyShareDialogUI::BindInterface(
    mojo::PendingReceiver<mojom::DiscoveryManager> manager) {
  mojo::MakeSelfOwnedReceiver(
      std::make_unique<NearbyPerSessionDiscoveryManager>(
          nearby_service_, std::move(attachments_)),
      std::move(manager));
}

void NearbyShareDialogUI::BindInterface(
    mojo::PendingReceiver<mojom::NearbyShareSettings> receiver) {
  NearbySharingService* nearby_sharing_service =
      NearbySharingServiceFactory::GetForBrowserContext(
          Profile::FromWebUI(web_ui()));
  nearby_sharing_service->GetSettings()->Bind(std::move(receiver));
}

void NearbyShareDialogUI::BindInterface(
    mojo::PendingReceiver<nearby_share::mojom::ContactManager> receiver) {
  NearbySharingService* nearby_sharing_service =
      NearbySharingServiceFactory::GetForBrowserContext(
          Profile::FromWebUI(web_ui()));
  nearby_sharing_service->GetContactManager()->Bind(std::move(receiver));
}

void NearbyShareDialogUI::BindInterface(
    mojo::PendingReceiver<color_change_listener::mojom::PageHandler> receiver) {
  color_provider_handler_ = std::make_unique<ui::ColorChangeHandler>(
      web_ui()->GetWebContents(), std::move(receiver));
}

bool NearbyShareDialogUI::HandleKeyboardEvent(
    content::WebContents* source,
    const input::NativeWebKeyboardEvent& event) {
  if (!web_view_) {
    return false;
  }

  return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
      event, web_view_->GetFocusManager());
}

void NearbyShareDialogUI::WebContentsCreated(
    content::WebContents* source_contents,
    int opener_render_process_id,
    int opener_render_frame_id,
    const std::string& frame_name,
    const GURL& target_url,
    content::WebContents* new_contents) {
  chrome::ScopedTabbedBrowserDisplayer displayer(Profile::FromWebUI(web_ui()));
  NavigateParams nav_params(displayer.browser(), target_url,
                            ui::PageTransition::PAGE_TRANSITION_LINK);
  Navigate(&nav_params);
}

void NearbyShareDialogUI::HandleClose(const base::Value::List& args) {
  if (!sharesheet_controller_)
    return;

  CHECK_EQ(1u, args.size());
  CHECK_GE(args[0].GetInt(), 0);
  CHECK_LE(args[0].GetInt(), static_cast<int>(CloseReason::kMax));
  CloseReason reason = static_cast<CloseReason>(args[0].GetInt());

  sharesheet::SharesheetResult sharesheet_result;
  switch (reason) {
    case CloseReason::kTransferStarted:
    case CloseReason::kTransferSucceeded:
      sharesheet_result = sharesheet::SharesheetResult::kSuccess;
      break;
    case CloseReason::kUnknown:
    case CloseReason::kCancelled:
    case CloseReason::kRejected:
      sharesheet_result = sharesheet::SharesheetResult::kCancel;
      break;
  }

  sharesheet_controller_->CloseBubble(sharesheet_result);

  // We need to clear out the controller here to protect against calling
  // CloseBubble() more than once, which will cause a crash.
  sharesheet_controller_ = nullptr;
}

void NearbyShareDialogUI::SetAttachmentFromQueryParameter(const GURL& url) {
  std::vector<std::unique_ptr<Attachment>> attachments;
  std::string value;
  for (auto& text_type :
       std::vector<std::pair<std::string, TextAttachment::Type>>{
           {"address", TextAttachment::Type::kAddress},
           {"url", TextAttachment::Type::kUrl},
           {"phone", TextAttachment::Type::kPhoneNumber},
           {"text", TextAttachment::Type::kText}}) {
    if (net::GetValueForKeyInQuery(url, text_type.first, &value)) {
      attachments.push_back(std::make_unique<TextAttachment>(
          text_type.second, value, /*title=*/std::nullopt,
          /*mime_type=*/std::nullopt));
      SetAttachments(std::move(attachments));
      return;
    }
  }

  if (net::GetValueForKeyInQuery(url, "file", &value)) {
    for (std::string& path : base::SplitString(
             value, "|", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
      attachments.push_back(
          std::make_unique<FileAttachment>(base::FilePath(path)));
    }
    SetAttachments(std::move(attachments));
  }
}

WEB_UI_CONTROLLER_TYPE_IMPL(NearbyShareDialogUI)

}  // namespace nearby_share