// 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 #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 { 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 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( content::WebContents* web_contents, std::unique_ptr 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 dialog_; base::OnceCallback()> 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 delegate_; WEB_CONTENTS_USER_DATA_KEY_DECL(); }; } // namespace javascript_dialogs #endif // COMPONENTS_JAVASCRIPT_DIALOGS_TAB_MODAL_DIALOG_MANAGER_H_