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/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"
22 using content::NativeWebKeyboardEvent;
23 using content::WebContents;
24 using content::WebUIMessageHandler;
25 using ui::WebDialogDelegate;
26 using ui::WebDialogUI;
27 using ui::WebDialogWebContentsDelegate;
29 // Thin bridge that routes notifications to
30 // WebDialogWindowController's member variables.
31 class WebDialogWindowDelegateBridge
32 : public WebDialogDelegate,
33 public WebDialogWebContentsDelegate {
35 // All parameters must be non-NULL/non-nil.
36 WebDialogWindowDelegateBridge(WebDialogWindowController* controller,
37 content::BrowserContext* context,
38 WebDialogDelegate* delegate);
40 virtual ~WebDialogWindowDelegateBridge();
42 // Called when the window is directly closed, e.g. from the close
43 // button or from an accelerator.
44 void WindowControllerClosed();
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; }
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,
74 bool* was_blocked) OVERRIDE;
75 virtual void LoadingStateChanged(content::WebContents* source,
76 bool to_different_document) OVERRIDE;
79 WebDialogWindowController* controller_; // weak
80 WebDialogDelegate* delegate_; // weak, owned by controller_
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);
87 DISALLOW_COPY_AND_ASSIGN(WebDialogWindowDelegateBridge);
90 // ChromeEventProcessingWindow expects its controller to implement the
91 // BrowserCommandExecutor protocol.
92 @interface WebDialogWindowController (InternalAPI) <BrowserCommandExecutor>
94 // BrowserCommandExecutor methods.
95 - (void)executeCommand:(int)command;
101 gfx::NativeWindow ShowWebDialog(gfx::NativeWindow parent,
102 content::BrowserContext* context,
103 WebDialogDelegate* delegate) {
104 return [WebDialogWindowController showWebDialog:delegate
108 } // namespace chrome
110 WebDialogWindowDelegateBridge::WebDialogWindowDelegateBridge(
111 WebDialogWindowController* controller,
112 content::BrowserContext* context,
113 WebDialogDelegate* delegate)
114 : WebDialogWebContentsDelegate(context, new ChromeWebContentsHandler),
115 controller_(controller),
116 delegate_(delegate) {
121 WebDialogWindowDelegateBridge::~WebDialogWindowDelegateBridge() {}
123 void WebDialogWindowDelegateBridge::WindowControllerClosed() {
126 DelegateOnDialogClosed("");
129 bool WebDialogWindowDelegateBridge::DelegateOnDialogClosed(
130 const std::string& json_retval) {
132 WebDialogDelegate* real_delegate = delegate_;
134 real_delegate->OnDialogClosed(json_retval);
140 // WebDialogDelegate definitions.
142 // All of these functions check for NULL first since delegate_ is set
143 // to NULL when the window is closed.
145 ui::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";
150 return ui::MODAL_TYPE_NONE;
153 base::string16 WebDialogWindowDelegateBridge::GetDialogTitle() const {
154 return delegate_ ? delegate_->GetDialogTitle() : base::string16();
157 GURL WebDialogWindowDelegateBridge::GetDialogContentURL() const {
158 return delegate_ ? delegate_->GetDialogContentURL() : GURL();
161 void WebDialogWindowDelegateBridge::GetWebUIMessageHandlers(
162 std::vector<WebUIMessageHandler*>* handlers) const {
164 delegate_->GetWebUIMessageHandlers(handlers);
166 // TODO(akalin): Add this clause in the windows version. Also
167 // make sure that everything expects handlers to be non-NULL and
173 void WebDialogWindowDelegateBridge::GetDialogSize(gfx::Size* size) const {
175 delegate_->GetDialogSize(size);
180 void WebDialogWindowDelegateBridge::GetMinimumDialogSize(
181 gfx::Size* size) const {
183 delegate_->GetMinimumDialogSize(size);
188 std::string WebDialogWindowDelegateBridge::GetDialogArgs() const {
189 return delegate_ ? delegate_->GetDialogArgs() : "";
192 void WebDialogWindowDelegateBridge::OnDialogClosed(
193 const std::string& json_retval) {
195 // [controller_ close] should be called at most once, too.
196 if (DelegateOnDialogClosed(json_retval)) {
202 void WebDialogWindowDelegateBridge::OnCloseContents(WebContents* source,
203 bool* out_close_dialog) {
204 if (out_close_dialog)
205 *out_close_dialog = true;
208 void WebDialogWindowDelegateBridge::CloseContents(WebContents* source) {
209 bool close_dialog = false;
210 OnCloseContents(source, &close_dialog);
212 OnDialogClosed(std::string());
215 content::WebContents* WebDialogWindowDelegateBridge::OpenURLFromTab(
216 content::WebContents* source,
217 const content::OpenURLParams& params) {
218 content::WebContents* new_contents = NULL;
220 delegate_->HandleOpenURLFromTab(source, params, &new_contents)) {
223 return WebDialogWebContentsDelegate::OpenURLFromTab(source, params);
226 void WebDialogWindowDelegateBridge::AddNewContents(
227 content::WebContents* source,
228 content::WebContents* new_contents,
229 WindowOpenDisposition disposition,
230 const gfx::Rect& initial_pos,
233 if (delegate_ && delegate_->HandleAddNewContents(
234 source, new_contents, disposition, initial_pos, user_gesture)) {
237 WebDialogWebContentsDelegate::AddNewContents(
238 source, new_contents, disposition, initial_pos, user_gesture,
242 void WebDialogWindowDelegateBridge::LoadingStateChanged(
243 content::WebContents* source, bool to_different_document) {
245 delegate_->OnLoadingStateChanged(source);
248 void WebDialogWindowDelegateBridge::MoveContents(WebContents* source,
249 const gfx::Rect& pos) {
250 // TODO(akalin): Actually set the window bounds.
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.)
256 void WebDialogWindowDelegateBridge::HandleKeyboardEvent(
257 content::WebContents* source,
258 const NativeWebKeyboardEvent& event) {
259 if (event.skip_in_browser || event.type == NativeWebKeyboardEvent::Char)
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.
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.
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))) {
281 ChromeEventProcessingWindow* event_window =
282 static_cast<ChromeEventProcessingWindow*>([controller_ window]);
283 DCHECK([event_window isKindOfClass:[ChromeEventProcessingWindow class]]);
284 [event_window redispatchKeyEvent:event.os_event];
287 @implementation WebDialogWindowController (InternalAPI)
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 {}
295 @implementation WebDialogWindowController
297 // NOTE(akalin): We'll probably have to add the parentWindow parameter back
298 // in once we implement modal dialogs.
300 + (NSWindow*)showWebDialog:(WebDialogDelegate*)delegate
301 context:(content::BrowserContext*)context {
302 WebDialogWindowController* webDialogWindowController =
303 [[WebDialogWindowController alloc] initWithDelegate:delegate
305 [webDialogWindowController loadDialogContents];
306 [webDialogWindowController showWindow:nil];
307 return [webDialogWindowController window];
310 - (id)initWithDelegate:(WebDialogDelegate*)delegate
311 context:(content::BrowserContext*)context {
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
324 backing:NSBackingStoreBuffered
329 self = [super initWithWindow:window];
333 [window setWindowController:self];
334 [window setDelegate:self];
335 [window setTitle:base::SysUTF16ToNSString(delegate->GetDialogTitle())];
336 [window setMinSize:dialogRect.size];
339 new WebDialogWindowDelegateBridge(self, context, delegate));
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());
349 // This must be done before loading the page; see the comments in
351 WebDialogUI::SetDelegate(webContents_.get(), delegate_.get());
353 webContents_->GetController().LoadURL(
354 delegate_->GetDialogContentURL(),
356 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
359 // TODO(akalin): add accelerator for ESC to close the dialog box.
361 // TODO(akalin): Figure out why implementing (void)cancel:(id)sender
362 // to do the above doesn't work.
365 - (void)windowWillClose:(NSNotification*)notification {
366 delegate_->WindowControllerClosed();