blob: f620403859cdf1538ffa9935f0fd646422901d52 [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/omnibox/omnibox_popup_presenter.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/observer_list_types.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/browser/ui/views/omnibox/rounded_omnibox_results_frame.h"
#include "chrome/browser/ui/views/theme_copying_widget.h"
#include "chrome/browser/ui/webui/omnibox_popup/omnibox_popup_ui.h"
#include "chrome/browser/ui/webui/searchbox/realbox_handler.h"
#include "chrome/common/webui_url_constants.h"
#include "components/omnibox/browser/omnibox_view.h"
#include "ui/base/metadata/metadata_impl_macros.h"
OmniboxPopupPresenter::OmniboxPopupPresenter(LocationBarView* location_bar_view,
OmniboxController* controller)
: views::WebView(location_bar_view->profile()),
location_bar_view_(location_bar_view),
widget_(nullptr),
requested_handler_(false) {
set_owned_by_client(OwnedByClientPassKey());
// Build URL with SessionID to ensure correct omnibox controller binding
// without relying on mutable state subject to timing and destruction issues.
// The webui's page handler (RealboxHandler) needs to know what omnibox
// controller to use, but the native pointer value can't safely be passed in,
// and hacks like getting last active browser or any other shared mutable
// state can result in subtle races and even use-after-free bugs. The
// window and its omnibox could destruct, or the browser changed, or even
// a new omnibox could be constructed to overwrite the shared value, within
// the time window of loading the URL in a separate process. Using a unique
// session ID avoids these problems and ensures that only the omnibox
// controller that owns this popup presenter will be selected.
const GURL url(
base::StringPrintf("%s?session_id=%d", chrome::kChromeUIOmniboxPopupURL,
location_bar_view->browser()->session_id().id()));
LoadInitialURL(url);
location_bar_view_->AddObserver(this);
}
OmniboxPopupPresenter::~OmniboxPopupPresenter() {
location_bar_view_->RemoveObserver(this);
ReleaseWidget(false);
}
void OmniboxPopupPresenter::Show() {
if (!widget_) {
widget_ = new ThemeCopyingWidget(location_bar_view_->GetWidget());
const views::Widget* parent_widget = location_bar_view_->GetWidget();
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_POPUP);
#if BUILDFLAG(IS_WIN)
// On Windows use the software compositor to ensure that we don't block
// the UI thread during command buffer creation. See http://crbug.com/125248
params.force_software_compositing = true;
#endif
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.parent = parent_widget->GetNativeView();
params.context = parent_widget->GetNativeWindow();
RoundedOmniboxResultsFrame::OnBeforeWidgetInit(&params, widget_);
widget_->Init(std::move(params));
widget_->ShowInactive();
widget_->SetContentsView(
std::make_unique<RoundedOmniboxResultsFrame>(this, location_bar_view_));
widget_->AddObserver(this);
}
RealboxHandler* handler = GetHandler();
if (handler && !handler->HasObserver(this)) {
handler->AddObserver(this);
}
}
void OmniboxPopupPresenter::Hide() {
// Only close if UI DevTools settings allow.
if (widget_ && widget_->ShouldHandleNativeWidgetActivationChanged(false)) {
ReleaseWidget(true);
}
}
bool OmniboxPopupPresenter::IsShown() const {
return !!widget_;
}
RealboxHandler* OmniboxPopupPresenter::GetHandler() {
const bool ready = IsHandlerReady();
if (!requested_handler_) {
// Only log on first access.
requested_handler_ = true;
base::UmaHistogramBoolean("Omnibox.WebUI.HandlerReadyOnFirstAccess", ready);
}
if (!ready) {
return nullptr;
}
OmniboxPopupUI* omnibox_popup_ui = static_cast<OmniboxPopupUI*>(
GetWebContents()->GetWebUI()->GetController());
return omnibox_popup_ui->handler();
}
void OmniboxPopupPresenter::OnWidgetDestroyed(views::Widget* widget) {
if (widget == widget_) {
widget_ = nullptr;
}
}
void OmniboxPopupPresenter::OnPopupElementSizeChanged(gfx::Size size) {
webui_element_size_ = size;
if (widget_) {
// The width is known, and is the basis for consistent web content rendering
// so width is specified exactly; then only height adjusts dynamically.
gfx::Rect widget_bounds = location_bar_view_->GetBoundsInScreen();
widget_bounds.Inset(
-RoundedOmniboxResultsFrame::GetLocationBarAlignmentInsets());
// TODO(crbug.com/40062053): Change max height according to max suggestion
// count and calculated row height, or use a more general maximum value.
constexpr int kMaxHeight = 600;
widget_bounds.set_height(widget_bounds.height() +
std::min(kMaxHeight, size.height()));
widget_bounds.Inset(-RoundedOmniboxResultsFrame::GetShadowInsets());
widget_->SetBounds(widget_bounds);
}
}
void OmniboxPopupPresenter::OnViewBoundsChanged(View* observed_view) {
CHECK(observed_view == location_bar_view_);
OnPopupElementSizeChanged(webui_element_size_);
}
bool OmniboxPopupPresenter::IsHandlerReady() {
OmniboxPopupUI* omnibox_popup_ui = static_cast<OmniboxPopupUI*>(
GetWebContents()->GetWebUI()->GetController());
return omnibox_popup_ui->handler() &&
omnibox_popup_ui->handler()->IsRemoteBound();
}
void OmniboxPopupPresenter::ReleaseWidget(bool close) {
RealboxHandler* handler = GetHandler();
if (handler && handler->HasObserver(this)) {
handler->RemoveObserver(this);
}
if (widget_) {
// Avoid possibility of dangling raw_ptr by nulling before cleanup.
views::Widget* widget = widget_;
widget_ = nullptr;
widget->RemoveObserver(this);
if (close) {
// Ensure we close `widget_` synchronously. This is necessary as the
// `widget_`'s contents view has dependencies on the hosting widget's
// BrowserView (see `SetContentsView()` above). Since the popup widget is
// owned by its NativeWidget there is a risk of dangling pointers if it is
// not destroyed synchronously with its parent.
// TODO(crbug.com/40232479): Once this is migrated to CLIENT_OWNS_WIDGET
// this will no longer be necessary.
widget->CloseNow();
}
}
CHECK(!views::WidgetObserver::IsInObserverList());
}
BEGIN_METADATA(OmniboxPopupPresenter)
END_METADATA