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_app_modal_dialog.h"
6
7#include "chrome/browser/browser_shutdown.h"
8#include "chrome/browser/ui/app_modal_dialogs/native_app_modal_dialog.h"
9#include "content/public/browser/web_contents.h"
10#include "content/public/browser/web_contents_view.h"
11#include "ui/base/text/text_elider.h"
12
13#if defined(USE_AURA)
14#include "ui/aura/root_window.h"
15#endif
16
17using content::JavaScriptDialogManager;
18using content::WebContents;
19
20namespace {
21
22// Control maximum sizes of various texts passed to us from javascript.
23#if defined(OS_POSIX) && !defined(OS_MACOSX)
24// Two-dimensional eliding.  Reformat the text of the message dialog
25// inserting line breaks because otherwise a single long line can overflow
26// the message dialog (and crash/hang the GTK, depending on the version).
27const int kMessageTextMaxRows = 32;
28const int kMessageTextMaxCols = 132;
29const int kDefaultPromptMaxRows = 24;
30const int kDefaultPromptMaxCols = 132;
31void EnforceMaxTextSize(const string16& in_string, string16* out_string) {
32  ui::ElideRectangleString(in_string, kMessageTextMaxRows,
33                           kMessageTextMaxCols, false, out_string);
34}
35void EnforceMaxPromptSize(const string16& in_string, string16* out_string) {
36  ui::ElideRectangleString(in_string, kDefaultPromptMaxRows,
37                           kDefaultPromptMaxCols, false, out_string);
38}
39#else
40// One-dimensional eliding.  Trust the window system to break the string
41// appropriately, but limit its overall length to something reasonable.
42const int kMessageTextMaxSize = 3000;
43const int kDefaultPromptMaxSize = 2000;
44void EnforceMaxTextSize(const string16& in_string, string16* out_string) {
45  ui::ElideString(in_string, kMessageTextMaxSize, out_string);
46}
47void EnforceMaxPromptSize(const string16& in_string, string16* out_string) {
48  ui::ElideString(in_string, kDefaultPromptMaxSize, out_string);
49}
50#endif
51
52}  // namespace
53
54ChromeJavaScriptDialogExtraData::ChromeJavaScriptDialogExtraData()
55    : suppress_javascript_messages_(false) {
56}
57
58JavaScriptAppModalDialog::JavaScriptAppModalDialog(
59    WebContents* web_contents,
60    ExtraDataMap* extra_data_map,
61    const string16& title,
62    content::JavaScriptMessageType javascript_message_type,
63    const string16& message_text,
64    const string16& default_prompt_text,
65    bool display_suppress_checkbox,
66    bool is_before_unload_dialog,
67    bool is_reload,
68    const JavaScriptDialogManager::DialogClosedCallback& callback)
69    : AppModalDialog(web_contents, title),
70      extra_data_map_(extra_data_map),
71      javascript_message_type_(javascript_message_type),
72      display_suppress_checkbox_(display_suppress_checkbox),
73      is_before_unload_dialog_(is_before_unload_dialog),
74      is_reload_(is_reload),
75      callback_(callback),
76      use_override_prompt_text_(false) {
77  EnforceMaxTextSize(message_text, &message_text_);
78  EnforceMaxPromptSize(default_prompt_text, &default_prompt_text_);
79}
80
81JavaScriptAppModalDialog::~JavaScriptAppModalDialog() {
82}
83
84NativeAppModalDialog* JavaScriptAppModalDialog::CreateNativeDialog() {
85  gfx::NativeWindow parent_window =
86      web_contents()->GetView()->GetTopLevelNativeWindow();
87
88#if defined(USE_AURA)
89  if (!parent_window->GetRootWindow()) {
90    // When we are part of a WebContents that isn't actually being displayed on
91    // the screen, we can't actually attach to it.
92    parent_window = NULL;
93  }
94#endif  // defined(USE_AURA)
95
96  return NativeAppModalDialog::CreateNativeJavaScriptPrompt(this,
97                                                            parent_window);
98}
99
100bool JavaScriptAppModalDialog::IsJavaScriptModalDialog() {
101  return true;
102}
103
104void JavaScriptAppModalDialog::Invalidate() {
105  if (!IsValid())
106    return;
107
108  AppModalDialog::Invalidate();
109  callback_.Reset();
110  if (native_dialog())
111    CloseModalDialog();
112}
113
114void JavaScriptAppModalDialog::OnCancel(bool suppress_js_messages) {
115  // If we are shutting down and this is an onbeforeunload dialog, cancel the
116  // shutdown.
117  if (is_before_unload_dialog_)
118    browser_shutdown::SetTryingToQuit(false);
119
120  // We need to do this before WM_DESTROY (WindowClosing()) as any parent frame
121  // will receive its activation messages before this dialog receives
122  // WM_DESTROY. The parent frame would then try to activate any modal dialogs
123  // that were still open in the ModalDialogQueue, which would send activation
124  // back to this one. The framework should be improved to handle this, so this
125  // is a temporary workaround.
126  CompleteDialog();
127
128  NotifyDelegate(false, string16(), suppress_js_messages);
129}
130
131void JavaScriptAppModalDialog::OnAccept(const string16& prompt_text,
132                                        bool suppress_js_messages) {
133  string16 prompt_text_to_use = prompt_text;
134  // This is only for testing.
135  if (use_override_prompt_text_)
136    prompt_text_to_use = override_prompt_text_;
137
138  CompleteDialog();
139  NotifyDelegate(true, prompt_text_to_use, suppress_js_messages);
140}
141
142void JavaScriptAppModalDialog::OnClose() {
143  NotifyDelegate(false, string16(), false);
144}
145
146void JavaScriptAppModalDialog::SetOverridePromptText(
147    const string16& override_prompt_text) {
148  override_prompt_text_ = override_prompt_text;
149  use_override_prompt_text_ = true;
150}
151
152void JavaScriptAppModalDialog::NotifyDelegate(bool success,
153                                              const string16& user_input,
154                                              bool suppress_js_messages) {
155  if (!IsValid())
156    return;
157
158  callback_.Run(success, user_input);
159
160  // The callback_ above may delete web_contents_, thus removing the extra
161  // data from the map owned by ChromeJavaScriptDialogManager. Make sure
162  // to only use the data if still present. http://crbug.com/236476
163  ExtraDataMap::iterator extra_data = extra_data_map_->find(web_contents());
164  if (extra_data != extra_data_map_->end()) {
165    extra_data->second.last_javascript_message_dismissal_ =
166        base::TimeTicks::Now();
167    extra_data->second.suppress_javascript_messages_ = suppress_js_messages;
168  }
169
170  // On Views, we can end up coming through this code path twice :(.
171  // See crbug.com/63732.
172  AppModalDialog::Invalidate();
173}
174