| // 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 CHROME_BROWSER_UI_JAVASCRIPT_DIALOGS_JAVASCRIPT_DIALOG_TAB_HELPER_H_ | 
 | #define CHROME_BROWSER_UI_JAVASCRIPT_DIALOGS_JAVASCRIPT_DIALOG_TAB_HELPER_H_ | 
 |  | 
 | #include <memory> | 
 |  | 
 | #include "base/callback_forward.h" | 
 | #include "base/macros.h" | 
 | #include "base/memory/weak_ptr.h" | 
 | #include "build/build_config.h" | 
 | #include "chrome/browser/ui/javascript_dialogs/javascript_dialog.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" | 
 |  | 
 | #if !defined(OS_ANDROID) | 
 | #include "chrome/browser/ui/browser_list_observer.h" | 
 | #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" | 
 | #endif | 
 |  | 
 | // A class, attached to WebContentses in browser windows, that is the | 
 | // JavaScriptDialogManager for them and handles displaying their 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 JavaScriptDialogTabHelper | 
 |     : public content::JavaScriptDialogManager, | 
 |       public content::WebContentsObserver, | 
 | #if !defined(OS_ANDROID) | 
 |       public BrowserListObserver, | 
 |       public TabStripModelObserver, | 
 | #endif | 
 |       public content::WebContentsUserData<JavaScriptDialogTabHelper> { | 
 |  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, | 
 |   }; | 
 |  | 
 |   explicit JavaScriptDialogTabHelper(content::WebContents* web_contents); | 
 |   ~JavaScriptDialogTabHelper() override; | 
 |  | 
 |   void SetDialogShownCallbackForTesting(base::OnceClosure callback); | 
 |   bool IsShowingDialogForTesting() const; | 
 |   void ClickDialogButtonForTesting(bool accept, | 
 |                                    const base::string16& user_input); | 
 |  | 
 |   // JavaScriptDialogManager: | 
 |   void RunJavaScriptDialog(content::WebContents* web_contents, | 
 |                            content::RenderFrameHost* render_frame_host, | 
 |                            content::JavaScriptDialogType dialog_type, | 
 |                            const base::string16& message_text, | 
 |                            const base::string16& 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 base::string16* 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; | 
 |   void DidStartNavigationToPendingEntry( | 
 |       const GURL& url, | 
 |       content::ReloadType reload_type) override; | 
 |  | 
 | #if !defined(OS_ANDROID) | 
 |   // BrowserListObserver: | 
 |   void OnBrowserSetLastActive(Browser* browser) override; | 
 |  | 
 |   // TabStripModelObserver: | 
 |   void OnTabStripModelChanged( | 
 |       TabStripModel* tab_strip_model, | 
 |       const TabStripModelChange& change, | 
 |       const TabStripSelectionChange& selection) override; | 
 | #endif | 
 |  | 
 |  private: | 
 |   friend class content::WebContentsUserData<JavaScriptDialogTabHelper>; | 
 |  | 
 |   // 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 base::string16& user_input); | 
 |  | 
 |   // Marks the tab as needing attention. The WebContents must be in a browser | 
 |   // window. | 
 |   void SetTabNeedsAttention(bool attention); | 
 |  | 
 | #if !defined(OS_ANDROID) | 
 |   // Marks the tab as needing attention. | 
 |   void SetTabNeedsAttentionImpl(bool attention, | 
 |                                 TabStripModel* tab_strip_model, | 
 |                                 int index); | 
 | #endif | 
 |  | 
 |   // 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<JavaScriptDialog> dialog_; | 
 |   base::OnceCallback<base::WeakPtr<JavaScriptDialog>()> 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_; | 
 |  | 
 | #if !defined(OS_ANDROID) | 
 |   // If this instance is observing a TabStripModel, then this member is not | 
 |   // nullptr. | 
 |   // | 
 |   // This raw pointer is safe to use because the lifetime of this instance is | 
 |   // tied to the corresponding WebContents, which is owned by the TabStripModel. | 
 |   // Any time TabStripModel would give up ownership of the WebContents, it would | 
 |   // first send either a TabStripModelChange::kRemoved or kReplaced | 
 |   // via OnTabStripModelChanged() callback, which gives this instance the | 
 |   // opportunity to stop observing the TabStripModel. | 
 |   // | 
 |   // A TabStripModel cannot be destroyed without first detaching all of its | 
 |   // WebContents. | 
 |   TabStripModel* tab_strip_model_being_observed_ = nullptr; | 
 | #endif | 
 |  | 
 |   WEB_CONTENTS_USER_DATA_KEY_DECL(); | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(JavaScriptDialogTabHelper); | 
 | }; | 
 |  | 
 | #endif  // CHROME_BROWSER_UI_JAVASCRIPT_DIALOGS_JAVASCRIPT_DIALOG_TAB_HELPER_H_ |