1 // Copyright 2014 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.
5 #include "components/app_modal/javascript_dialog_manager.h"
8 #include "base/i18n/rtl.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "components/app_modal/app_modal_dialog.h"
11 #include "components/app_modal/app_modal_dialog_queue.h"
12 #include "components/app_modal/javascript_dialog_extensions_client.h"
13 #include "components/app_modal/javascript_native_dialog_factory.h"
14 #include "components/app_modal/native_app_modal_dialog.h"
15 #include "content/public/common/javascript_message_type.h"
16 #include "grit/components_strings.h"
17 #include "net/base/net_util.h"
18 #include "ui/base/l10n/l10n_util.h"
23 class DefaultExtensionsClient
: public JavaScriptDialogExtensionsClient
{
25 DefaultExtensionsClient() {}
26 ~DefaultExtensionsClient() override
{}
29 // JavaScriptDialogExtensionsClient:
30 void OnDialogOpened(content::WebContents
* web_contents
) override
{}
31 void OnDialogClosed(content::WebContents
* web_contents
) override
{}
32 bool GetExtensionName(content::WebContents
* web_contents
,
33 const GURL
& origin_url
,
34 std::string
* name_out
) override
{
38 DISALLOW_COPY_AND_ASSIGN(DefaultExtensionsClient
);
41 bool ShouldDisplaySuppressCheckbox(
42 ChromeJavaScriptDialogExtraData
* extra_data
) {
43 base::TimeDelta time_since_last_message
= base::TimeTicks::Now() -
44 extra_data
->last_javascript_message_dismissal_
;
46 // If a WebContents is impolite and displays a second JavaScript
47 // alert within kJavaScriptMessageExpectedDelay of a previous
48 // JavaScript alert being dismissed, show a checkbox offering to
49 // suppress future alerts from this WebContents.
50 const int kJavaScriptMessageExpectedDelay
= 1000;
52 return time_since_last_message
<
53 base::TimeDelta::FromMilliseconds(kJavaScriptMessageExpectedDelay
);
58 ////////////////////////////////////////////////////////////////////////////////
59 // JavaScriptDialogManager, public:
62 JavaScriptDialogManager
* JavaScriptDialogManager::GetInstance() {
63 return Singleton
<JavaScriptDialogManager
>::get();
66 void JavaScriptDialogManager::SetNativeDialogFactory(
67 scoped_ptr
<JavaScriptNativeDialogFactory
> factory
) {
68 native_dialog_factory_
= factory
.Pass();
71 void JavaScriptDialogManager::SetExtensionsClient(
72 scoped_ptr
<JavaScriptDialogExtensionsClient
> extensions_client
) {
73 extensions_client_
= extensions_client
.Pass();
76 ////////////////////////////////////////////////////////////////////////////////
77 // JavaScriptDialogManager, private:
79 JavaScriptDialogManager::JavaScriptDialogManager()
80 : extensions_client_(new DefaultExtensionsClient
) {
83 JavaScriptDialogManager::~JavaScriptDialogManager() {
86 void JavaScriptDialogManager::RunJavaScriptDialog(
87 content::WebContents
* web_contents
,
88 const GURL
& origin_url
,
89 const std::string
& accept_lang
,
90 content::JavaScriptMessageType message_type
,
91 const base::string16
& message_text
,
92 const base::string16
& default_prompt_text
,
93 const DialogClosedCallback
& callback
,
94 bool* did_suppress_message
) {
95 *did_suppress_message
= false;
97 ChromeJavaScriptDialogExtraData
* extra_data
=
98 &javascript_dialog_extra_data_
[web_contents
];
100 if (extra_data
->suppress_javascript_messages_
) {
101 *did_suppress_message
= true;
105 bool is_alert
= message_type
== content::JAVASCRIPT_MESSAGE_TYPE_ALERT
;
106 base::string16 dialog_title
=
107 GetTitle(web_contents
, origin_url
, accept_lang
, is_alert
);
109 extensions_client_
->OnDialogOpened(web_contents
);
111 AppModalDialogQueue::GetInstance()->AddDialog(new JavaScriptAppModalDialog(
113 &javascript_dialog_extra_data_
,
118 ShouldDisplaySuppressCheckbox(extra_data
),
119 false, // is_before_unload_dialog
121 base::Bind(&JavaScriptDialogManager::OnDialogClosed
,
122 base::Unretained(this), web_contents
, callback
)));
125 void JavaScriptDialogManager::RunBeforeUnloadDialog(
126 content::WebContents
* web_contents
,
127 const base::string16
& message_text
,
129 const DialogClosedCallback
& callback
) {
130 ChromeJavaScriptDialogExtraData
* extra_data
=
131 &javascript_dialog_extra_data_
[web_contents
];
133 if (extra_data
->suppress_javascript_messages_
) {
134 // If a site harassed the user enough for them to put it on mute, then it
135 // lost its privilege to deny unloading.
136 callback
.Run(true, base::string16());
140 const base::string16 title
= l10n_util::GetStringUTF16(is_reload
?
141 IDS_BEFORERELOAD_MESSAGEBOX_TITLE
: IDS_BEFOREUNLOAD_MESSAGEBOX_TITLE
);
142 const base::string16 footer
= l10n_util::GetStringUTF16(is_reload
?
143 IDS_BEFORERELOAD_MESSAGEBOX_FOOTER
: IDS_BEFOREUNLOAD_MESSAGEBOX_FOOTER
);
145 base::string16 full_message
=
146 message_text
+ base::ASCIIToUTF16("\n\n") + footer
;
148 extensions_client_
->OnDialogOpened(web_contents
);
150 AppModalDialogQueue::GetInstance()->AddDialog(new JavaScriptAppModalDialog(
152 &javascript_dialog_extra_data_
,
154 content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM
,
156 base::string16(), // default_prompt_text
157 ShouldDisplaySuppressCheckbox(extra_data
),
158 true, // is_before_unload_dialog
160 base::Bind(&JavaScriptDialogManager::OnDialogClosed
,
161 base::Unretained(this), web_contents
, callback
)));
164 bool JavaScriptDialogManager::HandleJavaScriptDialog(
165 content::WebContents
* web_contents
,
167 const base::string16
* prompt_override
) {
168 AppModalDialogQueue
* dialog_queue
= AppModalDialogQueue::GetInstance();
169 if (!dialog_queue
->HasActiveDialog() ||
170 !dialog_queue
->active_dialog()->IsJavaScriptModalDialog() ||
171 dialog_queue
->active_dialog()->web_contents() != web_contents
) {
174 JavaScriptAppModalDialog
* dialog
= static_cast<JavaScriptAppModalDialog
*>(
175 dialog_queue
->active_dialog());
178 dialog
->SetOverridePromptText(*prompt_override
);
179 dialog
->native_dialog()->AcceptAppModalDialog();
181 dialog
->native_dialog()->CancelAppModalDialog();
186 void JavaScriptDialogManager::ResetDialogState(
187 content::WebContents
* web_contents
) {
188 CancelActiveAndPendingDialogs(web_contents
);
189 javascript_dialog_extra_data_
.erase(web_contents
);
192 base::string16
JavaScriptDialogManager::GetTitle(
193 content::WebContents
* web_contents
,
194 const GURL
& origin_url
,
195 const std::string
& accept_lang
,
197 // If the URL hasn't any host, return the default string.
198 if (!origin_url
.has_host()) {
199 return l10n_util::GetStringUTF16(
200 is_alert
? IDS_JAVASCRIPT_ALERT_DEFAULT_TITLE
201 : IDS_JAVASCRIPT_MESSAGEBOX_DEFAULT_TITLE
);
204 // For extensions, show the extension name, but only if the origin of
205 // the alert matches the top-level WebContents.
207 if (extensions_client_
->GetExtensionName(web_contents
, origin_url
, &name
))
208 return base::UTF8ToUTF16(name
);
210 // Otherwise, return the formatted URL.
211 // In this case, force URL to have LTR directionality.
212 base::string16 url_string
= net::FormatUrl(origin_url
, accept_lang
);
213 return l10n_util::GetStringFUTF16(
214 is_alert
? IDS_JAVASCRIPT_ALERT_TITLE
215 : IDS_JAVASCRIPT_MESSAGEBOX_TITLE
,
216 base::i18n::GetDisplayStringInLTRDirectionality(url_string
));
219 void JavaScriptDialogManager::CancelActiveAndPendingDialogs(
220 content::WebContents
* web_contents
) {
221 AppModalDialogQueue
* queue
= AppModalDialogQueue::GetInstance();
222 AppModalDialog
* active_dialog
= queue
->active_dialog();
223 for (AppModalDialogQueue::iterator i
= queue
->begin();
224 i
!= queue
->end(); ++i
) {
225 // Invalidating the active dialog might trigger showing a not-yet
226 // invalidated dialog, so invalidate the active dialog last.
227 if ((*i
) == active_dialog
)
229 if ((*i
)->web_contents() == web_contents
)
232 if (active_dialog
&& active_dialog
->web_contents() == web_contents
)
233 active_dialog
->Invalidate();
236 void JavaScriptDialogManager::OnDialogClosed(
237 content::WebContents
* web_contents
,
238 DialogClosedCallback callback
,
240 const base::string16
& user_input
) {
241 // If an extension opened this dialog then the extension may shut down its
242 // lazy background page after the dialog closes. (Dialogs are closed before
243 // their WebContents is destroyed so |web_contents| is still valid here.)
244 extensions_client_
->OnDialogClosed(web_contents
);
246 callback
.Run(success
, user_input
);
249 } // namespace app_modal