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