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