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