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/ui/app_modal_dialogs/app_modal_dialog.h"
     14 #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h"
     15 #include "chrome/browser/ui/app_modal_dialogs/javascript_app_modal_dialog.h"
     16 #include "chrome/browser/ui/app_modal_dialogs/native_app_modal_dialog.h"
     17 #include "chrome/common/chrome_constants.h"
     18 #include "content/public/browser/web_contents.h"
     19 #include "content/public/common/content_client.h"
     20 #include "content/public/common/javascript_message_type.h"
     21 #include "extensions/browser/extension_system.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::Get(
     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 =
    213       message_text + base::ASCIIToUTF16("\n\n") + footer;
    214 
    215   const Extension* extension = GetExtensionForWebContents(web_contents);
    216   if (extension)
    217     IncrementLazyKeepaliveCount(extension, web_contents);
    218 
    219   AppModalDialogQueue::GetInstance()->AddDialog(new JavaScriptAppModalDialog(
    220       web_contents,
    221       &javascript_dialog_extra_data_,
    222       title,
    223       content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM,
    224       full_message,
    225       base::string16(),  // default_prompt_text
    226       false,       // display_suppress_checkbox
    227       true,        // is_before_unload_dialog
    228       is_reload,
    229       base::Bind(&ChromeJavaScriptDialogManager::OnDialogClosed,
    230                  base::Unretained(this), web_contents, callback)));
    231 }
    232 
    233 bool ChromeJavaScriptDialogManager::HandleJavaScriptDialog(
    234     WebContents* web_contents,
    235     bool accept,
    236     const base::string16* prompt_override) {
    237   AppModalDialogQueue* dialog_queue = AppModalDialogQueue::GetInstance();
    238   if (!dialog_queue->HasActiveDialog() ||
    239       !dialog_queue->active_dialog()->IsJavaScriptModalDialog() ||
    240       dialog_queue->active_dialog()->web_contents() != web_contents) {
    241     return false;
    242   }
    243   JavaScriptAppModalDialog* dialog = static_cast<JavaScriptAppModalDialog*>(
    244       dialog_queue->active_dialog());
    245   if (accept) {
    246     if (prompt_override)
    247       dialog->SetOverridePromptText(*prompt_override);
    248     dialog->native_dialog()->AcceptAppModalDialog();
    249   } else {
    250     dialog->native_dialog()->CancelAppModalDialog();
    251   }
    252   return true;
    253 }
    254 
    255 void ChromeJavaScriptDialogManager::WebContentsDestroyed(
    256     WebContents* web_contents) {
    257   CancelActiveAndPendingDialogs(web_contents);
    258   javascript_dialog_extra_data_.erase(web_contents);
    259 }
    260 
    261 base::string16 ChromeJavaScriptDialogManager::GetTitle(
    262     WebContents* web_contents,
    263     const GURL& origin_url,
    264     const std::string& accept_lang,
    265     bool is_alert) {
    266   // If the URL hasn't any host, return the default string.
    267   if (!origin_url.has_host()) {
    268       return l10n_util::GetStringUTF16(
    269           is_alert ? IDS_JAVASCRIPT_ALERT_DEFAULT_TITLE
    270                    : IDS_JAVASCRIPT_MESSAGEBOX_DEFAULT_TITLE);
    271   }
    272 
    273   // For extensions, show the extension name, but only if the origin of
    274   // the alert matches the top-level WebContents.
    275   const Extension* extension = GetExtensionForWebContents(web_contents);
    276   if (extension &&
    277       web_contents->GetLastCommittedURL().GetOrigin() == origin_url) {
    278     return base::UTF8ToUTF16(extension->name());
    279   }
    280 
    281   // Otherwise, return the formatted URL.
    282   // In this case, force URL to have LTR directionality.
    283   base::string16 url_string = net::FormatUrl(origin_url, accept_lang);
    284   return l10n_util::GetStringFUTF16(
    285       is_alert ? IDS_JAVASCRIPT_ALERT_TITLE
    286       : IDS_JAVASCRIPT_MESSAGEBOX_TITLE,
    287       base::i18n::GetDisplayStringInLTRDirectionality(url_string));
    288 }
    289 
    290 void ChromeJavaScriptDialogManager::CancelActiveAndPendingDialogs(
    291     WebContents* web_contents) {
    292   AppModalDialogQueue* queue = AppModalDialogQueue::GetInstance();
    293   AppModalDialog* active_dialog = queue->active_dialog();
    294   if (active_dialog && active_dialog->web_contents() == web_contents)
    295     active_dialog->Invalidate();
    296   for (AppModalDialogQueue::iterator i = queue->begin();
    297        i != queue->end(); ++i) {
    298     if ((*i)->web_contents() == web_contents)
    299       (*i)->Invalidate();
    300   }
    301 }
    302 
    303 void ChromeJavaScriptDialogManager::OnDialogClosed(
    304     WebContents* web_contents,
    305     DialogClosedCallback callback,
    306     bool success,
    307     const base::string16& user_input) {
    308   // If an extension opened this dialog then the extension may shut down its
    309   // lazy background page after the dialog closes. (Dialogs are closed before
    310   // their WebContents is destroyed so |web_contents| is still valid here.)
    311   const Extension* extension = GetExtensionForWebContents(web_contents);
    312   if (extension)
    313     DecrementLazyKeepaliveCount(extension, web_contents);
    314 
    315   callback.Run(success, user_input);
    316 }
    317 
    318 }  // namespace
    319 
    320 content::JavaScriptDialogManager* GetJavaScriptDialogManagerInstance() {
    321   return ChromeJavaScriptDialogManager::GetInstance();
    322 }
    323