blob: 0a7cd6780b072ce8875c5f6e78cc3742796dca77 [file] [log] [blame]
// 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 "chrome/browser/ui/views/compose/chrome_compose_dialog_controller.h"
#include "base/functional/callback.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/views/bubble/webui_bubble_dialog_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "components/compose/core/browser/compose_client.h"
#include "components/compose/core/browser/compose_features.h"
#include "components/compose/core/browser/compose_metrics.h"
#include "components/compose/core/browser/config.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
namespace chrome {
std::unique_ptr<compose::ComposeDialogController> ShowComposeDialog(
content::WebContents& web_contents,
const gfx::RectF& element_bounds_in_screen,
compose::ComposeClient::FieldIdentifier field_ids) {
// The Compose dialog is not anchored to any particular View. Pass the
// BrowserView so that it still knows about the Browser window, which is
// needed to access the correct ColorProvider for theming.
views::View* anchor_view = BrowserView::GetBrowserViewForBrowser(
chrome::FindBrowserWithTab(&web_contents));
auto controller =
std::make_unique<ChromeComposeDialogController>(&web_contents, field_ids);
controller->ShowComposeDialog(anchor_view, element_bounds_in_screen);
return controller;
}
} // namespace chrome
ChromeComposeDialogController::~ChromeComposeDialogController() = default;
// TOOD(b/301371110): This function should accept an argument indicating whether
// it was called from the context menu. If so, open by popping in. Otherwise,
// open with an expanding animation.
void ChromeComposeDialogController::ShowComposeDialog(
views::View* anchor_view,
const gfx::RectF& element_bounds_in_screen) {
if (!web_contents_) {
compose::LogOpenComposeDialogResult(
compose::OpenComposeDialogResult::kNoWebContents);
return;
}
Profile* profile =
Profile::FromBrowserContext(web_contents_->GetBrowserContext());
auto bubble_wrapper =
std::make_unique<WebUIContentsWrapperT<ComposeUntrustedUI>>(
GURL(chrome::kChromeUIUntrustedComposeUrl), profile,
IDS_COMPOSE_DIALOG_TITLE);
// This WebUI needs to know the calling BrowserContents so that the compose
// request/result can be properly associated with the triggering form.
bubble_wrapper->GetWebUIController()->set_triggering_web_contents(
web_contents_.get());
// The element will not be visible if it is outside the Browser View bounds,
// so clamp the element bounds to be within them.
gfx::Rect clamped_element_bounds =
gfx::ToRoundedRect(element_bounds_in_screen);
clamped_element_bounds.Intersect(anchor_view->GetBoundsInScreen());
auto compose_dialog_view = std::make_unique<ComposeDialogView>(
anchor_view, std::move(bubble_wrapper), clamped_element_bounds,
views::BubbleBorder::Arrow::TOP_CENTER);
bubble_ = compose_dialog_view->GetWeakPtr();
views::BubbleDialogDelegateView::CreateBubble(std::move(compose_dialog_view));
if (bubble_) {
compose::LogOpenComposeDialogResult(
compose::OpenComposeDialogResult::kSuccess);
// This must be called after CreateBubble, as that resets the
// |adjust_if_offscreen| field to the platform-dependent default.
bubble_->set_adjust_if_offscreen(true);
if (base::FeatureList::IsEnabled(
compose::features::kEnableComposeSavedStateNotification)) {
if (bubble_->GetWidget()) {
widget_observation_.Observe(bubble_->GetWidget());
}
}
} else {
compose::LogOpenComposeDialogResult(
compose::OpenComposeDialogResult::kFailedCreatingComposeDialogView);
}
}
WebUIContentsWrapperT<ComposeUntrustedUI>*
ChromeComposeDialogController::GetBubbleWrapper() const {
if (bubble_) {
return bubble_->bubble_wrapper();
}
return nullptr;
}
void ChromeComposeDialogController::ShowUI(
base::OnceClosure focus_lost_callback) {
focus_lost_callback_ = std::move(focus_lost_callback);
if (bubble_) {
bubble_->ShowUI();
}
}
// TODO(b/300939629): Flesh out implementation and cover other closing paths.
void ChromeComposeDialogController::Close() {
// This will no-op if there is no observation.
widget_observation_.Reset();
focus_lost_callback_.Reset();
auto* wrapper = GetBubbleWrapper();
if (wrapper) {
wrapper->CloseUI();
}
}
bool ChromeComposeDialogController::IsDialogShowing() {
return bubble_ && !bubble_->GetWidget()->IsClosed();
}
void ChromeComposeDialogController::OnWidgetDestroying(views::Widget* widget) {
if (focus_lost_callback_) {
const compose::Config& config = compose::GetComposeConfig();
// TODO(b/328730979): Add slight delay so that focus lost callback can be
// called after all focus-related events have been processed.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ChromeComposeDialogController::OnAfterWidgetDestroyed,
weak_ptr_factory_.GetWeakPtr()),
config.focus_lost_delay);
}
// This will no-op if there is no observation.
widget_observation_.Reset();
}
const compose::ComposeClient::FieldIdentifier&
ChromeComposeDialogController::GetFieldIds() {
return field_ids_;
}
void ChromeComposeDialogController::OnAfterWidgetDestroyed() {
if (focus_lost_callback_) {
std::move(focus_lost_callback_).Run();
}
}
ChromeComposeDialogController::ChromeComposeDialogController(
content::WebContents* web_contents,
compose::ComposeClient::FieldIdentifier field_ids)
: web_contents_(web_contents->GetWeakPtr()), field_ids_(field_ids) {}