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#import "chrome/browser/ui/cocoa/web_dialog_window_controller.h" 6 7#include "base/logging.h" 8#include "base/mac/scoped_nsobject.h" 9#include "base/strings/sys_string_conversions.h" 10#import "chrome/browser/ui/browser_dialogs.h" 11#import "chrome/browser/ui/cocoa/browser_command_executor.h" 12#import "chrome/browser/ui/cocoa/chrome_event_processing_window.h" 13#include "chrome/browser/ui/webui/chrome_web_contents_handler.h" 14#include "content/public/browser/native_web_keyboard_event.h" 15#include "content/public/browser/web_contents.h" 16#include "content/public/browser/web_ui_message_handler.h" 17#include "ui/events/keycodes/keyboard_codes.h" 18#include "ui/gfx/size.h" 19#include "ui/web_dialogs/web_dialog_delegate.h" 20#include "ui/web_dialogs/web_dialog_web_contents_delegate.h" 21 22using content::NativeWebKeyboardEvent; 23using content::WebContents; 24using content::WebUIMessageHandler; 25using ui::WebDialogDelegate; 26using ui::WebDialogUI; 27using ui::WebDialogWebContentsDelegate; 28 29// Thin bridge that routes notifications to 30// WebDialogWindowController's member variables. 31class WebDialogWindowDelegateBridge 32 : public WebDialogDelegate, 33 public WebDialogWebContentsDelegate { 34public: 35 // All parameters must be non-NULL/non-nil. 36 WebDialogWindowDelegateBridge(WebDialogWindowController* controller, 37 content::BrowserContext* context, 38 WebDialogDelegate* delegate); 39 40 virtual ~WebDialogWindowDelegateBridge(); 41 42 // Called when the window is directly closed, e.g. from the close 43 // button or from an accelerator. 44 void WindowControllerClosed(); 45 46 // WebDialogDelegate declarations. 47 virtual ui::ModalType GetDialogModalType() const OVERRIDE; 48 virtual base::string16 GetDialogTitle() const OVERRIDE; 49 virtual GURL GetDialogContentURL() const OVERRIDE; 50 virtual void GetWebUIMessageHandlers( 51 std::vector<WebUIMessageHandler*>* handlers) const OVERRIDE; 52 virtual void GetDialogSize(gfx::Size* size) const OVERRIDE; 53 virtual void GetMinimumDialogSize(gfx::Size* size) const OVERRIDE; 54 virtual std::string GetDialogArgs() const OVERRIDE; 55 virtual void OnDialogClosed(const std::string& json_retval) OVERRIDE; 56 virtual void OnCloseContents(WebContents* source, 57 bool* out_close_dialog) OVERRIDE; 58 virtual bool ShouldShowDialogTitle() const OVERRIDE { return true; } 59 60 // WebDialogWebContentsDelegate declarations. 61 virtual void MoveContents(WebContents* source, const gfx::Rect& pos) OVERRIDE; 62 virtual void HandleKeyboardEvent( 63 content::WebContents* source, 64 const NativeWebKeyboardEvent& event) OVERRIDE; 65 virtual void CloseContents(WebContents* source) OVERRIDE; 66 virtual content::WebContents* OpenURLFromTab( 67 content::WebContents* source, 68 const content::OpenURLParams& params) OVERRIDE; 69 virtual void AddNewContents(content::WebContents* source, 70 content::WebContents* new_contents, 71 WindowOpenDisposition disposition, 72 const gfx::Rect& initial_pos, 73 bool user_gesture, 74 bool* was_blocked) OVERRIDE; 75 virtual void LoadingStateChanged(content::WebContents* source, 76 bool to_different_document) OVERRIDE; 77 78private: 79 WebDialogWindowController* controller_; // weak 80 WebDialogDelegate* delegate_; // weak, owned by controller_ 81 82 // Calls delegate_'s OnDialogClosed() exactly once, nulling it out afterwards 83 // so that no other WebDialogDelegate calls are sent to it. Returns whether or 84 // not the OnDialogClosed() was actually called on the delegate. 85 bool DelegateOnDialogClosed(const std::string& json_retval); 86 87 DISALLOW_COPY_AND_ASSIGN(WebDialogWindowDelegateBridge); 88}; 89 90// ChromeEventProcessingWindow expects its controller to implement the 91// BrowserCommandExecutor protocol. 92@interface WebDialogWindowController (InternalAPI) <BrowserCommandExecutor> 93 94// BrowserCommandExecutor methods. 95- (void)executeCommand:(int)command; 96 97@end 98 99namespace chrome { 100 101gfx::NativeWindow ShowWebDialog(gfx::NativeWindow parent, 102 content::BrowserContext* context, 103 WebDialogDelegate* delegate) { 104 return [WebDialogWindowController showWebDialog:delegate 105 context:context]; 106} 107 108} // namespace chrome 109 110WebDialogWindowDelegateBridge::WebDialogWindowDelegateBridge( 111 WebDialogWindowController* controller, 112 content::BrowserContext* context, 113 WebDialogDelegate* delegate) 114 : WebDialogWebContentsDelegate(context, new ChromeWebContentsHandler), 115 controller_(controller), 116 delegate_(delegate) { 117 DCHECK(controller_); 118 DCHECK(delegate_); 119} 120 121WebDialogWindowDelegateBridge::~WebDialogWindowDelegateBridge() {} 122 123void WebDialogWindowDelegateBridge::WindowControllerClosed() { 124 Detach(); 125 controller_ = nil; 126 DelegateOnDialogClosed(""); 127} 128 129bool WebDialogWindowDelegateBridge::DelegateOnDialogClosed( 130 const std::string& json_retval) { 131 if (delegate_) { 132 WebDialogDelegate* real_delegate = delegate_; 133 delegate_ = NULL; 134 real_delegate->OnDialogClosed(json_retval); 135 return true; 136 } 137 return false; 138} 139 140// WebDialogDelegate definitions. 141 142// All of these functions check for NULL first since delegate_ is set 143// to NULL when the window is closed. 144 145ui::ModalType WebDialogWindowDelegateBridge::GetDialogModalType() const { 146 // TODO(akalin): Support modal dialog boxes. 147 if (delegate_ && delegate_->GetDialogModalType() != ui::MODAL_TYPE_NONE) { 148 LOG(WARNING) << "Modal Web dialogs are not supported yet"; 149 } 150 return ui::MODAL_TYPE_NONE; 151} 152 153base::string16 WebDialogWindowDelegateBridge::GetDialogTitle() const { 154 return delegate_ ? delegate_->GetDialogTitle() : base::string16(); 155} 156 157GURL WebDialogWindowDelegateBridge::GetDialogContentURL() const { 158 return delegate_ ? delegate_->GetDialogContentURL() : GURL(); 159} 160 161void WebDialogWindowDelegateBridge::GetWebUIMessageHandlers( 162 std::vector<WebUIMessageHandler*>* handlers) const { 163 if (delegate_) { 164 delegate_->GetWebUIMessageHandlers(handlers); 165 } else { 166 // TODO(akalin): Add this clause in the windows version. Also 167 // make sure that everything expects handlers to be non-NULL and 168 // document it. 169 handlers->clear(); 170 } 171} 172 173void WebDialogWindowDelegateBridge::GetDialogSize(gfx::Size* size) const { 174 if (delegate_) 175 delegate_->GetDialogSize(size); 176 else 177 *size = gfx::Size(); 178} 179 180void WebDialogWindowDelegateBridge::GetMinimumDialogSize( 181 gfx::Size* size) const { 182 if (delegate_) 183 delegate_->GetMinimumDialogSize(size); 184 else 185 *size = gfx::Size(); 186} 187 188std::string WebDialogWindowDelegateBridge::GetDialogArgs() const { 189 return delegate_ ? delegate_->GetDialogArgs() : ""; 190} 191 192void WebDialogWindowDelegateBridge::OnDialogClosed( 193 const std::string& json_retval) { 194 Detach(); 195 // [controller_ close] should be called at most once, too. 196 if (DelegateOnDialogClosed(json_retval)) { 197 [controller_ close]; 198 } 199 controller_ = nil; 200} 201 202void WebDialogWindowDelegateBridge::OnCloseContents(WebContents* source, 203 bool* out_close_dialog) { 204 if (out_close_dialog) 205 *out_close_dialog = true; 206} 207 208void WebDialogWindowDelegateBridge::CloseContents(WebContents* source) { 209 bool close_dialog = false; 210 OnCloseContents(source, &close_dialog); 211 if (close_dialog) 212 OnDialogClosed(std::string()); 213} 214 215content::WebContents* WebDialogWindowDelegateBridge::OpenURLFromTab( 216 content::WebContents* source, 217 const content::OpenURLParams& params) { 218 content::WebContents* new_contents = NULL; 219 if (delegate_ && 220 delegate_->HandleOpenURLFromTab(source, params, &new_contents)) { 221 return new_contents; 222 } 223 return WebDialogWebContentsDelegate::OpenURLFromTab(source, params); 224} 225 226void WebDialogWindowDelegateBridge::AddNewContents( 227 content::WebContents* source, 228 content::WebContents* new_contents, 229 WindowOpenDisposition disposition, 230 const gfx::Rect& initial_pos, 231 bool user_gesture, 232 bool* was_blocked) { 233 if (delegate_ && delegate_->HandleAddNewContents( 234 source, new_contents, disposition, initial_pos, user_gesture)) { 235 return; 236 } 237 WebDialogWebContentsDelegate::AddNewContents( 238 source, new_contents, disposition, initial_pos, user_gesture, 239 was_blocked); 240} 241 242void WebDialogWindowDelegateBridge::LoadingStateChanged( 243 content::WebContents* source, bool to_different_document) { 244 if (delegate_) 245 delegate_->OnLoadingStateChanged(source); 246} 247 248void WebDialogWindowDelegateBridge::MoveContents(WebContents* source, 249 const gfx::Rect& pos) { 250 // TODO(akalin): Actually set the window bounds. 251} 252 253// A simplified version of BrowserWindowCocoa::HandleKeyboardEvent(). 254// We don't handle global keyboard shortcuts here, but that's fine since 255// they're all browser-specific. (This may change in the future.) 256void WebDialogWindowDelegateBridge::HandleKeyboardEvent( 257 content::WebContents* source, 258 const NativeWebKeyboardEvent& event) { 259 if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char) 260 return; 261 262 // Close ourselves if the user hits Esc or Command-. . The normal 263 // way to do this is to implement (void)cancel:(int)sender, but 264 // since we handle keyboard events ourselves we can't do that. 265 // 266 // According to experiments, hitting Esc works regardless of the 267 // presence of other modifiers (as long as it's not an app-level 268 // shortcut, e.g. Commmand-Esc for Front Row) but no other modifiers 269 // can be present for Command-. to work. 270 // 271 // TODO(thakis): It would be nice to get cancel: to work somehow. 272 // Bug: http://code.google.com/p/chromium/issues/detail?id=32828 . 273 if (event.type == NativeWebKeyboardEvent::RawKeyDown && 274 ((event.windowsKeyCode == ui::VKEY_ESCAPE) || 275 (event.windowsKeyCode == ui::VKEY_OEM_PERIOD && 276 event.modifiers == NativeWebKeyboardEvent::MetaKey))) { 277 [controller_ close]; 278 return; 279 } 280 281 ChromeEventProcessingWindow* event_window = 282 static_cast<ChromeEventProcessingWindow*>([controller_ window]); 283 DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]); 284 [event_window redispatchKeyEvent:event.os_event]; 285} 286 287@implementation WebDialogWindowController (InternalAPI) 288 289// This gets called whenever a chrome-specific keyboard shortcut is performed 290// in the Web dialog window. We simply swallow all those events. 291- (void)executeCommand:(int)command {} 292 293@end 294 295@implementation WebDialogWindowController 296 297// NOTE(akalin): We'll probably have to add the parentWindow parameter back 298// in once we implement modal dialogs. 299 300+ (NSWindow*)showWebDialog:(WebDialogDelegate*)delegate 301 context:(content::BrowserContext*)context { 302 WebDialogWindowController* webDialogWindowController = 303 [[WebDialogWindowController alloc] initWithDelegate:delegate 304 context:context]; 305 [webDialogWindowController loadDialogContents]; 306 [webDialogWindowController showWindow:nil]; 307 return [webDialogWindowController window]; 308} 309 310- (id)initWithDelegate:(WebDialogDelegate*)delegate 311 context:(content::BrowserContext*)context { 312 DCHECK(delegate); 313 DCHECK(context); 314 315 gfx::Size dialogSize; 316 delegate->GetDialogSize(&dialogSize); 317 NSRect dialogRect = NSMakeRect(0, 0, dialogSize.width(), dialogSize.height()); 318 NSUInteger style = NSTitledWindowMask | NSClosableWindowMask | 319 NSResizableWindowMask; 320 base::scoped_nsobject<ChromeEventProcessingWindow> window( 321 [[ChromeEventProcessingWindow alloc] 322 initWithContentRect:dialogRect 323 styleMask:style 324 backing:NSBackingStoreBuffered 325 defer:YES]); 326 if (!window.get()) { 327 return nil; 328 } 329 self = [super initWithWindow:window]; 330 if (!self) { 331 return nil; 332 } 333 [window setWindowController:self]; 334 [window setDelegate:self]; 335 [window setTitle:base::SysUTF16ToNSString(delegate->GetDialogTitle())]; 336 [window setMinSize:dialogRect.size]; 337 [window center]; 338 delegate_.reset( 339 new WebDialogWindowDelegateBridge(self, context, delegate)); 340 return self; 341} 342 343- (void)loadDialogContents { 344 webContents_.reset(WebContents::Create( 345 WebContents::CreateParams(delegate_->browser_context()))); 346 [[self window] setContentView:webContents_->GetNativeView()]; 347 webContents_->SetDelegate(delegate_.get()); 348 349 // This must be done before loading the page; see the comments in 350 // WebDialogUI. 351 WebDialogUI::SetDelegate(webContents_.get(), delegate_.get()); 352 353 webContents_->GetController().LoadURL( 354 delegate_->GetDialogContentURL(), 355 content::Referrer(), 356 ui::PAGE_TRANSITION_AUTO_TOPLEVEL, 357 std::string()); 358 359 // TODO(akalin): add accelerator for ESC to close the dialog box. 360 // 361 // TODO(akalin): Figure out why implementing (void)cancel:(id)sender 362 // to do the above doesn't work. 363} 364 365- (void)windowWillClose:(NSNotification*)notification { 366 delegate_->WindowControllerClosed(); 367 [self autorelease]; 368} 369 370@end 371