blob: 5c20e96b389bde95771179644ca8acfdf09fcf3b [file] [log] [blame]
// Copyright 2014 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/session_crashed_bubble_view.h"
#include <stddef.h>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/metrics/metrics_reporting_state.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/exit_type_service.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/bubble_anchor_util.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/frame/app_menu_button.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/toolbar/toolbar_view.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/installer/util/google_update_settings.h"
#include "components/strings/grit/components_branded_strings.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/buildflags.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/window_open_disposition.h"
#include "ui/base/window_open_disposition_utils.h"
#include "ui/views/bubble/bubble_dialog_model_host.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/label_button_border.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/separator.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
namespace {
views::BubbleDialogDelegate* g_instance_for_test = nullptr;
bool DoesSupportConsentCheck() {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return true;
#else
return false;
#endif
}
void OpenUmaLink(Browser* browser, const ui::Event& event) {
browser->OpenURL(
content::OpenURLParams(
GURL("https://support.google.com/chrome/answer/96817"),
content::Referrer(),
ui::DispositionFromEventFlags(
event.flags(), WindowOpenDisposition::NEW_FOREGROUND_TAB),
ui::PAGE_TRANSITION_LINK, false),
/*navigation_handle_callback=*/{});
}
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kUmaConsentCheckboxId);
class SessionCrashedBubbleDelegate : public ui::DialogModelDelegate {
public:
explicit SessionCrashedBubbleDelegate(Profile* profile) {
if (ExitTypeService* exit_type_service =
ExitTypeService::GetInstanceForProfile(profile)) {
crashed_lock_ = exit_type_service->CreateCrashedLock();
}
}
~SessionCrashedBubbleDelegate() override { g_instance_for_test = nullptr; }
void OpenStartupPages(Browser* browser) {
MaybeEnableUma();
dialog_model()->host()->Close();
// Opening tabs has side effects, so it's preferable to do it after the
// bubble was closed.
SessionRestore::OpenStartupPagesAfterCrash(browser);
}
void RestorePreviousSession(Browser* browser) {
MaybeEnableUma();
// The call to Close() deletes this. Grab the lock so that session restore
// is triggered before the lock is destroyed, otherwise ExitTypeService
// won't wait for restore to complete.
std::unique_ptr<ExitTypeService::CrashedLock> lock =
std::move(crashed_lock_);
dialog_model()->host()->Close();
// Restoring tabs has side effects, so it's preferable to do it after the
// bubble was closed.
SessionRestore::RestoreSessionAfterCrash(browser);
}
void MaybeEnableUma() {
// Record user's choice for opt-in in to UMA.
// There's no opt-out choice in the crash restore bubble.
if (!dialog_model()->HasField(kUmaConsentCheckboxId)) {
return;
}
if (dialog_model()
->GetCheckboxByUniqueId(kUmaConsentCheckboxId)
->is_checked()) {
ChangeMetricsReportingState(
true, ChangeMetricsReportingStateCalledFrom::kSessionCrashedDialog);
}
}
private:
std::unique_ptr<ExitTypeService::CrashedLock> crashed_lock_;
};
} // namespace
// A helper class that listens to browser removal event.
class SessionCrashedBubbleView::BrowserRemovalObserver
: public BrowserListObserver {
public:
explicit BrowserRemovalObserver(Browser* browser) : browser_(browser) {
DCHECK(browser_);
BrowserList::AddObserver(this);
}
BrowserRemovalObserver(const BrowserRemovalObserver&) = delete;
BrowserRemovalObserver& operator=(const BrowserRemovalObserver&) = delete;
~BrowserRemovalObserver() override { BrowserList::RemoveObserver(this); }
// Overridden from BrowserListObserver.
void OnBrowserRemoved(Browser* browser) override {
if (browser == browser_) {
browser_ = nullptr;
}
}
Browser* browser() const { return browser_; }
private:
raw_ptr<Browser> browser_;
};
// static
void SessionCrashedBubble::ShowIfNotOffTheRecordProfile(
Browser* browser,
bool skip_tab_checking) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (browser->profile()->IsOffTheRecord()) {
return;
}
// Observes possible browser removal before Show is called.
auto browser_observer =
std::make_unique<SessionCrashedBubbleView::BrowserRemovalObserver>(
browser);
if (DoesSupportConsentCheck()) {
GoogleUpdateSettings::CollectStatsConsentTaskRunner()
->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(&GoogleUpdateSettings::GetCollectStatsConsent),
base::BindOnce(&SessionCrashedBubbleView::Show,
std::move(browser_observer), skip_tab_checking));
} else {
SessionCrashedBubbleView::Show(std::move(browser_observer),
skip_tab_checking, false);
}
}
// static
void SessionCrashedBubbleView::Show(
std::unique_ptr<BrowserRemovalObserver> browser_observer,
bool skip_tab_checking,
bool uma_opted_in_already) {
// Determine whether or not the UMA opt-in option should be offered. It is
// offered only when it is a Google chrome build, user hasn't opted in yet,
// and the preference is modifiable by the user.
bool offer_uma_optin = false;
if (DoesSupportConsentCheck() && !uma_opted_in_already) {
offer_uma_optin = !IsMetricsReportingPolicyManaged();
}
Browser* browser = browser_observer->browser();
if (browser && (skip_tab_checking ||
browser->tab_strip_model()->GetActiveWebContents())) {
ShowBubble(browser, offer_uma_optin);
return;
}
}
// static
views::BubbleDialogDelegate* SessionCrashedBubbleView::GetInstanceForTest() {
return g_instance_for_test;
}
views::BubbleDialogDelegate* SessionCrashedBubbleView::ShowBubble(
Browser* browser,
bool offer_uma_optin) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
// TODO(webium): WebUI browser does not use BrowserView. Make an WebUI anchor
// for the bubble.
if (!browser_view) {
return nullptr;
}
views::View* anchor_view =
browser_view->toolbar_button_provider()->GetAppMenuButton();
auto bubble_delegate_unique =
std::make_unique<SessionCrashedBubbleDelegate>(browser->profile());
SessionCrashedBubbleDelegate* bubble_delegate = bubble_delegate_unique.get();
ui::DialogModel::Builder dialog_builder(std::move(bubble_delegate_unique));
dialog_builder
.SetTitle(l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_BUBBLE_TITLE))
.DisableCloseOnDeactivate()
.SetIsAlertDialog()
.AddParagraph(ui::DialogModelLabel(IDS_SESSION_CRASHED_VIEW_MESSAGE));
if (offer_uma_optin) {
dialog_builder.AddCheckbox(
kUmaConsentCheckboxId,
ui::DialogModelLabel::CreateWithReplacement(
IDS_SESSION_CRASHED_VIEW_UMA_OPTIN,
ui::DialogModelLabel::CreateLink(
IDS_SESSION_CRASHED_BUBBLE_UMA_LINK_TEXT,
base::BindRepeating(&OpenUmaLink, browser)))
.set_is_secondary());
}
const SessionStartupPref session_startup_pref =
SessionStartupPref::GetStartupPref(browser->profile());
if (session_startup_pref.ShouldOpenUrls() &&
!session_startup_pref.urls.empty()) {
dialog_builder.AddCancelButton(
base::BindOnce(&SessionCrashedBubbleDelegate::OpenStartupPages,
base::Unretained(bubble_delegate), browser));
}
dialog_builder.AddOkButton(
base::BindOnce(&SessionCrashedBubbleDelegate::RestorePreviousSession,
base::Unretained(bubble_delegate), browser),
ui::DialogModel::Button::Params().SetLabel(
l10n_util::GetStringUTF16(IDS_SESSION_CRASHED_VIEW_RESTORE_BUTTON)));
auto bubble = std::make_unique<views::BubbleDialogModelHost>(
dialog_builder.Build(), anchor_view, views::BubbleBorder::TOP_RIGHT);
views::BubbleDialogDelegate* bubble_ptr = bubble.get();
g_instance_for_test = bubble_ptr;
views::BubbleDialogDelegate::CreateBubble(std::move(bubble))->Show();
return bubble_ptr;
}