blob: 3e393762a8ec709a472f4515917fccf43469652d [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_JAVASCRIPT_DIALOGS_TAB_MODAL_DIALOG_MANAGER_H_
#define COMPONENTS_JAVASCRIPT_DIALOGS_TAB_MODAL_DIALOG_MANAGER_H_
#include <memory>
#include "base/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "components/javascript_dialogs/tab_modal_dialog_manager_delegate.h"
#include "components/javascript_dialogs/tab_modal_dialog_view.h"
#include "content/public/browser/javascript_dialog_manager.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
namespace javascript_dialogs {
// A class that serves as the JavaScriptDialogManager for tab modal JavaScript
// dialogs.
//
// This implements two different functionalities for JavaScript dialogs.
//
// window.alert() dialogs are tab-modal dialogs. If a tab calls alert() while it
// is foremost, a dialog is displayed and the renderer is held blocked. When the
// user switches to a different tab, or if the dialog is shown while the tab is
// not foremost, while the dialog is shown, the renderer is not held blocked.
//
// window.confirm() and window.prompt() dialogs are auto-dismissing,
// dialogs that close when the user switches away to a different tab. Because
// JavaScript dialogs are synchronous and block arbitrary sets of renderers,
// they cannot be made tab-modal. Therefore the next best option is to make them
// auto-closing, so that they never block the user's access to other renderers.
//
// References:
// http://bit.ly/project-oldspice
class TabModalDialogManager
: public content::JavaScriptDialogManager,
public content::WebContentsObserver,
public content::WebContentsUserData<TabModalDialogManager> {
public:
enum class DismissalCause {
// This is used for a UMA histogram. Please never alter existing values,
// only append new ones.
// The tab helper is destroyed. By current design, the dialog is always torn
// down before the tab helper is destroyed, so we never see the
// |kTabHelperDestroyed| enum. However, that might not always be the case.
// It's better to have a simple rule in the code of "when you close a dialog
// you must provide a UMA enum reason why" and have some enums that never
// happen than have haphazard code that enforces no rules.
kTabHelperDestroyed = 0,
// Subsequent dialog pops up.
kSubsequentDialogShown = 1,
// HandleJavaScriptDialog() is called. In practice, this can happen whenever
// browser choose to accept/cancel the dialog without user's interaction.
kHandleDialogCalled = 2,
// CancelDialogs() is called. In practice, this can happen whenever browser
// choose to close the dialog without user's interaction. Besides, this can
// also happen when tab is closed by user on a Mac platform.
kCancelDialogsCalled = 3,
// Tab is made hidden by opening a new tab, switching to another tab, etc.
// Note that only Prompt() and Confirm() can be dismissed for this cause;
// it won't affect Alert().
kTabHidden = 4,
// Another browser instance is made active.
kBrowserSwitched = 5,
// Accept/Cancel button is clicked by user.
kDialogButtonClicked = 6,
// Navigation occurs.
kTabNavigated = 7,
// Tab's contents was replaced.
kTabSwitchedOut = 8,
// CloseDialog() is called. In practice, this happens when tab is closed by
// user on a non-Mac platform.
kDialogClosed = 9,
kMaxValue = kDialogClosed,
};
TabModalDialogManager(const TabModalDialogManager&) = delete;
TabModalDialogManager& operator=(const TabModalDialogManager&) = delete;
~TabModalDialogManager() override;
void BrowserActiveStateChanged();
void CloseDialogWithReason(DismissalCause reason);
void SetDialogShownCallbackForTesting(base::OnceClosure callback);
bool IsShowingDialogForTesting() const;
void ClickDialogButtonForTesting(bool accept,
const std::u16string& user_input);
using DialogDismissedCallback = base::OnceCallback<void(DismissalCause)>;
void SetDialogDismissedCallbackForTesting(DialogDismissedCallback callback);
// JavaScriptDialogManager:
void RunJavaScriptDialog(content::WebContents* web_contents,
content::RenderFrameHost* render_frame_host,
content::JavaScriptDialogType dialog_type,
const std::u16string& message_text,
const std::u16string& default_prompt_text,
DialogClosedCallback callback,
bool* did_suppress_message) override;
void RunBeforeUnloadDialog(content::WebContents* web_contents,
content::RenderFrameHost* render_frame_host,
bool is_reload,
DialogClosedCallback callback) override;
bool HandleJavaScriptDialog(content::WebContents* web_contents,
bool accept,
const std::u16string* prompt_override) override;
void CancelDialogs(content::WebContents* web_contents,
bool reset_state) override;
// WebContentsObserver:
void OnVisibilityChanged(content::Visibility visibility) override;
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override;
private:
friend class content::WebContentsUserData<TabModalDialogManager>;
TabModalDialogManager(
content::WebContents* web_contents,
std::unique_ptr<TabModalDialogManagerDelegate> delegate);
// Logs the cause of a dialog dismissal in UMA.
void LogDialogDismissalCause(DismissalCause cause);
// Handles the case when the user switches away from a tab.
void HandleTabSwitchAway(DismissalCause cause);
// This closes any open dialog. It is safe to call if there is no currently
// open dialog.
void CloseDialog(DismissalCause cause,
bool success,
const std::u16string& user_input);
// There can be at most one dialog (pending or not) being shown at any given
// time on a tab. Depending on the type of the dialog, the variables
// |dialog_|, |pending_dialog_|, and |dialog_callback_| can be present in
// different combinations.
//
// No dialog:
// |dialog_|, |pending_dialog_|, and |dialog_callback_| are null.
// alert() dialog:
// Either |dialog_| or |pending_dialog_| is not null. If the dialog is shown
// while the tab was foremost, the dialog is be created and a weak pointer
// to it is held in |dialog_|. If the dialog is attempted while the tab
// is not foremost, the call to create the dialog-to-be is held in
// |pending_dialog_| until the tab is brought foremost. At that time the
// callback will be made, |pending_dialog_| will be null, and the dialog
// will live, referenced by |dialog_|. As for |dialog_callback_|, if the
// dialog is shown while the tab was foremost, |dialog_callback_| is not
// null. If the dialog was shown while the tab was not foremost, or if the
// tab was switched to be non-foremost, the renderer is not held blocked,
// and |dialog_callback_| will be null (because it will have been called to
// free up the renderer.)
// confirm() and prompt() dialogs:
// Both |dialog_| and |dialog_callback_| are not null. |pending_dialog_| is
// null, as only alert() dialogs can be in a pending state.
// The dialog being displayed on the observed WebContents, if any. At any
// given time at most one of |dialog_| and |pending_dialog_| can be non-null.
base::WeakPtr<TabModalDialogView> dialog_;
base::OnceCallback<base::WeakPtr<TabModalDialogView>()> pending_dialog_;
// The callback to return a result for a dialog. Not null if the renderer is
// waiting for a result; null if there is no |dialog_| or if the dialog is an
// alert() dialog and the callback has already been called.
content::JavaScriptDialogManager::DialogClosedCallback dialog_callback_;
// The type of dialog being displayed. Only valid when |dialog_| or
// |pending_dialog_| is non-null.
content::JavaScriptDialogType dialog_type_ =
content::JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_ALERT;
// A closure to be fired when a dialog is shown. For testing only.
base::OnceClosure dialog_shown_;
// A closure to be fired when a dialog is dismissed. For testing only.
DialogDismissedCallback dialog_dismissed_;
std::unique_ptr<TabModalDialogManagerDelegate> delegate_;
WEB_CONTENTS_USER_DATA_KEY_DECL();
};
} // namespace javascript_dialogs
#endif // COMPONENTS_JAVASCRIPT_DIALOGS_TAB_MODAL_DIALOG_MANAGER_H_