blob: 875aecf6a900872b47ec858536b7d668a5778e2a [file] [log] [blame]
// 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.
#ifndef CHROME_BROWSER_UI_VIEWS_BUBBLE_WEBUI_BUBBLE_MANAGER_H_
#define CHROME_BROWSER_UI_VIEWS_BUBBLE_WEBUI_BUBBLE_MANAGER_H_
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/bubble/webui_bubble_dialog_view.h"
#include "chrome/browser/ui/views/bubble/webui_bubble_manager_observer.h"
#include "chrome/browser/ui/views/close_bubble_on_tab_activation_helper.h"
#include "chrome/browser/ui/webui/top_chrome/webui_contents_warmup_level.h"
#include "chrome/browser/ui/webui/top_chrome/webui_contents_wrapper_service.h"
#include "chrome/browser/ui/webui/top_chrome/webui_contents_wrapper_service_factory.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
class GURL;
class Profile;
class WebUIBubbleDialogView;
// WebUIBubbleManager handles the creation / destruction of the WebUI bubble.
// This is needed to deal with the asynchronous presentation of WebUI.
class WebUIBubbleManager : public views::WidgetObserver {
public:
template <typename Controller>
static std::unique_ptr<WebUIBubbleManager> Create(
views::View* anchor_view,
Profile* profile,
const GURL& webui_url,
int task_manager_string_id,
bool force_load_on_create = false);
WebUIBubbleManager(const WebUIBubbleManager&) = delete;
const WebUIBubbleManager& operator=(const WebUIBubbleManager&) = delete;
~WebUIBubbleManager() override;
// Show the bubble. The widget is created synchronously. If the contents is
// preloaded, the bubble will show immediately. Otherwise, the widget is
// hidden and will be made visible at a later time when the page invokes
// `MojoBubbleWebUIController::Embedder::ShowUI()`.
bool ShowBubble(
const std::optional<gfx::Rect>& anchor = std::nullopt,
views::BubbleBorder::Arrow arrow = views::BubbleBorder::TOP_RIGHT,
ui::ElementIdentifier identifier = ui::ElementIdentifier());
void CloseBubble();
views::Widget* GetBubbleWidget() const;
void AddObserver(WebUIBubbleManagerObserver* observer);
void RemoveObserver(WebUIBubbleManagerObserver* observer);
// Register a callback that will be invoked when the bubble widget is
// initialized. This is used for metrics collections.
void set_widget_initialization_callback(base::OnceClosure callback) {
widget_initialization_callback_ = std::move(callback);
}
bool bubble_using_cached_web_contents() const {
return bubble_using_cached_web_contents_;
}
WebUIContentsWarmupLevel contents_warmup_level() const {
CHECK(contents_warmup_level_.has_value());
return *contents_warmup_level_;
}
// views::WidgetObserver:
void OnWidgetDestroying(views::Widget* widget) override;
base::WeakPtr<WebUIBubbleDialogView> bubble_view_for_testing() {
return bubble_view_;
}
void ResetContentsWrapperForTesting();
void DisableCloseBubbleHelperForTesting();
protected:
WebUIBubbleManager();
// Creates the persistent renderer process if the feature is enabled.
virtual void MaybeInitPersistentRenderer() = 0;
virtual base::WeakPtr<WebUIBubbleDialogView> CreateWebUIBubbleDialog(
const std::optional<gfx::Rect>& anchor,
views::BubbleBorder::Arrow arrow) = 0;
// Gets the WebUIContentsWrapper. This is available after calling
// ShowBubble().
virtual WebUIContentsWrapper* GetContentsWrapper() = 0;
WebUIContentsWrapper* cached_contents_wrapper() {
return cached_contents_wrapper_.get();
}
void set_cached_contents_wrapper(
std::unique_ptr<WebUIContentsWrapper> cached_contents_wrapper) {
cached_contents_wrapper_ = std::move(cached_contents_wrapper);
}
void set_bubble_using_cached_web_contents(bool is_cached) {
bubble_using_cached_web_contents_ = is_cached;
}
// A callback that will be invoked when the bubble widget is initialized.
base::OnceClosure widget_initialization_callback_;
base::ObserverList<WebUIBubbleManagerObserver> observers_;
private:
void ResetContentsWrapper();
base::WeakPtr<WebUIBubbleDialogView> bubble_view_;
// Stores a cached WebUIContentsWrapper for reuse in the WebUIBubbleDialog.
std::unique_ptr<WebUIContentsWrapper> cached_contents_wrapper_;
// Tracks whether the current bubble was created by reusing a preloaded web
// contents.
bool bubble_using_cached_web_contents_ = false;
// The readiness of the browser when it is about to show this
// bubble. See WebUIContentsWarmupLevel.
std::optional<WebUIContentsWarmupLevel> contents_warmup_level_;
// A timer controlling how long the |cached_web_view_| is cached for.
std::unique_ptr<base::RetainingOneShotTimer> cache_timer_;
base::ScopedObservation<views::Widget, views::WidgetObserver>
bubble_widget_observation_{this};
// This is necessary to prevent a bug closing the active tab in the bubble.
// See https://crbug.com/1139028.
std::unique_ptr<CloseBubbleOnTabActivationHelper> close_bubble_helper_;
// Controls whether `close_bubble_helper_` is set when ShowBubble() is called.
bool disable_close_bubble_helper_ = false;
};
template <typename T>
class WebUIBubbleManagerImpl : public WebUIBubbleManager {
public:
WebUIBubbleManagerImpl(views::View* anchor_view,
Profile* profile,
const GURL& webui_url,
int task_manager_string_id,
bool force_load_on_create)
: anchor_view_(anchor_view),
profile_(profile),
webui_url_(webui_url),
task_manager_string_id_(task_manager_string_id),
force_load_on_create_(force_load_on_create) {}
~WebUIBubbleManagerImpl() override = default;
private:
// WebUIBubbleManager:
void MaybeInitPersistentRenderer() override;
base::WeakPtr<WebUIBubbleDialogView> CreateWebUIBubbleDialog(
const std::optional<gfx::Rect>& anchor,
views::BubbleBorder::Arrow arrow) override;
WebUIContentsWrapper* GetContentsWrapper() override;
const raw_ptr<views::View> anchor_view_;
const raw_ptr<Profile, DanglingUntriaged> profile_;
const GURL webui_url_;
const int task_manager_string_id_;
// Forces the WebUI through the page load lifecycle when a bubble is created,
// if necessary. If set page load is forced regardless of the caching status
// of the contents.
const bool force_load_on_create_;
};
template <typename Controller>
std::unique_ptr<WebUIBubbleManager> WebUIBubbleManager::Create(
views::View* anchor_view,
Profile* profile,
const GURL& webui_url,
int task_manager_string_id,
bool force_load_on_create) {
return std::make_unique<WebUIBubbleManagerImpl<Controller>>(
anchor_view, profile, webui_url, task_manager_string_id,
force_load_on_create);
}
template <typename T>
void WebUIBubbleManagerImpl<T>::MaybeInitPersistentRenderer() {
if (base::FeatureList::IsEnabled(
features::kWebUIBubblePerProfilePersistence)) {
auto* service =
WebUIContentsWrapperServiceFactory::GetForProfile(profile_, true);
if (service && !service->GetWebUIContentsWrapperFromURL(webui_url_)) {
service->template InitWebUIContentsWrapper<T>(webui_url_,
task_manager_string_id_);
}
}
}
template <typename T>
base::WeakPtr<WebUIBubbleDialogView>
WebUIBubbleManagerImpl<T>::CreateWebUIBubbleDialog(
const std::optional<gfx::Rect>& anchor,
views::BubbleBorder::Arrow arrow) {
WebUIContentsWrapper* contents_wrapper = nullptr;
// Only use per profile peristence if the flag is set and if a
// WebUIContentsWrapperService exists for the current profile. The service
// may not exist for off the record profiles.
auto* service =
WebUIContentsWrapperServiceFactory::GetForProfile(profile_, true);
if (service && base::FeatureList::IsEnabled(
features::kWebUIBubblePerProfilePersistence)) {
set_bubble_using_cached_web_contents(
!!service->GetWebUIContentsWrapperFromURL(webui_url_));
// If using per-profile WebContents persistence get the associated
// WebUIContentsWrapper from the WebUIContentsWrapperService.
MaybeInitPersistentRenderer();
contents_wrapper = service->GetWebUIContentsWrapperFromURL(webui_url_);
DCHECK(contents_wrapper);
// If there is a host currently associated to this contents wrapper ensure
// the host has closed and the association has been removed.
if (contents_wrapper->GetHost()) {
contents_wrapper->CloseUI();
}
DCHECK(!contents_wrapper->GetHost());
// If the wrapped WebContents has crashed ensure we reload it here before
// passing it over to the dialog host.
if (contents_wrapper->web_contents()->IsCrashed()) {
contents_wrapper->ReloadWebContents();
}
} else {
set_bubble_using_cached_web_contents(!!cached_contents_wrapper());
if (!cached_contents_wrapper()) {
set_cached_contents_wrapper(std::make_unique<WebUIContentsWrapperT<T>>(
webui_url_, profile_, task_manager_string_id_));
}
contents_wrapper = cached_contents_wrapper();
}
// If the contents has already navigated to the current WebUI page force a
// reload. This will force the contents back through the page load lifecycle.
if (force_load_on_create_ &&
!contents_wrapper->web_contents()
->HasUncommittedNavigationInPrimaryMainFrame()) {
contents_wrapper->ReloadWebContents();
}
auto bubble_view = std::make_unique<WebUIBubbleDialogView>(
anchor_view_, contents_wrapper->GetWeakPtr(), anchor, arrow);
if (!widget_initialization_callback_.is_null()) {
bubble_view->RegisterWidgetInitializedCallback(
std::move(widget_initialization_callback_));
}
auto weak_ptr = bubble_view->GetWeakPtr();
views::BubbleDialogDelegateView::CreateBubble(std::move(bubble_view));
return weak_ptr;
}
template <typename T>
WebUIContentsWrapper* WebUIBubbleManagerImpl<T>::GetContentsWrapper() {
if (cached_contents_wrapper()) {
return cached_contents_wrapper();
}
if (!base::FeatureList::IsEnabled(
features::kWebUIBubblePerProfilePersistence)) {
return nullptr;
}
auto* service =
WebUIContentsWrapperServiceFactory::GetForProfile(profile_, true);
if (!service) {
return nullptr;
}
return service->GetWebUIContentsWrapperFromURL(webui_url_);
}
#endif // CHROME_BROWSER_UI_VIEWS_BUBBLE_WEBUI_BUBBLE_MANAGER_H_