1 // Copyright (c) 2011 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/cocoa/js_modal_dialog_cocoa.h" 6 7 #import <Cocoa/Cocoa.h> 8 9 #include "base/logging.h" 10 #import "base/mac/cocoa_protocols.h" 11 #include "base/sys_string_conversions.h" 12 #import "chrome/browser/chrome_browser_application_mac.h" 13 #include "chrome/browser/ui/app_modal_dialogs/js_modal_dialog.h" 14 #include "grit/app_strings.h" 15 #include "grit/generated_resources.h" 16 #include "ui/base/l10n/l10n_util_mac.h" 17 #include "ui/base/message_box_flags.h" 18 19 // Helper object that receives the notification that the dialog/sheet is 20 // going away. Is responsible for cleaning itself up. 21 @interface JavaScriptAppModalDialogHelper : NSObject<NSAlertDelegate> { 22 @private 23 NSAlert* alert_; 24 NSTextField* textField_; // WEAK; owned by alert_ 25 } 26 27 - (NSAlert*)alert; 28 - (NSTextField*)textField; 29 - (void)alertDidEnd:(NSAlert *)alert 30 returnCode:(int)returnCode 31 contextInfo:(void*)contextInfo; 32 33 @end 34 35 @implementation JavaScriptAppModalDialogHelper 36 37 - (NSAlert*)alert { 38 alert_ = [[NSAlert alloc] init]; 39 return alert_; 40 } 41 42 - (NSTextField*)textField { 43 textField_ = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 22)]; 44 [[textField_ cell] setLineBreakMode:NSLineBreakByTruncatingTail]; 45 [alert_ setAccessoryView:textField_]; 46 [textField_ release]; 47 48 return textField_; 49 } 50 51 - (void)dealloc { 52 [alert_ release]; 53 [super dealloc]; 54 } 55 56 // |contextInfo| is the JSModalDialogCocoa that owns us. 57 - (void)alertDidEnd:(NSAlert*)alert 58 returnCode:(int)returnCode 59 contextInfo:(void*)contextInfo { 60 scoped_ptr<JSModalDialogCocoa> native_dialog( 61 reinterpret_cast<JSModalDialogCocoa*>(contextInfo)); 62 std::wstring input; 63 if (textField_) 64 input = base::SysNSStringToWide([textField_ stringValue]); 65 bool shouldSuppress = false; 66 if ([alert showsSuppressionButton]) 67 shouldSuppress = [[alert suppressionButton] state] == NSOnState; 68 switch (returnCode) { 69 case NSAlertFirstButtonReturn: { // OK 70 native_dialog->dialog()->OnAccept(input, shouldSuppress); 71 break; 72 } 73 case NSAlertSecondButtonReturn: { // Cancel 74 // If the user wants to stay on this page, stop quitting (if a quit is in 75 // progress). 76 if (native_dialog->dialog()->is_before_unload_dialog()) 77 chrome_browser_application_mac::CancelTerminate(); 78 native_dialog->dialog()->OnCancel(shouldSuppress); 79 break; 80 } 81 case NSRunStoppedResponse: { // Window was closed underneath us 82 // Need to call OnCancel() because there is some cleanup that needs 83 // to be done. It won't call back to the javascript since the 84 // JavaScriptAppModalDialog knows that the TabContents was destroyed. 85 native_dialog->dialog()->OnCancel(shouldSuppress); 86 break; 87 } 88 default: { 89 NOTREACHED(); 90 } 91 } 92 } 93 @end 94 95 //////////////////////////////////////////////////////////////////////////////// 96 // JSModalDialogCocoa, public: 97 98 JSModalDialogCocoa::JSModalDialogCocoa(JavaScriptAppModalDialog* dialog) 99 : dialog_(dialog), 100 helper_(NULL) { 101 // Determine the names of the dialog buttons based on the flags. "Default" 102 // is the OK button. "Other" is the cancel button. We don't use the 103 // "Alternate" button in NSRunAlertPanel. 104 NSString* default_button = l10n_util::GetNSStringWithFixup(IDS_APP_OK); 105 NSString* other_button = l10n_util::GetNSStringWithFixup(IDS_APP_CANCEL); 106 bool text_field = false; 107 bool one_button = false; 108 switch (dialog_->dialog_flags()) { 109 case ui::MessageBoxFlags::kIsJavascriptAlert: 110 one_button = true; 111 break; 112 case ui::MessageBoxFlags::kIsJavascriptConfirm: 113 if (dialog_->is_before_unload_dialog()) { 114 default_button = l10n_util::GetNSStringWithFixup( 115 IDS_BEFOREUNLOAD_MESSAGEBOX_OK_BUTTON_LABEL); 116 other_button = l10n_util::GetNSStringWithFixup( 117 IDS_BEFOREUNLOAD_MESSAGEBOX_CANCEL_BUTTON_LABEL); 118 } 119 break; 120 case ui::MessageBoxFlags::kIsJavascriptPrompt: 121 text_field = true; 122 break; 123 124 default: 125 NOTREACHED(); 126 } 127 128 // Create a helper which will receive the sheet ended selector. It will 129 // delete itself when done. It doesn't need anything passed to its init 130 // as it will get a contextInfo parameter. 131 helper_.reset([[JavaScriptAppModalDialogHelper alloc] init]); 132 133 // Show the modal dialog. 134 alert_ = [helper_ alert]; 135 NSTextField* field = nil; 136 if (text_field) { 137 field = [helper_ textField]; 138 [field setStringValue:base::SysWideToNSString( 139 dialog_->default_prompt_text())]; 140 } 141 [alert_ setDelegate:helper_]; 142 [alert_ setInformativeText:base::SysWideToNSString(dialog_->message_text())]; 143 [alert_ setMessageText:base::SysWideToNSString(dialog_->title())]; 144 [alert_ addButtonWithTitle:default_button]; 145 if (!one_button) { 146 NSButton* other = [alert_ addButtonWithTitle:other_button]; 147 [other setKeyEquivalent:@"\e"]; 148 } 149 if (dialog_->display_suppress_checkbox()) { 150 [alert_ setShowsSuppressionButton:YES]; 151 NSString* suppression_title = l10n_util::GetNSStringWithFixup( 152 IDS_JAVASCRIPT_MESSAGEBOX_SUPPRESS_OPTION); 153 [[alert_ suppressionButton] setTitle:suppression_title]; 154 } 155 } 156 157 JSModalDialogCocoa::~JSModalDialogCocoa() { 158 } 159 160 //////////////////////////////////////////////////////////////////////////////// 161 // JSModalDialogCocoa, NativeAppModalDialog implementation: 162 163 int JSModalDialogCocoa::GetAppModalDialogButtons() const { 164 // From the above, it is the case that if there is 1 button, it is always the 165 // OK button. The second button, if it exists, is always the Cancel button. 166 int num_buttons = [[alert_ buttons] count]; 167 switch (num_buttons) { 168 case 1: 169 return ui::MessageBoxFlags::DIALOGBUTTON_OK; 170 case 2: 171 return ui::MessageBoxFlags::DIALOGBUTTON_OK | 172 ui::MessageBoxFlags::DIALOGBUTTON_CANCEL; 173 default: 174 NOTREACHED(); 175 return 0; 176 } 177 } 178 179 void JSModalDialogCocoa::ShowAppModalDialog() { 180 [alert_ 181 beginSheetModalForWindow:nil // nil here makes it app-modal 182 modalDelegate:helper_.get() 183 didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) 184 contextInfo:this]; 185 186 if ([alert_ accessoryView]) 187 [[alert_ window] makeFirstResponder:[alert_ accessoryView]]; 188 } 189 190 void JSModalDialogCocoa::ActivateAppModalDialog() { 191 } 192 193 void JSModalDialogCocoa::CloseAppModalDialog() { 194 DCHECK([alert_ isKindOfClass:[NSAlert class]]); 195 196 // Note: the call below will delete |this|, 197 // see JavaScriptAppModalDialogHelper's alertDidEnd. 198 [NSApp endSheet:[alert_ window]]; 199 } 200 201 void JSModalDialogCocoa::AcceptAppModalDialog() { 202 NSButton* first = [[alert_ buttons] objectAtIndex:0]; 203 [first performClick:nil]; 204 } 205 206 void JSModalDialogCocoa::CancelAppModalDialog() { 207 DCHECK([[alert_ buttons] count] >= 2); 208 NSButton* second = [[alert_ buttons] objectAtIndex:1]; 209 [second performClick:nil]; 210 } 211 212 //////////////////////////////////////////////////////////////////////////////// 213 // NativeAppModalDialog, public: 214 215 // static 216 NativeAppModalDialog* NativeAppModalDialog::CreateNativeJavaScriptPrompt( 217 JavaScriptAppModalDialog* dialog, 218 gfx::NativeWindow parent_window) { 219 return new JSModalDialogCocoa(dialog); 220 } 221