app_modal_dialog_manager.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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.
  4. #include "components/javascript_dialogs/app_modal_dialog_manager.h"
  5. #include <algorithm>
  6. #include <utility>
  7. #include "base/bind.h"
  8. #include "base/i18n/rtl.h"
  9. #include "base/metrics/histogram_macros.h"
  10. #include "base/strings/utf_string_conversions.h"
  11. #include "build/build_config.h"
  12. #include "components/javascript_dialogs/app_modal_dialog_queue.h"
  13. #include "components/javascript_dialogs/app_modal_dialog_view.h"
  14. #include "components/javascript_dialogs/extensions_client.h"
  15. #include "components/strings/grit/components_strings.h"
  16. #include "components/url_formatter/elide_url.h"
  17. #include "content/public/browser/web_contents.h"
  18. #include "content/public/common/javascript_dialog_type.h"
  19. #include "ui/base/l10n/l10n_util.h"
  20. #include "ui/gfx/font_list.h"
  21. #include "url/origin.h"
  22. namespace javascript_dialogs {
  23. namespace {
  24. class DefaultExtensionsClient : public ExtensionsClient {
  25. public:
  26. DefaultExtensionsClient() = default;
  27. DefaultExtensionsClient(const DefaultExtensionsClient&) = delete;
  28. DefaultExtensionsClient& operator=(const DefaultExtensionsClient&) = delete;
  29. ~DefaultExtensionsClient() override = default;
  30. private:
  31. // ExtensionsClient:
  32. void OnDialogOpened(content::WebContents* web_contents) override {}
  33. void OnDialogClosed(content::WebContents* web_contents) override {}
  34. bool GetExtensionName(content::WebContents* web_contents,
  35. const url::Origin& alerting_frame_origin,
  36. std::string* name_out) override {
  37. return false;
  38. }
  39. };
  40. bool ShouldDisplaySuppressCheckbox(
  41. ChromeJavaScriptDialogExtraData* extra_data) {
  42. return extra_data->has_already_shown_a_dialog_;
  43. }
  44. } // namespace
  45. // static
  46. AppModalDialogManager* AppModalDialogManager::GetInstance() {
  47. return base::Singleton<AppModalDialogManager>::get();
  48. }
  49. void AppModalDialogManager::SetNativeDialogFactory(
  50. AppModalViewFactory factory) {
  51. view_factory_ = std::move(factory);
  52. }
  53. void AppModalDialogManager::SetExtensionsClient(
  54. std::unique_ptr<ExtensionsClient> extensions_client) {
  55. extensions_client_ = std::move(extensions_client);
  56. }
  57. AppModalDialogManager::AppModalDialogManager()
  58. : extensions_client_(new DefaultExtensionsClient) {}
  59. AppModalDialogManager::~AppModalDialogManager() = default;
  60. std::u16string AppModalDialogManager::GetTitle(
  61. content::WebContents* web_contents,
  62. const url::Origin& alerting_frame_origin) {
  63. // For extensions, show the extension name, but only if the origin of
  64. // the alert matches the top-level WebContents.
  65. std::string name;
  66. if (extensions_client_->GetExtensionName(web_contents, alerting_frame_origin,
  67. &name))
  68. return base::UTF8ToUTF16(name);
  69. // Otherwise, return the formatted URL.
  70. return GetTitleImpl(
  71. web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
  72. alerting_frame_origin);
  73. }
  74. namespace {
  75. // If an origin is opaque but has a precursor, then returns the precursor
  76. // origin. If the origin is not opaque, returns it unchanged. Unwrapping origins
  77. // allows the dialog code to provide the user with a clearer picture of which
  78. // page is actually showing the dialog.
  79. url::Origin UnwrapOriginIfOpaque(const url::Origin& origin) {
  80. if (!origin.opaque())
  81. return origin;
  82. const url::SchemeHostPort& precursor =
  83. origin.GetTupleOrPrecursorTupleIfOpaque();
  84. if (!precursor.IsValid())
  85. return origin;
  86. return url::Origin::CreateFromNormalizedTuple(
  87. precursor.scheme(), precursor.host(), precursor.port());
  88. }
  89. } // namespace
  90. // static
  91. std::u16string AppModalDialogManager::GetTitleImpl(
  92. const url::Origin& main_frame_origin,
  93. const url::Origin& alerting_frame_origin) {
  94. // Note that `Origin::Create()` handles unwrapping of `blob:` and
  95. // `filesystem:` schemed URLs, so no special handling is needed for that.
  96. // However, origins can be opaque but have precursors that are origins that a
  97. // user would be able to make sense of, so do unwrapping for that.
  98. const url::Origin unwrapped_main_frame_origin =
  99. UnwrapOriginIfOpaque(main_frame_origin);
  100. const url::Origin unwrapped_alerting_frame_origin =
  101. UnwrapOriginIfOpaque(alerting_frame_origin);
  102. bool is_same_origin_as_main_frame =
  103. unwrapped_alerting_frame_origin.IsSameOriginWith(
  104. unwrapped_main_frame_origin);
  105. if (unwrapped_alerting_frame_origin.GetURL().IsStandard() &&
  106. !unwrapped_alerting_frame_origin.GetURL().SchemeIsFile()) {
  107. std::u16string origin_string =
  108. url_formatter::FormatOriginForSecurityDisplay(
  109. unwrapped_alerting_frame_origin,
  110. url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
  111. return l10n_util::GetStringFUTF16(
  112. is_same_origin_as_main_frame ? IDS_JAVASCRIPT_MESSAGEBOX_TITLE
  113. : IDS_JAVASCRIPT_MESSAGEBOX_TITLE_IFRAME,
  114. base::i18n::GetDisplayStringInLTRDirectionality(origin_string));
  115. }
  116. return l10n_util::GetStringUTF16(
  117. is_same_origin_as_main_frame
  118. ? IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL
  119. : IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL_IFRAME);
  120. }
  121. void AppModalDialogManager::RunJavaScriptDialog(
  122. content::WebContents* web_contents,
  123. content::RenderFrameHost* render_frame_host,
  124. content::JavaScriptDialogType dialog_type,
  125. const std::u16string& message_text,
  126. const std::u16string& default_prompt_text,
  127. DialogClosedCallback callback,
  128. bool* did_suppress_message) {
  129. *did_suppress_message = false;
  130. ChromeJavaScriptDialogExtraData* extra_data =
  131. &javascript_dialog_extra_data_[web_contents];
  132. if (extra_data->suppress_javascript_messages_) {
  133. *did_suppress_message = true;
  134. return;
  135. }
  136. std::u16string dialog_title =
  137. GetTitle(web_contents, render_frame_host->GetLastCommittedOrigin());
  138. extensions_client_->OnDialogOpened(web_contents);
  139. AppModalDialogQueue::GetInstance()->AddDialog(new AppModalDialogController(
  140. web_contents, &javascript_dialog_extra_data_, dialog_title, dialog_type,
  141. message_text, default_prompt_text,
  142. ShouldDisplaySuppressCheckbox(extra_data),
  143. false, // is_before_unload_dialog
  144. false, // is_reload
  145. base::BindOnce(&AppModalDialogManager::OnDialogClosed,
  146. base::Unretained(this), web_contents,
  147. std::move(callback))));
  148. }
  149. void AppModalDialogManager::RunBeforeUnloadDialog(
  150. content::WebContents* web_contents,
  151. content::RenderFrameHost* render_frame_host,
  152. bool is_reload,
  153. DialogClosedCallback callback) {
  154. RunBeforeUnloadDialogWithOptions(web_contents, render_frame_host, is_reload,
  155. false, std::move(callback));
  156. }
  157. void AppModalDialogManager::RunBeforeUnloadDialogWithOptions(
  158. content::WebContents* web_contents,
  159. content::RenderFrameHost* render_frame_host,
  160. bool is_reload,
  161. bool is_app,
  162. DialogClosedCallback callback) {
  163. ChromeJavaScriptDialogExtraData* extra_data =
  164. &javascript_dialog_extra_data_[web_contents];
  165. if (extra_data->suppress_javascript_messages_) {
  166. // If a site harassed the user enough for them to put it on mute, then it
  167. // lost its privilege to deny unloading.
  168. std::move(callback).Run(true, std::u16string());
  169. return;
  170. }
  171. // Build the dialog message. We explicitly do _not_ allow the webpage to
  172. // specify the contents of this dialog, as per the current spec
  173. //
  174. // https://html.spec.whatwg.org/#unloading-documents, step 8:
  175. //
  176. // "The message shown to the user is not customizable, but instead
  177. // determined by the user agent. In particular, the actual value of the
  178. // returnValue attribute is ignored."
  179. //
  180. // This message used to be customizable, but it was frequently abused by
  181. // scam websites so the specification was changed.
  182. std::u16string title;
  183. if (is_app) {
  184. title = l10n_util::GetStringUTF16(
  185. is_reload ? IDS_BEFORERELOAD_APP_MESSAGEBOX_TITLE
  186. : IDS_BEFOREUNLOAD_APP_MESSAGEBOX_TITLE);
  187. } else {
  188. title = l10n_util::GetStringUTF16(is_reload
  189. ? IDS_BEFORERELOAD_MESSAGEBOX_TITLE
  190. : IDS_BEFOREUNLOAD_MESSAGEBOX_TITLE);
  191. }
  192. const std::u16string message =
  193. l10n_util::GetStringUTF16(IDS_BEFOREUNLOAD_MESSAGEBOX_MESSAGE);
  194. extensions_client_->OnDialogOpened(web_contents);
  195. AppModalDialogQueue::GetInstance()->AddDialog(new AppModalDialogController(
  196. web_contents, &javascript_dialog_extra_data_, title,
  197. content::JAVASCRIPT_DIALOG_TYPE_CONFIRM, message,
  198. std::u16string(), // default_prompt_text
  199. ShouldDisplaySuppressCheckbox(extra_data),
  200. true, // is_before_unload_dialog
  201. is_reload,
  202. base::BindOnce(&AppModalDialogManager::OnDialogClosed,
  203. base::Unretained(this), web_contents,
  204. std::move(callback))));
  205. }
  206. bool AppModalDialogManager::HandleJavaScriptDialog(
  207. content::WebContents* web_contents,
  208. bool accept,
  209. const std::u16string* prompt_override) {
  210. AppModalDialogQueue* dialog_queue = AppModalDialogQueue::GetInstance();
  211. if (!dialog_queue->HasActiveDialog() ||
  212. dialog_queue->active_dialog()->web_contents() != web_contents) {
  213. return false;
  214. }
  215. AppModalDialogController* dialog =
  216. static_cast<AppModalDialogController*>(dialog_queue->active_dialog());
  217. if (dialog->javascript_dialog_type() ==
  218. content::JavaScriptDialogType::JAVASCRIPT_DIALOG_TYPE_ALERT) {
  219. // Alert dialogs only have one button: OK. Any "handling" of this dialog has
  220. // to be a click on the OK button.
  221. accept = true;
  222. }
  223. if (accept) {
  224. if (prompt_override)
  225. dialog->SetOverridePromptText(*prompt_override);
  226. dialog->view()->AcceptAppModalDialog();
  227. } else {
  228. dialog->view()->CancelAppModalDialog();
  229. }
  230. return true;
  231. }
  232. void AppModalDialogManager::CancelDialogs(content::WebContents* web_contents,
  233. bool reset_state) {
  234. AppModalDialogQueue* queue = AppModalDialogQueue::GetInstance();
  235. for (auto* dialog : *queue) {
  236. if (dialog->web_contents() == web_contents)
  237. dialog->Invalidate();
  238. }
  239. AppModalDialogController* active_dialog = queue->active_dialog();
  240. if (active_dialog && active_dialog->web_contents() == web_contents)
  241. active_dialog->Invalidate();
  242. if (reset_state)
  243. javascript_dialog_extra_data_.erase(web_contents);
  244. }
  245. void AppModalDialogManager::OnDialogClosed(content::WebContents* web_contents,
  246. DialogClosedCallback callback,
  247. bool success,
  248. const std::u16string& user_input) {
  249. // If an extension opened this dialog then the extension may shut down its
  250. // lazy background page after the dialog closes. (Dialogs are closed before
  251. // their WebContents is destroyed so |web_contents| is still valid here.)
  252. extensions_client_->OnDialogClosed(web_contents);
  253. std::move(callback).Run(success, user_input);
  254. }
  255. } // namespace javascript_dialogs