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