tab_modal_dialog_manager.h 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Copyright 2016 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #ifndef COMPONENTS_JAVASCRIPT_DIALOGS_TAB_MODAL_DIALOG_MANAGER_H_
  5. #define COMPONENTS_JAVASCRIPT_DIALOGS_TAB_MODAL_DIALOG_MANAGER_H_
  6. #include <memory>
  7. #include "base/callback_forward.h"
  8. #include "base/memory/weak_ptr.h"
  9. #include "build/build_config.h"
  10. #include "components/javascript_dialogs/tab_modal_dialog_manager_delegate.h"
  11. #include "components/javascript_dialogs/tab_modal_dialog_view.h"
  12. #include "content/public/browser/javascript_dialog_manager.h"
  13. #include "content/public/browser/web_contents_observer.h"
  14. #include "content/public/browser/web_contents_user_data.h"
  15. namespace javascript_dialogs {
  16. // A class that serves as the JavaScriptDialogManager for tab modal JavaScript
  17. // dialogs.
  18. //
  19. // This implements two different functionalities for JavaScript dialogs.
  20. //
  21. // window.alert() dialogs are tab-modal dialogs. If a tab calls alert() while it
  22. // is foremost, a dialog is displayed and the renderer is held blocked. When the
  23. // user switches to a different tab, or if the dialog is shown while the tab is
  24. // not foremost, while the dialog is shown, the renderer is not held blocked.
  25. //
  26. // window.confirm() and window.prompt() dialogs are auto-dismissing,
  27. // dialogs that close when the user switches away to a different tab. Because
  28. // JavaScript dialogs are synchronous and block arbitrary sets of renderers,
  29. // they cannot be made tab-modal. Therefore the next best option is to make them
  30. // auto-closing, so that they never block the user's access to other renderers.
  31. //
  32. // References:
  33. // http://bit.ly/project-oldspice
  34. class TabModalDialogManager
  35. : public content::JavaScriptDialogManager,
  36. public content::WebContentsObserver,
  37. public content::WebContentsUserData<TabModalDialogManager> {
  38. public:
  39. enum class DismissalCause {
  40. // This is used for a UMA histogram. Please never alter existing values,
  41. // only append new ones.
  42. // The tab helper is destroyed. By current design, the dialog is always torn
  43. // down before the tab helper is destroyed, so we never see the
  44. // |kTabHelperDestroyed| enum. However, that might not always be the case.
  45. // It's better to have a simple rule in the code of "when you close a dialog
  46. // you must provide a UMA enum reason why" and have some enums that never
  47. // happen than have haphazard code that enforces no rules.
  48. kTabHelperDestroyed = 0,
  49. // Subsequent dialog pops up.
  50. kSubsequentDialogShown = 1,
  51. // HandleJavaScriptDialog() is called. In practice, this can happen whenever
  52. // browser choose to accept/cancel the dialog without user's interaction.
  53. kHandleDialogCalled = 2,
  54. // CancelDialogs() is called. In practice, this can happen whenever browser
  55. // choose to close the dialog without user's interaction. Besides, this can
  56. // also happen when tab is closed by user on a Mac platform.
  57. kCancelDialogsCalled = 3,
  58. // Tab is made hidden by opening a new tab, switching to another tab, etc.
  59. // Note that only Prompt() and Confirm() can be dismissed for this cause;
  60. // it won't affect Alert().
  61. kTabHidden = 4,
  62. // Another browser instance is made active.
  63. kBrowserSwitched = 5,
  64. // Accept/Cancel button is clicked by user.
  65. kDialogButtonClicked = 6,
  66. // Navigation occurs.
  67. kTabNavigated = 7,
  68. // Tab's contents was replaced.
  69. kTabSwitchedOut = 8,
  70. // CloseDialog() is called. In practice, this happens when tab is closed by
  71. // user on a non-Mac platform.
  72. kDialogClosed = 9,
  73. kMaxValue = kDialogClosed,
  74. };
  75. TabModalDialogManager(const TabModalDialogManager&) = delete;
  76. TabModalDialogManager& operator=(const TabModalDialogManager&) = delete;
  77. ~TabModalDialogManager() override;
  78. void BrowserActiveStateChanged();
  79. void CloseDialogWithReason(DismissalCause reason);
  80. void SetDialogShownCallbackForTesting(base::OnceClosure callback);
  81. bool IsShowingDialogForTesting() const;
  82. void ClickDialogButtonForTesting(bool accept,
  83. const std::u16string& user_input);
  84. using DialogDismissedCallback = base::OnceCallback<void(DismissalCause)>;
  85. void SetDialogDismissedCallbackForTesting(DialogDismissedCallback callback);
  86. // JavaScriptDialogManager:
  87. void RunJavaScriptDialog(content::WebContents* web_contents,
  88. content::RenderFrameHost* render_frame_host,
  89. content::JavaScriptDialogType dialog_type,
  90. const std::u16string& message_text,
  91. const std::u16string& default_prompt_text,
  92. DialogClosedCallback callback,
  93. bool* did_suppress_message) override;
  94. void RunBeforeUnloadDialog(content::WebContents* web_contents,
  95. content::RenderFrameHost* render_frame_host,
  96. bool is_reload,
  97. DialogClosedCallback callback) override;
  98. bool HandleJavaScriptDialog(content::WebContents* web_contents,
  99. bool accept,
  100. const std::u16string* prompt_override) override;
  101. void CancelDialogs(content::WebContents* web_contents,
  102. bool reset_state) override;
  103. // WebContentsObserver:
  104. void OnVisibilityChanged(content::Visibility visibility) override;
  105. void DidStartNavigation(
  106. content::NavigationHandle* navigation_handle) override;
  107. private:
  108. friend class content::WebContentsUserData<TabModalDialogManager>;
  109. TabModalDialogManager(
  110. content::WebContents* web_contents,
  111. std::unique_ptr<TabModalDialogManagerDelegate> delegate);
  112. // Logs the cause of a dialog dismissal in UMA.
  113. void LogDialogDismissalCause(DismissalCause cause);
  114. // Handles the case when the user switches away from a tab.
  115. void HandleTabSwitchAway(DismissalCause cause);
  116. // This closes any open dialog. It is safe to call if there is no currently
  117. // open dialog.
  118. void CloseDialog(DismissalCause cause,
  119. bool success,
  120. const std::u16string& user_input);
  121. // There can be at most one dialog (pending or not) being shown at any given
  122. // time on a tab. Depending on the type of the dialog, the variables
  123. // |dialog_|, |pending_dialog_|, and |dialog_callback_| can be present in
  124. // different combinations.
  125. //
  126. // No dialog:
  127. // |dialog_|, |pending_dialog_|, and |dialog_callback_| are null.
  128. // alert() dialog:
  129. // Either |dialog_| or |pending_dialog_| is not null. If the dialog is shown
  130. // while the tab was foremost, the dialog is be created and a weak pointer
  131. // to it is held in |dialog_|. If the dialog is attempted while the tab
  132. // is not foremost, the call to create the dialog-to-be is held in
  133. // |pending_dialog_| until the tab is brought foremost. At that time the
  134. // callback will be made, |pending_dialog_| will be null, and the dialog
  135. // will live, referenced by |dialog_|. As for |dialog_callback_|, if the
  136. // dialog is shown while the tab was foremost, |dialog_callback_| is not
  137. // null. If the dialog was shown while the tab was not foremost, or if the
  138. // tab was switched to be non-foremost, the renderer is not held blocked,
  139. // and |dialog_callback_| will be null (because it will have been called to
  140. // free up the renderer.)
  141. // confirm() and prompt() dialogs:
  142. // Both |dialog_| and |dialog_callback_| are not null. |pending_dialog_| is
  143. // null, as only alert() dialogs can be in a pending state.
  144. // The dialog being displayed on the observed WebContents, if any. At any
  145. // given time at most one of |dialog_| and |pending_dialog_| can be non-null.
  146. base::WeakPtr<TabModalDialogView> dialog_;
  147. base::OnceCallback<base::WeakPtr<TabModalDialogView>()> pending_dialog_;
  148. // The callback to return a result for a dialog. Not null if the renderer is
  149. // waiting for a result; null if there is no |dialog_| or if the dialog is an
  150. // alert() dialog and the callback has already been called.
  151. content::JavaScriptDialogManager::DialogClosedCallback dialog_callback_;
  152. // The type of dialog being displayed. Only valid when |dialog_| or
  153. // |pending_dialog_| is non-null.
  154. content::JavaScriptDialogType dialog_type_ =
  155. content::JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_ALERT;
  156. // A closure to be fired when a dialog is shown. For testing only.
  157. base::OnceClosure dialog_shown_;
  158. // A closure to be fired when a dialog is dismissed. For testing only.
  159. DialogDismissedCallback dialog_dismissed_;
  160. std::unique_ptr<TabModalDialogManagerDelegate> delegate_;
  161. WEB_CONTENTS_USER_DATA_KEY_DECL();
  162. };
  163. } // namespace javascript_dialogs
  164. #endif // COMPONENTS_JAVASCRIPT_DIALOGS_TAB_MODAL_DIALOG_MANAGER_H_