Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / javascript_app_modal_dialog_cocoa.mm
blob7341835b372e3527306b29bcf4a07474317b3d93
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.
5 #include "chrome/browser/ui/cocoa/javascript_app_modal_dialog_cocoa.h"
7 #import <Cocoa/Cocoa.h>
9 #include "base/i18n/rtl.h"
10 #include "base/logging.h"
11 #import "base/mac/foundation_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #import "chrome/browser/chrome_browser_application_mac.h"
14 #include "chrome/browser/ui/app_modal_dialogs/javascript_app_modal_dialog.h"
15 #include "grit/generated_resources.h"
16 #include "grit/ui_strings.h"
17 #include "ui/base/l10n/l10n_util_mac.h"
18 #include "ui/base/ui_base_types.h"
20 // Helper object that receives the notification that the dialog/sheet is
21 // going away. Is responsible for cleaning itself up.
22 @interface JavaScriptAppModalDialogHelper : NSObject<NSAlertDelegate> {
23  @private
24   base::scoped_nsobject<NSAlert> alert_;
25   NSTextField* textField_;  // WEAK; owned by alert_
28 - (NSAlert*)alert;
29 - (NSTextField*)textField;
30 - (void)alertDidEnd:(NSAlert*)alert
31          returnCode:(int)returnCode
32         contextInfo:(void*)contextInfo;
34 @end
36 @implementation JavaScriptAppModalDialogHelper
38 - (NSAlert*)alert {
39   alert_.reset([[NSAlert alloc] init]);
40   return alert_;
43 - (NSTextField*)textField {
44   textField_ = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 22)];
45   [[textField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail];
46   [alert_ setAccessoryView:textField_];
47   [textField_ release];
49   return textField_;
52 // |contextInfo| is the JavaScriptAppModalDialogCocoa that owns us.
53 - (void)alertDidEnd:(NSAlert*)alert
54          returnCode:(int)returnCode
55         contextInfo:(void*)contextInfo {
56   scoped_ptr<JavaScriptAppModalDialogCocoa> native_dialog(
57       reinterpret_cast<JavaScriptAppModalDialogCocoa*>(contextInfo));
58   base::string16 input;
59   if (textField_)
60     input = base::SysNSStringToUTF16([textField_ stringValue]);
61   bool shouldSuppress = false;
62   if ([alert showsSuppressionButton])
63     shouldSuppress = [[alert suppressionButton] state] == NSOnState;
64   switch (returnCode) {
65     case NSAlertFirstButtonReturn:  {  // OK
66       native_dialog->dialog()->OnAccept(input, shouldSuppress);
67       break;
68     }
69     case NSAlertSecondButtonReturn:  {  // Cancel
70       // If the user wants to stay on this page, stop quitting (if a quit is in
71       // progress).
72       if (native_dialog->dialog()->is_before_unload_dialog())
73         chrome_browser_application_mac::CancelTerminate();
74       native_dialog->dialog()->OnCancel(shouldSuppress);
75       break;
76     }
77     case NSRunStoppedResponse: {  // Window was closed underneath us
78       // Need to call OnCancel() because there is some cleanup that needs
79       // to be done.  It won't call back to the javascript since the
80       // JavaScriptAppModalDialog knows that the WebContents was destroyed.
81       native_dialog->dialog()->OnCancel(shouldSuppress);
82       break;
83     }
84     default:  {
85       NOTREACHED();
86     }
87   }
90 @end
92 ////////////////////////////////////////////////////////////////////////////////
93 // JavaScriptAppModalDialogCocoa, public:
95 JavaScriptAppModalDialogCocoa::JavaScriptAppModalDialogCocoa(
96     JavaScriptAppModalDialog* dialog)
97     : dialog_(dialog),
98       helper_(NULL) {
99   // Determine the names of the dialog buttons based on the flags. "Default"
100   // is the OK button. "Other" is the cancel button. We don't use the
101   // "Alternate" button in NSRunAlertPanel.
102   NSString* default_button = l10n_util::GetNSStringWithFixup(IDS_APP_OK);
103   NSString* other_button = l10n_util::GetNSStringWithFixup(IDS_APP_CANCEL);
104   bool text_field = false;
105   bool one_button = false;
106   switch (dialog_->javascript_message_type()) {
107     case content::JAVASCRIPT_MESSAGE_TYPE_ALERT:
108       one_button = true;
109       break;
110     case content::JAVASCRIPT_MESSAGE_TYPE_CONFIRM:
111       if (dialog_->is_before_unload_dialog()) {
112         if (dialog_->is_reload()) {
113           default_button = l10n_util::GetNSStringWithFixup(
114               IDS_BEFORERELOAD_MESSAGEBOX_OK_BUTTON_LABEL);
115           other_button = l10n_util::GetNSStringWithFixup(
116               IDS_BEFORERELOAD_MESSAGEBOX_CANCEL_BUTTON_LABEL);
117         } else {
118           default_button = l10n_util::GetNSStringWithFixup(
119               IDS_BEFOREUNLOAD_MESSAGEBOX_OK_BUTTON_LABEL);
120           other_button = l10n_util::GetNSStringWithFixup(
121               IDS_BEFOREUNLOAD_MESSAGEBOX_CANCEL_BUTTON_LABEL);
122         }
123       }
124       break;
125     case content::JAVASCRIPT_MESSAGE_TYPE_PROMPT:
126       text_field = true;
127       break;
129     default:
130       NOTREACHED();
131   }
133   // Create a helper which will receive the sheet ended selector. It will
134   // delete itself when done. It doesn't need anything passed to its init
135   // as it will get a contextInfo parameter.
136   helper_.reset([[JavaScriptAppModalDialogHelper alloc] init]);
138   // Show the modal dialog.
139   alert_ = [helper_ alert];
140   NSTextField* field = nil;
141   if (text_field) {
142     field = [helper_ textField];
143     [field setStringValue:base::SysUTF16ToNSString(
144         dialog_->default_prompt_text())];
145   }
146   [alert_ setDelegate:helper_];
147   NSString* informative_text =
148       base::SysUTF16ToNSString(dialog_->message_text());
149   [alert_ setInformativeText:informative_text];
150   NSString* message_text =
151       base::SysUTF16ToNSString(dialog_->title());
152   [alert_ setMessageText:message_text];
153   [alert_ addButtonWithTitle:default_button];
154   if (!one_button) {
155     NSButton* other = [alert_ addButtonWithTitle:other_button];
156     [other setKeyEquivalent:@"\e"];
157   }
158   if (dialog_->display_suppress_checkbox()) {
159     [alert_ setShowsSuppressionButton:YES];
160     NSString* suppression_title = l10n_util::GetNSStringWithFixup(
161         IDS_JAVASCRIPT_MESSAGEBOX_SUPPRESS_OPTION);
162     [[alert_ suppressionButton] setTitle:suppression_title];
163   }
165   // Fix RTL dialogs.
166   //
167   // Mac OS X will always display NSAlert strings as LTR. A workaround is to
168   // manually set the text as attributed strings in the implementing
169   // NSTextFields. This is a basic correctness issue.
170   //
171   // In addition, for readability, the overall alignment is set based on the
172   // directionality of the first strongly-directional character.
173   //
174   // If the dialog fields are selectable then they will scramble when clicked.
175   // Therefore, selectability is disabled.
176   //
177   // See http://crbug.com/70806 for more details.
179   bool message_has_rtl =
180       base::i18n::StringContainsStrongRTLChars(dialog_->title());
181   bool informative_has_rtl =
182       base::i18n::StringContainsStrongRTLChars(dialog_->message_text());
184   NSTextField* message_text_field = nil;
185   NSTextField* informative_text_field = nil;
186   if (message_has_rtl || informative_has_rtl) {
187     // Force layout of the dialog. NSAlert leaves its dialog alone once laid
188     // out; if this is not done then all the modifications that are to come will
189     // be un-done when the dialog is finally displayed.
190     [alert_ layout];
192     // Locate the NSTextFields that implement the text display. These are
193     // actually available as the ivars |_messageField| and |_informationField|
194     // of the NSAlert, but it is safer (and more forward-compatible) to search
195     // for them in the subviews.
196     for (NSView* view in [[[alert_ window] contentView] subviews]) {
197       NSTextField* text_field = base::mac::ObjCCast<NSTextField>(view);
198       if ([[text_field stringValue] isEqualTo:message_text])
199         message_text_field = text_field;
200       else if ([[text_field stringValue] isEqualTo:informative_text])
201         informative_text_field = text_field;
202     }
204     // This may fail in future OS releases, but it will still work for shipped
205     // versions of Chromium.
206     DCHECK(message_text_field);
207     DCHECK(informative_text_field);
208   }
210   if (message_has_rtl && message_text_field) {
211     base::scoped_nsobject<NSMutableParagraphStyle> alignment(
212         [[NSParagraphStyle defaultParagraphStyle] mutableCopy]);
213     [alignment setAlignment:NSRightTextAlignment];
215     NSDictionary* alignment_attributes =
216         @{ NSParagraphStyleAttributeName : alignment };
217     base::scoped_nsobject<NSAttributedString> attr_string(
218         [[NSAttributedString alloc] initWithString:message_text
219                                         attributes:alignment_attributes]);
221     [message_text_field setAttributedStringValue:attr_string];
222     [message_text_field setSelectable:NO];
223   }
225   if (informative_has_rtl && informative_text_field) {
226     base::i18n::TextDirection direction =
227         base::i18n::GetFirstStrongCharacterDirection(dialog_->message_text());
228     base::scoped_nsobject<NSMutableParagraphStyle> alignment(
229         [[NSParagraphStyle defaultParagraphStyle] mutableCopy]);
230     [alignment setAlignment:
231         (direction == base::i18n::RIGHT_TO_LEFT) ? NSRightTextAlignment
232                                                  : NSLeftTextAlignment];
234     NSDictionary* alignment_attributes =
235         @{ NSParagraphStyleAttributeName : alignment };
236     base::scoped_nsobject<NSAttributedString> attr_string(
237         [[NSAttributedString alloc] initWithString:informative_text
238                                         attributes:alignment_attributes]);
240     [informative_text_field setAttributedStringValue:attr_string];
241     [informative_text_field setSelectable:NO];
242   }
245 JavaScriptAppModalDialogCocoa::~JavaScriptAppModalDialogCocoa() {
248 ////////////////////////////////////////////////////////////////////////////////
249 // JavaScriptAppModalDialogCocoa, NativeAppModalDialog implementation:
251 int JavaScriptAppModalDialogCocoa::GetAppModalDialogButtons() const {
252   // From the above, it is the case that if there is 1 button, it is always the
253   // OK button.  The second button, if it exists, is always the Cancel button.
254   int num_buttons = [[alert_ buttons] count];
255   switch (num_buttons) {
256     case 1:
257       return ui::DIALOG_BUTTON_OK;
258     case 2:
259       return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
260     default:
261       NOTREACHED();
262       return 0;
263   }
266 void JavaScriptAppModalDialogCocoa::ShowAppModalDialog() {
267   [alert_
268       beginSheetModalForWindow:nil  // nil here makes it app-modal
269                  modalDelegate:helper_.get()
270                 didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
271                    contextInfo:this];
273   if ([alert_ accessoryView])
274     [[alert_ window] makeFirstResponder:[alert_ accessoryView]];
277 void JavaScriptAppModalDialogCocoa::ActivateAppModalDialog() {
280 void JavaScriptAppModalDialogCocoa::CloseAppModalDialog() {
281   DCHECK([alert_ isKindOfClass:[NSAlert class]]);
283   // Note: the call below will delete |this|,
284   // see JavaScriptAppModalDialogHelper's alertDidEnd.
285   [NSApp endSheet:[alert_ window]];
288 void JavaScriptAppModalDialogCocoa::AcceptAppModalDialog() {
289   NSButton* first = [[alert_ buttons] objectAtIndex:0];
290   [first performClick:nil];
293 void JavaScriptAppModalDialogCocoa::CancelAppModalDialog() {
294   DCHECK([[alert_ buttons] count] >= 2);
295   NSButton* second = [[alert_ buttons] objectAtIndex:1];
296   [second performClick:nil];
299 ////////////////////////////////////////////////////////////////////////////////
300 // NativeAppModalDialog, public:
302 // static
303 NativeAppModalDialog* NativeAppModalDialog::CreateNativeJavaScriptPrompt(
304     JavaScriptAppModalDialog* dialog,
305     gfx::NativeWindow parent_window) {
306   return new JavaScriptAppModalDialogCocoa(dialog);