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 "components/url_formatter/url_formatter.h"
16 #include "content/public/common/javascript_message_type.h"
17 #include "grit/components_strings.h"
18 #include "net/base/net_util.h"
19 #include "ui/base/l10n/l10n_util.h"
24 class DefaultExtensionsClient
: public JavaScriptDialogExtensionsClient
{
26 DefaultExtensionsClient() {}
27 ~DefaultExtensionsClient() override
{}
30 // JavaScriptDialogExtensionsClient:
31 void OnDialogOpened(content::WebContents
* web_contents
) override
{}
32 void OnDialogClosed(content::WebContents
* web_contents
) override
{}
33 bool GetExtensionName(content::WebContents
* web_contents
,
34 const GURL
& origin_url
,
35 std::string
* name_out
) override
{
39 DISALLOW_COPY_AND_ASSIGN(DefaultExtensionsClient
);
42 bool ShouldDisplaySuppressCheckbox(
43 ChromeJavaScriptDialogExtraData
* extra_data
) {
44 base::TimeDelta time_since_last_message
= base::TimeTicks::Now() -
45 extra_data
->last_javascript_message_dismissal_
;
47 // If a WebContents is impolite and displays a second JavaScript
48 // alert within kJavaScriptMessageExpectedDelay of a previous
49 // JavaScript alert being dismissed, show a checkbox offering to
50 // suppress future alerts from this WebContents.
51 const int kJavaScriptMessageExpectedDelay
= 1000;
53 return time_since_last_message
<
54 base::TimeDelta::FromMilliseconds(kJavaScriptMessageExpectedDelay
);
59 ////////////////////////////////////////////////////////////////////////////////
60 // JavaScriptDialogManager, public:
63 JavaScriptDialogManager
* JavaScriptDialogManager::GetInstance() {
64 return Singleton
<JavaScriptDialogManager
>::get();
67 void JavaScriptDialogManager::SetNativeDialogFactory(
68 scoped_ptr
<JavaScriptNativeDialogFactory
> factory
) {
69 native_dialog_factory_
= factory
.Pass();
72 void JavaScriptDialogManager::SetExtensionsClient(
73 scoped_ptr
<JavaScriptDialogExtensionsClient
> extensions_client
) {
74 extensions_client_
= extensions_client
.Pass();
77 ////////////////////////////////////////////////////////////////////////////////
78 // JavaScriptDialogManager, private:
80 JavaScriptDialogManager::JavaScriptDialogManager()
81 : extensions_client_(new DefaultExtensionsClient
) {
84 JavaScriptDialogManager::~JavaScriptDialogManager() {
87 void JavaScriptDialogManager::RunJavaScriptDialog(
88 content::WebContents
* web_contents
,
89 const GURL
& origin_url
,
90 const std::string
& accept_lang
,
91 content::JavaScriptMessageType message_type
,
92 const base::string16
& message_text
,
93 const base::string16
& default_prompt_text
,
94 const DialogClosedCallback
& callback
,
95 bool* did_suppress_message
) {
96 *did_suppress_message
= false;
98 ChromeJavaScriptDialogExtraData
* extra_data
=
99 &javascript_dialog_extra_data_
[web_contents
];
101 if (extra_data
->suppress_javascript_messages_
) {
102 *did_suppress_message
= true;
106 bool is_alert
= message_type
== content::JAVASCRIPT_MESSAGE_TYPE_ALERT
;
107 base::string16 dialog_title
=
108 GetTitle(web_contents
, origin_url
, accept_lang
, is_alert
);
110 extensions_client_
->OnDialogOpened(web_contents
);
112 AppModalDialogQueue::GetInstance()->AddDialog(new JavaScriptAppModalDialog(
114 &javascript_dialog_extra_data_
,
119 ShouldDisplaySuppressCheckbox(extra_data
),
120 false, // is_before_unload_dialog
122 base::Bind(&JavaScriptDialogManager::OnDialogClosed
,
123 base::Unretained(this), web_contents
, callback
)));
126 void JavaScriptDialogManager::RunBeforeUnloadDialog(
127 content::WebContents
* web_contents
,
128 const base::string16
& message_text
,
130 const DialogClosedCallback
& callback
) {
131 ChromeJavaScriptDialogExtraData
* extra_data
=
132 &javascript_dialog_extra_data_
[web_contents
];
134 if (extra_data
->suppress_javascript_messages_
) {
135 // If a site harassed the user enough for them to put it on mute, then it
136 // lost its privilege to deny unloading.
137 callback
.Run(true, base::string16());
141 const base::string16 title
= l10n_util::GetStringUTF16(is_reload
?
142 IDS_BEFORERELOAD_MESSAGEBOX_TITLE
: IDS_BEFOREUNLOAD_MESSAGEBOX_TITLE
);
143 const base::string16 footer
= l10n_util::GetStringUTF16(is_reload
?
144 IDS_BEFORERELOAD_MESSAGEBOX_FOOTER
: IDS_BEFOREUNLOAD_MESSAGEBOX_FOOTER
);
146 base::string16 full_message
=
147 message_text
+ base::ASCIIToUTF16("\n\n") + footer
;
149 extensions_client_
->OnDialogOpened(web_contents
);
151 AppModalDialogQueue::GetInstance()->AddDialog(new JavaScriptAppModalDialog(
153 &javascript_dialog_extra_data_
,
155 content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM
,
157 base::string16(), // default_prompt_text
158 ShouldDisplaySuppressCheckbox(extra_data
),
159 true, // is_before_unload_dialog
161 base::Bind(&JavaScriptDialogManager::OnDialogClosed
,
162 base::Unretained(this), web_contents
, callback
)));
165 bool JavaScriptDialogManager::HandleJavaScriptDialog(
166 content::WebContents
* web_contents
,
168 const base::string16
* prompt_override
) {
169 AppModalDialogQueue
* dialog_queue
= AppModalDialogQueue::GetInstance();
170 if (!dialog_queue
->HasActiveDialog() ||
171 !dialog_queue
->active_dialog()->IsJavaScriptModalDialog() ||
172 dialog_queue
->active_dialog()->web_contents() != web_contents
) {
175 JavaScriptAppModalDialog
* dialog
= static_cast<JavaScriptAppModalDialog
*>(
176 dialog_queue
->active_dialog());
179 dialog
->SetOverridePromptText(*prompt_override
);
180 dialog
->native_dialog()->AcceptAppModalDialog();
182 dialog
->native_dialog()->CancelAppModalDialog();
187 void JavaScriptDialogManager::ResetDialogState(
188 content::WebContents
* web_contents
) {
189 CancelActiveAndPendingDialogs(web_contents
);
190 javascript_dialog_extra_data_
.erase(web_contents
);
193 base::string16
JavaScriptDialogManager::GetTitle(
194 content::WebContents
* web_contents
,
195 const GURL
& origin_url
,
196 const std::string
& accept_lang
,
198 // If the URL hasn't any host, return the default string.
199 if (!origin_url
.has_host()) {
200 return l10n_util::GetStringUTF16(
201 is_alert
? IDS_JAVASCRIPT_ALERT_DEFAULT_TITLE
202 : IDS_JAVASCRIPT_MESSAGEBOX_DEFAULT_TITLE
);
205 // For extensions, show the extension name, but only if the origin of
206 // the alert matches the top-level WebContents.
208 if (extensions_client_
->GetExtensionName(web_contents
, origin_url
, &name
))
209 return base::UTF8ToUTF16(name
);
211 // Otherwise, return the formatted URL.
212 // In this case, force URL to have LTR directionality.
213 base::string16 url_string
= url_formatter::FormatUrl(origin_url
, accept_lang
);
214 return l10n_util::GetStringFUTF16(
215 is_alert
? IDS_JAVASCRIPT_ALERT_TITLE
216 : IDS_JAVASCRIPT_MESSAGEBOX_TITLE
,
217 base::i18n::GetDisplayStringInLTRDirectionality(url_string
));
220 void JavaScriptDialogManager::CancelActiveAndPendingDialogs(
221 content::WebContents
* web_contents
) {
222 AppModalDialogQueue
* queue
= AppModalDialogQueue::GetInstance();
223 AppModalDialog
* active_dialog
= queue
->active_dialog();
224 for (AppModalDialogQueue::iterator i
= queue
->begin();
225 i
!= queue
->end(); ++i
) {
226 // Invalidating the active dialog might trigger showing a not-yet
227 // invalidated dialog, so invalidate the active dialog last.
228 if ((*i
) == active_dialog
)
230 if ((*i
)->web_contents() == web_contents
)
233 if (active_dialog
&& active_dialog
->web_contents() == web_contents
)
234 active_dialog
->Invalidate();
237 void JavaScriptDialogManager::OnDialogClosed(
238 content::WebContents
* web_contents
,
239 DialogClosedCallback callback
,
241 const base::string16
& user_input
) {
242 // If an extension opened this dialog then the extension may shut down its
243 // lazy background page after the dialog closes. (Dialogs are closed before
244 // their WebContents is destroyed so |web_contents| is still valid here.)
245 extensions_client_
->OnDialogClosed(web_contents
);
247 callback
.Run(success
, user_input
);
250 } // namespace app_modal