[dPWA] Make the WebAppInstallDialogCoordinator be a WidgetObserver
Instead of using a raw_ptr to a bubble dialog delegate and then using
a WeakPtr to itself everywhere inside the view, the install dialog
coordinator for web apps is now a WidgetObserver. This ensures:
1. Simple lifetimes, since the view inside the coordinator is
tracked only as long as the WidgetObserver is observing the main
dialog, which is more robust than the previous implementation of
a criss-cross raw_ptr, weak_ptr interface.
2. Practices separation of concerns, and brings the implementation
closer to a MVC model, where the behavior, view and its observation
happens independent of each other, with no overlapping logic.
As a side note, this also fixes the measurement of the
WebApp.InstallConfirmation.CloseReason by moving it to a central
location to be used by both the old and new install dialogs.
Bug: b/326418546
Change-Id: I7672d82d1683aa45ce9e51a37d3fef2e1d0c0a22
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5320614
Reviewed-by: Daniel Murphy <dmurph@chromium.org>
Commit-Queue: Dibyajyoti Pal <dibyapal@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1264651}
diff --git a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
index 8713de6c..965cc466 100644
--- a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
+++ b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
@@ -18,7 +18,6 @@
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
#include "chrome/browser/ui/views/web_apps/web_app_info_image_source.h"
-#include "chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h"
#include "chrome/browser/ui/views/web_apps/web_app_views_utils.h"
#include "chrome/browser/ui/web_applications/web_app_dialogs.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
@@ -93,18 +92,15 @@
web_app::AppInstallationAcceptanceCallback callback,
web_app::PwaInProductHelpState iph_state,
PrefService* prefs,
- feature_engagement::Tracker* tracker,
- base::WeakPtr<web_app::WebAppInstallDialogCoordinator> dialog_coordinator)
+ feature_engagement::Tracker* tracker)
: LocationBarBubbleDelegateView(anchor_view, web_contents.get()),
web_contents_(web_contents),
- highlight_icon_button_(highlight_icon_button),
web_app_info_(std::move(web_app_info)),
install_tracker_(std::move(install_tracker)),
callback_(std::move(callback)),
iph_state_(iph_state),
prefs_(prefs),
- tracker_(tracker),
- dialog_coordinator_(dialog_coordinator) {
+ tracker_(tracker) {
SetCloseOnMainFrameOriginNavigation(true);
DCHECK(web_app_info_);
DCHECK(prefs_);
@@ -165,7 +161,7 @@
SetDefaultButton(ui::DIALOG_BUTTON_CANCEL);
- SetHighlightedButton(highlight_icon_button_);
+ SetHighlightedButton(highlight_icon_button);
}
PWAConfirmationBubbleView::~PWAConfirmationBubbleView() = default;
@@ -180,8 +176,6 @@
bool PWAConfirmationBubbleView::OnCloseRequested(
views::Widget::ClosedReason close_reason) {
- base::UmaHistogramEnumeration("WebApp.InstallConfirmation.CloseReason",
- close_reason);
webapps::MlInstallUserResponse response;
switch (close_reason) {
case views::Widget::ClosedReason::kAcceptButtonClicked:
@@ -210,16 +204,6 @@
}
void PWAConfirmationBubbleView::WindowClosing() {
- // Stop tracking the bubble view so that the PageActionIconView update can
- // read the state of the dialog from the BrowserUserData when it gets updated.
- if (dialog_coordinator_ && dialog_coordinator_->IsShowing()) {
- dialog_coordinator_->StopTracking();
- }
-
- if (highlight_icon_button_) {
- highlight_icon_button_->Update();
- }
-
// If |web_app_info_| is populated, then the bubble was not accepted.
if (web_app_info_) {
base::RecordAction(base::UserMetricsAction("WebAppInstallCancelled"));
diff --git a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h
index 5379438..34444e6 100644
--- a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h
+++ b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h
@@ -9,7 +9,6 @@
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h"
-#include "chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h"
#include "chrome/browser/ui/web_applications/web_app_dialogs.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "components/prefs/pref_service.h"
@@ -51,9 +50,7 @@
web_app::AppInstallationAcceptanceCallback callback,
web_app::PwaInProductHelpState iph_state,
PrefService* prefs,
- feature_engagement::Tracker* tracker,
- base::WeakPtr<web_app::WebAppInstallDialogCoordinator>
- dialog_coordinator);
+ feature_engagement::Tracker* tracker);
METADATA_HEADER(PWAConfirmationBubbleView, views::BubbleDialogDelegateView)
public:
PWAConfirmationBubbleView(const PWAConfirmationBubbleView&) = delete;
@@ -80,7 +77,6 @@
private:
base::WeakPtr<content::WebContents> web_contents_;
- raw_ptr<PageActionIconView> highlight_icon_button_ = nullptr;
std::unique_ptr<web_app::WebAppInstallInfo> web_app_info_;
std::unique_ptr<webapps::MlInstallOperationTracker> install_tracker_;
web_app::AppInstallationAcceptanceCallback callback_;
@@ -91,7 +87,6 @@
web_app::PwaInProductHelpState iph_state_;
raw_ptr<PrefService> prefs_;
raw_ptr<feature_engagement::Tracker> tracker_;
- base::WeakPtr<web_app::WebAppInstallDialogCoordinator> dialog_coordinator_;
};
#endif // CHROME_BROWSER_UI_VIEWS_WEB_APPS_PWA_CONFIRMATION_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/web_apps/simple_install_dialog_bubble_view_browsertest.cc b/chrome/browser/ui/views/web_apps/simple_install_dialog_bubble_view_browsertest.cc
index dd2242f..8c6f123 100644
--- a/chrome/browser/ui/views/web_apps/simple_install_dialog_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/simple_install_dialog_bubble_view_browsertest.cc
@@ -31,9 +31,10 @@
#include "components/webapps/browser/installable/ml_installability_promoter.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/any_widget_observer.h"
-#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/widget/widget.h"
namespace web_app {
namespace {
@@ -117,7 +118,6 @@
GetInstallTracker(browser());
base::RunLoop loop;
- // Show the PWA install dialog.
ShowPWAInstallBubble(
browser()->tab_strip_model()->GetActiveWebContents(), std::move(app_info),
std::move(install_tracker),
@@ -131,13 +131,38 @@
GetBubbleView(browser())->CancelDialog();
loop.Run();
- // TOOD(b/326418546): Figure out a way to support widget close reason in new
- // install dialog.
- if (!UniversalInstallEnabled()) {
histograms.ExpectUniqueSample(
"WebApp.InstallConfirmation.CloseReason",
views::Widget::ClosedReason::kCancelButtonClicked, 1);
- }
+}
+
+IN_PROC_BROWSER_TEST_P(SimpleInstallDialogBubbleViewBrowserTest,
+ AcceptDialogReportsMetrics) {
+ auto app_info = GetAppInfo();
+
+ std::unique_ptr<webapps::MlInstallOperationTracker> install_tracker =
+ GetInstallTracker(browser());
+
+ base::RunLoop loop;
+ ShowPWAInstallBubble(
+ browser()->tab_strip_model()->GetActiveWebContents(), std::move(app_info),
+ std::move(install_tracker),
+ base::BindLambdaForTesting(
+ [&](bool accepted,
+ std::unique_ptr<WebAppInstallInfo> app_info_callback) {
+ loop.Quit();
+ }));
+
+ base::HistogramTester histogram_tester;
+ views::test::WidgetDestroyedWaiter destroyed_waiter(
+ GetBubbleView(browser())->GetWidget());
+ GetBubbleView(browser())->AcceptDialog();
+ loop.Run();
+ destroyed_waiter.Wait();
+
+ histogram_tester.ExpectUniqueSample(
+ "WebApp.InstallConfirmation.CloseReason",
+ views::Widget::ClosedReason::kAcceptButtonClicked, 1);
}
IN_PROC_BROWSER_TEST_P(SimpleInstallDialogBubbleViewBrowserTest,
@@ -146,7 +171,6 @@
GURL start_url = app_info->start_url;
base::RunLoop loop;
- // Show the PWA install dialog.
std::unique_ptr<webapps::MlInstallOperationTracker> install_tracker =
GetInstallTracker(browser());
content::WebContents* web_contents =
@@ -256,12 +280,8 @@
ASSERT_TRUE(dialog_accepted_);
ASSERT_FALSE(dialog_accepted_.value());
- // TOOD(b/326418546): Figure out a way to support widget close reason in new
- // install dialog.
- if (!UniversalInstallEnabled()) {
histograms.ExpectUniqueSample("WebApp.InstallConfirmation.CloseReason",
views::Widget::ClosedReason::kUnspecified, 1);
- }
}
INSTANTIATE_TEST_SUITE_P(All,
diff --git a/chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.cc b/chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.cc
index 54efedc..ad6e9ca0 100644
--- a/chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.cc
@@ -5,18 +5,24 @@
#include "chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h"
#include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_functions.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_user_data.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
+#include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
namespace web_app {
WebAppInstallDialogCoordinator::~WebAppInstallDialogCoordinator() {
- dialog_delegate_ = nullptr;
+ if (IsShowing()) {
+ StopTracking();
+ }
}
bool WebAppInstallDialogCoordinator::IsShowing() {
- return dialog_delegate_ != nullptr;
+ return widget_observation_.IsObserving();
}
views::BubbleDialogDelegate* WebAppInstallDialogCoordinator::GetBubbleView() {
@@ -28,17 +34,44 @@
CHECK(!IsShowing()) << "Cannot track a new install dialog if an existing "
"one is already open";
dialog_delegate_ = bubble_view;
+ auto* widget = bubble_view->GetWidget();
+ widget_observation_.Observe(widget);
+}
+
+void WebAppInstallDialogCoordinator::OnWidgetDestroying(views::Widget* widget) {
+ CHECK_EQ(widget, dialog_delegate_->GetWidget());
+ base::UmaHistogramEnumeration("WebApp.InstallConfirmation.CloseReason",
+ widget->closed_reason());
+ StopTracking();
}
void WebAppInstallDialogCoordinator::StopTracking() {
CHECK(IsShowing()) << "Cannot stop tracking install dialog when it was not "
"being tracked previously";
+ widget_observation_.Reset();
dialog_delegate_ = nullptr;
+ MaybeUpdatePwaAnchorViewIfNeeded();
}
-base::WeakPtr<WebAppInstallDialogCoordinator>
-WebAppInstallDialogCoordinator::AsWeakPtr() {
- return weak_ptr_factory_.GetWeakPtr();
+void WebAppInstallDialogCoordinator::MaybeUpdatePwaAnchorViewIfNeeded() {
+ Browser* browser = &GetBrowser();
+ if (!browser) {
+ return;
+ }
+ BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
+
+ if (browser_view && browser_view->toolbar_button_provider()) {
+ PageActionIconView* install_icon =
+ browser_view->toolbar_button_provider()->GetPageActionIconView(
+ PageActionIconType::kPwaInstall);
+ if (install_icon) {
+ // This ensures that quick navigations in between an installable and
+ // non-installable site hides the anchor view. See the test
+ // |IconVisibilityAfterTabSwitchingWhenPWAConfirmationBubbleViewShowing|
+ // for more information on why this was needed.
+ install_icon->Update();
+ }
+ }
}
WebAppInstallDialogCoordinator::WebAppInstallDialogCoordinator(Browser* browser)
diff --git a/chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h b/chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h
index 3776892..bf843c0 100644
--- a/chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h
+++ b/chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h
@@ -5,37 +5,37 @@
#ifndef CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_INSTALL_DIALOG_COORDINATOR_H_
#define CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_INSTALL_DIALOG_COORDINATOR_H_
-#include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/browser_user_data.h"
-#include "chrome/browser/ui/views/web_apps/web_app_icon_name_and_origin_view.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
+#include "ui/views/widget/widget_observer.h"
class Browser;
namespace web_app {
-// TODO(b/326418546): Tracking using a weak_ptr and a raw_ptr to the bubble
-// dialog delegate may lead to complicated ownership issues. Move to a
-// WidgetObserver class, or store a weakptr to a BubbleDialogDelegate instead
-// (needs views/ code modification and some scoping).
class WebAppInstallDialogCoordinator
- : public BrowserUserData<WebAppInstallDialogCoordinator> {
+ : public BrowserUserData<WebAppInstallDialogCoordinator>,
+ public views::WidgetObserver {
public:
~WebAppInstallDialogCoordinator() override;
bool IsShowing();
views::BubbleDialogDelegate* GetBubbleView();
void StartTracking(views::BubbleDialogDelegate* view);
- void StopTracking();
- base::WeakPtr<WebAppInstallDialogCoordinator> AsWeakPtr();
+
+ // WidgetObserver overrides:
+ void OnWidgetDestroying(views::Widget* widget) override;
private:
explicit WebAppInstallDialogCoordinator(Browser* browser);
friend BrowserUserData;
- raw_ptr<views::BubbleDialogDelegate> dialog_delegate_ = nullptr;
+ void StopTracking();
+ void MaybeUpdatePwaAnchorViewIfNeeded();
- base::WeakPtrFactory<WebAppInstallDialogCoordinator> weak_ptr_factory_{this};
+ raw_ptr<views::BubbleDialogDelegate> dialog_delegate_ = nullptr;
+ base::ScopedObservation<views::Widget, views::WidgetObserver>
+ widget_observation_{this};
BROWSER_USER_DATA_KEY_DECL();
};
diff --git a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc
index 983f6fb..d0f72f2 100644
--- a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc
@@ -13,7 +13,6 @@
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
-#include "chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h"
#include "chrome/browser/ui/web_applications/web_app_dialogs.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
@@ -56,9 +55,7 @@
PwaInProductHelpState iph_state,
PrefService* prefs,
feature_engagement::Tracker* tracker,
- InstallDialogType dialog_type,
- std::optional<base::WeakPtr<web_app::WebAppInstallDialogCoordinator>>
- dialog_coordinator)
+ InstallDialogType dialog_type)
: content::WebContentsObserver(web_contents),
web_contents_(web_contents),
install_info_(std::move(web_app_info)),
@@ -67,8 +64,7 @@
iph_state_(std::move(iph_state)),
prefs_(prefs),
tracker_(tracker),
- dialog_type_(dialog_type),
- dialog_coordinator_(dialog_coordinator) {
+ dialog_type_(dialog_type) {
CHECK(install_info_);
CHECK(install_info_->manifest_id.is_valid());
CHECK(install_tracker_);
@@ -76,11 +72,6 @@
}
WebAppInstallDialogDelegate::~WebAppInstallDialogDelegate() {
- if (dialog_coordinator_.has_value() &&
- dialog_coordinator_.value()->IsShowing()) {
- dialog_coordinator_.value()->StopTracking();
- }
-
// TODO(crbug.com/1327363): move this to dialog->SetHighlightedButton.
Browser* browser = chrome::FindBrowserWithTab(web_contents_);
if (!browser) {
@@ -95,9 +86,7 @@
PageActionIconType::kPwaInstall);
if (install_icon) {
// Dehighlight the install icon when this dialog is closed.
- browser_view->toolbar_button_provider()
- ->GetPageActionIconView(PageActionIconType::kPwaInstall)
- ->SetHighlighted(false);
+ install_icon->SetHighlighted(false);
}
}
}
@@ -179,11 +168,6 @@
}
void WebAppInstallDialogDelegate::MeasureIphOnDialogClose() {
- if (dialog_coordinator_.has_value() &&
- dialog_coordinator_.value()->IsShowing()) {
- dialog_coordinator_.value()->StopTracking();
- }
-
if (callback_.is_null()) {
return;
}
diff --git a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h
index cc4430e4..7fcd0f42 100644
--- a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h
+++ b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h
@@ -9,12 +9,12 @@
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/views/web_apps/web_app_install_dialog_coordinator.h"
#include "chrome/browser/ui/web_applications/web_app_dialogs.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/base/models/dialog_model.h"
#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_observer.h"
class PrefService;
@@ -45,8 +45,6 @@
: public ui::DialogModelDelegate,
public content::WebContentsObserver {
public:
- // dialog_coordinator is std::optional since the detailed install dialog uses
- // this delegate but is not tracked by the PWA action view.
WebAppInstallDialogDelegate(
content::WebContents* web_contents,
std::unique_ptr<WebAppInstallInfo> install_info,
@@ -55,9 +53,7 @@
PwaInProductHelpState iph_state,
PrefService* prefs,
feature_engagement::Tracker* tracker,
- InstallDialogType dialog_type,
- std::optional<base::WeakPtr<web_app::WebAppInstallDialogCoordinator>>
- dialog_coordinator = std::nullopt);
+ InstallDialogType dialog_type);
~WebAppInstallDialogDelegate() override;
@@ -87,8 +83,6 @@
raw_ptr<PrefService> prefs_;
raw_ptr<feature_engagement::Tracker> tracker_;
InstallDialogType dialog_type_;
- std::optional<base::WeakPtr<web_app::WebAppInstallDialogCoordinator>>
- dialog_coordinator_;
base::WeakPtrFactory<WebAppInstallDialogDelegate> weak_ptr_factory_{
this};
diff --git a/chrome/browser/ui/views/web_apps/web_app_simple_install_dialog.cc b/chrome/browser/ui/views/web_apps/web_app_simple_install_dialog.cc
index c600cfa..f0d592f 100644
--- a/chrome/browser/ui/views/web_apps/web_app_simple_install_dialog.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_simple_install_dialog.cc
@@ -110,7 +110,7 @@
auto delegate = std::make_unique<web_app::WebAppInstallDialogDelegate>(
web_contents, std::move(web_app_info), std::move(install_tracker),
std::move(callback), std::move(iph_state), prefs, tracker,
- InstallDialogType::kSimple, dialog_coordinator->AsWeakPtr());
+ InstallDialogType::kSimple);
auto delegate_weak_ptr = delegate->AsWeakPtr();
auto dialog_model =
@@ -150,7 +150,7 @@
auto* dialog_view = new PWAConfirmationBubbleView(
anchor_view, web_contents->GetWeakPtr(), icon, std::move(web_app_info),
std::move(install_tracker), std::move(callback), iph_state, prefs,
- tracker, dialog_coordinator->AsWeakPtr());
+ tracker);
if (g_dont_close_on_deactivate) {
dialog_view->set_close_on_deactivate(false);
}
@@ -158,7 +158,6 @@
views::BubbleDialogDelegateView::CreateBubble(dialog_view)->Show();
dialog_delegate = dialog_view->AsBubbleDialogDelegate();
dialog_coordinator->StartTracking(dialog_delegate);
-
if (icon) {
icon->Update();
DCHECK(icon->GetVisible());