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