1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
// Copyright 2016 The Chromium Authors
// 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/functional/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,
// Another modal dialog is showing, so this dialog could not be shown.
kSuppressedByOtherDialog = 10,
kMaxValue = kSuppressedByOtherDialog,
};
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_
|