Home | History | Annotate | Download | only in app_modal_dialogs
      1 // Copyright (c) 2012 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 
      5 #include "chrome/browser/ui/app_modal_dialogs/javascript_dialog_manager.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/compiler_specific.h"
      9 #include "base/i18n/rtl.h"
     10 #include "base/memory/singleton.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/extensions/extension_system.h"
     14 #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog.h"
     15 #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h"
     16 #include "chrome/browser/ui/app_modal_dialogs/javascript_app_modal_dialog.h"
     17 #include "chrome/browser/ui/app_modal_dialogs/native_app_modal_dialog.h"
     18 #include "chrome/common/chrome_constants.h"
     19 #include "content/public/browser/web_contents.h"
     20 #include "content/public/common/content_client.h"
     21 #include "content/public/common/javascript_message_type.h"
     22 #include "extensions/browser/process_manager.h"
     23 #include "grit/generated_resources.h"
     24 #include "net/base/net_util.h"
     25 #include "ui/base/l10n/l10n_util.h"
     26 
     27 using content::BrowserContext;
     28 using content::JavaScriptDialogManager;
     29 using content::WebContents;
     30 using extensions::Extension;
     31 
     32 namespace {
     33 
     34 // Returns the ProcessManager for the browser context from |web_contents|.
     35 extensions::ProcessManager* GetExtensionsProcessManager(
     36     WebContents* web_contents) {
     37 #if defined(ENABLE_EXTENSIONS)
     38   return extensions::ExtensionSystem::GetForBrowserContext(
     39       web_contents->GetBrowserContext())->process_manager();
     40 #else
     41   return NULL;
     42 #endif  // defined(ENABLE_EXTENSIONS)
     43 }
     44 
     45 // Returns the extension associated with |web_contents| or NULL if there is no
     46 // associated extension (or extensions are not supported).
     47 const Extension* GetExtensionForWebContents(WebContents* web_contents) {
     48 #if defined(ENABLE_EXTENSIONS)
     49   extensions::ProcessManager* pm = GetExtensionsProcessManager(web_contents);
     50   return pm->GetExtensionForRenderViewHost(web_contents->GetRenderViewHost());
     51 #else
     52   return NULL;
     53 #endif  // defined(ENABLE_EXTENSIONS)
     54 }
     55 
     56 // Keeps an |extension| from shutting down its lazy background page. If an
     57 // extension opens a dialog its lazy background page must stay alive until the
     58 // dialog closes.
     59 void IncrementLazyKeepaliveCount(const Extension* extension,
     60                                  WebContents* web_contents) {
     61   DCHECK(extension);
     62   DCHECK(web_contents);
     63   extensions::ProcessManager* pm = GetExtensionsProcessManager(web_contents);
     64   if (pm)
     65     pm->IncrementLazyKeepaliveCount(extension);
     66 }
     67 
     68 // Allows an |extension| to shut down its lazy background page after a dialog
     69 // closes (if nothing else is keeping it open).
     70 void DecrementLazyKeepaliveCount(const Extension* extension,
     71                                  WebContents* web_contents) {
     72   DCHECK(extension);
     73   DCHECK(web_contents);
     74   extensions::ProcessManager* pm = GetExtensionsProcessManager(web_contents);
     75   if (pm)
     76     pm->DecrementLazyKeepaliveCount(extension);
     77 }
     78 
     79 class ChromeJavaScriptDialogManager : public JavaScriptDialogManager {
     80  public:
     81   static ChromeJavaScriptDialogManager* GetInstance();
     82 
     83   virtual void RunJavaScriptDialog(
     84       WebContents* web_contents,
     85       const GURL& origin_url,
     86       const std::string& accept_lang,
     87       content::JavaScriptMessageType message_type,
     88       const base::string16& message_text,
     89       const base::string16& default_prompt_text,
     90       const DialogClosedCallback& callback,
     91       bool* did_suppress_message) OVERRIDE;
     92 
     93   virtual void RunBeforeUnloadDialog(
     94       WebContents* web_contents,
     95       const base::string16& message_text,
     96       bool is_reload,
     97       const DialogClosedCallback& callback) OVERRIDE;
     98 
     99   virtual bool HandleJavaScriptDialog(
    100       WebContents* web_contents,
    101       bool accept,
    102       const base::string16* prompt_override) OVERRIDE;
    103 
    104   virtual void CancelActiveAndPendingDialogs(
    105       WebContents* web_contents) OVERRIDE;
    106 
    107   virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
    108 
    109  private:
    110   friend struct DefaultSingletonTraits<ChromeJavaScriptDialogManager>;
    111 
    112   ChromeJavaScriptDialogManager();
    113   virtual ~ChromeJavaScriptDialogManager();
    114 
    115   base::string16 GetTitle(WebContents* web_contents,
    116                           const GURL& origin_url,
    117                           const std::string& accept_lang,
    118                           bool is_alert);
    119 
    120   // Wrapper around a DialogClosedCallback so that we can intercept it before
    121   // passing it onto the original callback.
    122   void OnDialogClosed(WebContents* web_contents,
    123                       DialogClosedCallback callback,
    124                       bool success,
    125                       const base::string16& user_input);
    126 
    127   // Mapping between the WebContents and their extra data. The key
    128   // is a void* because the pointer is just a cookie and is never dereferenced.
    129   JavaScriptAppModalDialog::ExtraDataMap javascript_dialog_extra_data_;
    130 
    131   DISALLOW_COPY_AND_ASSIGN(ChromeJavaScriptDialogManager);
    132 };
    133 
    134 ////////////////////////////////////////////////////////////////////////////////
    135 // ChromeJavaScriptDialogManager, public:
    136 
    137 ChromeJavaScriptDialogManager::ChromeJavaScriptDialogManager() {
    138 }
    139 
    140 ChromeJavaScriptDialogManager::~ChromeJavaScriptDialogManager() {
    141 }
    142 
    143 // static
    144 ChromeJavaScriptDialogManager* ChromeJavaScriptDialogManager::GetInstance() {
    145   return Singleton<ChromeJavaScriptDialogManager>::get();
    146 }
    147 
    148 void ChromeJavaScriptDialogManager::RunJavaScriptDialog(
    149     WebContents* web_contents,
    150     const GURL& origin_url,
    151     const std::string& accept_lang,
    152     content::JavaScriptMessageType message_type,
    153     const base::string16& message_text,
    154     const base::string16& default_prompt_text,
    155     const DialogClosedCallback& callback,
    156     bool* did_suppress_message)  {
    157   *did_suppress_message = false;
    158 
    159   ChromeJavaScriptDialogExtraData* extra_data =
    160       &javascript_dialog_extra_data_[web_contents];
    161 
    162   if (extra_data->suppress_javascript_messages_) {
    163     *did_suppress_message = true;
    164     return;
    165   }
    166 
    167   base::TimeDelta time_since_last_message = base::TimeTicks::Now() -
    168       extra_data->last_javascript_message_dismissal_;
    169   bool display_suppress_checkbox = false;
    170   // Show a checkbox offering to suppress further messages if this message is
    171   // being displayed within kJavaScriptMessageExpectedDelay of the last one.
    172   if (time_since_last_message <
    173       base::TimeDelta::FromMilliseconds(
    174           chrome::kJavaScriptMessageExpectedDelay)) {
    175     display_suppress_checkbox = true;
    176   } else {
    177     display_suppress_checkbox = false;
    178   }
    179 
    180   bool is_alert = message_type == content::JAVASCRIPT_MESSAGE_TYPE_ALERT;
    181   base::string16 dialog_title =
    182       GetTitle(web_contents, origin_url, accept_lang, is_alert);
    183 
    184   const Extension* extension = GetExtensionForWebContents(web_contents);
    185   if (extension)
    186     IncrementLazyKeepaliveCount(extension, web_contents);
    187 
    188   AppModalDialogQueue::GetInstance()->AddDialog(new JavaScriptAppModalDialog(
    189       web_contents,
    190       &javascript_dialog_extra_data_,
    191       dialog_title,
    192       message_type,
    193       message_text,
    194       default_prompt_text,
    195       display_suppress_checkbox,
    196       false,  // is_before_unload_dialog
    197       false,  // is_reload
    198       base::Bind(&ChromeJavaScriptDialogManager::OnDialogClosed,
    199                  base::Unretained(this), web_contents, callback)));
    200 }
    201 
    202 void ChromeJavaScriptDialogManager::RunBeforeUnloadDialog(
    203     WebContents* web_contents,
    204     const base::string16& message_text,
    205     bool is_reload,
    206     const DialogClosedCallback& callback) {
    207   const base::string16 title = l10n_util::GetStringUTF16(is_reload ?
    208       IDS_BEFORERELOAD_MESSAGEBOX_TITLE : IDS_BEFOREUNLOAD_MESSAGEBOX_TITLE);
    209   const base::string16 footer = l10n_util::GetStringUTF16(is_reload ?
    210       IDS_BEFORERELOAD_MESSAGEBOX_FOOTER : IDS_BEFOREUNLOAD_MESSAGEBOX_FOOTER);
    211 
    212   base::string16 full_message = message_text + ASCIIToUTF16("\n\n") + footer;
    213 
    214   const Extension* extension = GetExtensionForWebContents(web_contents);
    215   if (extension)
    216     IncrementLazyKeepaliveCount(extension, web_contents);
    217 
    218   AppModalDialogQueue::GetInstance()->AddDialog(new JavaScriptAppModalDialog(
    219       web_contents,
    220       &javascript_dialog_extra_data_,
    221       title,
    222       content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM,
    223       full_message,
    224       base::string16(),  // default_prompt_text
    225       false,       // display_suppress_checkbox
    226       true,        // is_before_unload_dialog
    227       is_reload,
    228       base::Bind(&ChromeJavaScriptDialogManager::OnDialogClosed,
    229                  base::Unretained(this), web_contents, callback)));
    230 }
    231 
    232 bool ChromeJavaScriptDialogManager::HandleJavaScriptDialog(
    233     WebContents* web_contents,
    234     bool accept,
    235     const base::string16* prompt_override) {
    236   AppModalDialogQueue* dialog_queue = AppModalDialogQueue::GetInstance();
    237   if (!dialog_queue->HasActiveDialog() ||
    238       !dialog_queue->active_dialog()->IsJavaScriptModalDialog() ||
    239       dialog_queue->active_dialog()->web_contents() != web_contents) {
    240     return false;
    241   }
    242   JavaScriptAppModalDialog* dialog = static_cast<JavaScriptAppModalDialog*>(
    243       dialog_queue->active_dialog());
    244   if (accept) {
    245     if (prompt_override)
    246       dialog->SetOverridePromptText(*prompt_override);
    247     dialog->native_dialog()->AcceptAppModalDialog();
    248   } else {
    249     dialog->native_dialog()->CancelAppModalDialog();
    250   }
    251   return true;
    252 }
    253 
    254 void ChromeJavaScriptDialogManager::WebContentsDestroyed(
    255     WebContents* web_contents) {
    256   CancelActiveAndPendingDialogs(web_contents);
    257   javascript_dialog_extra_data_.erase(web_contents);
    258 }
    259 
    260 base::string16 ChromeJavaScriptDialogManager::GetTitle(
    261     WebContents* web_contents,
    262     const GURL& origin_url,
    263     const std::string& accept_lang,
    264     bool is_alert) {
    265   // If the URL hasn't any host, return the default string.
    266   if (!origin_url.has_host()) {
    267       return l10n_util::GetStringUTF16(
    268           is_alert ? IDS_JAVASCRIPT_ALERT_DEFAULT_TITLE
    269                    : IDS_JAVASCRIPT_MESSAGEBOX_DEFAULT_TITLE);
    270   }
    271 
    272   const Extension* extension = GetExtensionForWebContents(web_contents);
    273   if (extension)
    274     return UTF8ToUTF16(extension->name());
    275 
    276   // Otherwise, return the formatted URL.
    277   // In this case, force URL to have LTR directionality.
    278   base::string16 url_string = net::FormatUrl(origin_url, accept_lang);
    279   return l10n_util::GetStringFUTF16(
    280       is_alert ? IDS_JAVASCRIPT_ALERT_TITLE
    281       : IDS_JAVASCRIPT_MESSAGEBOX_TITLE,
    282       base::i18n::GetDisplayStringInLTRDirectionality(url_string));
    283 }
    284 
    285 void ChromeJavaScriptDialogManager::CancelActiveAndPendingDialogs(
    286     WebContents* web_contents) {
    287   AppModalDialogQueue* queue = AppModalDialogQueue::GetInstance();
    288   AppModalDialog* active_dialog = queue->active_dialog();
    289   if (active_dialog && active_dialog->web_contents() == web_contents)
    290     active_dialog->Invalidate();
    291   for (AppModalDialogQueue::iterator i = queue->begin();
    292        i != queue->end(); ++i) {
    293     if ((*i)->web_contents() == web_contents)
    294       (*i)->Invalidate();
    295   }
    296 }
    297 
    298 void ChromeJavaScriptDialogManager::OnDialogClosed(
    299     WebContents* web_contents,
    300     DialogClosedCallback callback,
    301     bool success,
    302     const base::string16& user_input) {
    303   // If an extension opened this dialog then the extension may shut down its
    304   // lazy background page after the dialog closes. (Dialogs are closed before
    305   // their WebContents is destroyed so |web_contents| is still valid here.)
    306   const Extension* extension = GetExtensionForWebContents(web_contents);
    307   if (extension)
    308     DecrementLazyKeepaliveCount(extension, web_contents);
    309 
    310   callback.Run(success, user_input);
    311 }
    312 
    313 }  // namespace
    314 
    315 content::JavaScriptDialogManager* GetJavaScriptDialogManagerInstance() {
    316   return ChromeJavaScriptDialogManager::GetInstance();
    317 }
    318