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 #include "content/shell/shell.h" 6 7 #include <algorithm> 8 9 #include "base/logging.h" 10 #import "base/mac/scoped_nsobject.h" 11 #include "base/strings/string_piece.h" 12 #include "base/strings/sys_string_conversions.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_contents_view.h" 16 #include "content/shell/app/resource.h" 17 #import "ui/base/cocoa/underlay_opengl_hosting_window.h" 18 #include "url/gurl.h" 19 20 #if !defined(MAC_OS_X_VERSION_10_7) || \ 21 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 22 23 enum { 24 NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7, 25 NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8 26 }; 27 28 #endif // MAC_OS_X_VERSION_10_7 29 30 // Receives notification that the window is closing so that it can start the 31 // tear-down process. Is responsible for deleting itself when done. 32 @interface ContentShellWindowDelegate : NSObject<NSWindowDelegate> { 33 @private 34 content::Shell* shell_; 35 } 36 - (id)initWithShell:(content::Shell*)shell; 37 @end 38 39 @implementation ContentShellWindowDelegate 40 41 - (id)initWithShell:(content::Shell*)shell { 42 if ((self = [super init])) { 43 shell_ = shell; 44 } 45 return self; 46 } 47 48 // Called when the window is about to close. Perform the self-destruction 49 // sequence by getting rid of the shell and removing it and the window from 50 // the various global lists. By returning YES, we allow the window to be 51 // removed from the screen. 52 - (BOOL)windowShouldClose:(id)window { 53 [window autorelease]; 54 delete shell_; 55 [self release]; 56 57 return YES; 58 } 59 60 - (void)performAction:(id)sender { 61 shell_->ActionPerformed([sender tag]); 62 } 63 64 - (void)takeURLStringValueFrom:(id)sender { 65 shell_->URLEntered(base::SysNSStringToUTF8([sender stringValue])); 66 } 67 68 @end 69 70 @interface CrShellWindow : UnderlayOpenGLHostingWindow { 71 @private 72 content::Shell* shell_; 73 } 74 - (void)setShell:(content::Shell*)shell; 75 - (void)showDevTools:(id)sender; 76 @end 77 78 @implementation CrShellWindow 79 80 - (void)setShell:(content::Shell*)shell { 81 shell_ = shell; 82 } 83 84 - (void)showDevTools:(id)sender { 85 shell_->ShowDevTools(); 86 } 87 88 @end 89 90 namespace { 91 92 NSString* kWindowTitle = @"Content Shell"; 93 94 // Layout constants (in view coordinates) 95 const CGFloat kButtonWidth = 72; 96 const CGFloat kURLBarHeight = 24; 97 98 // The minimum size of the window's content (in view coordinates) 99 const CGFloat kMinimumWindowWidth = 400; 100 const CGFloat kMinimumWindowHeight = 300; 101 102 void MakeShellButton(NSRect* rect, 103 NSString* title, 104 NSView* parent, 105 int control, 106 NSView* target, 107 NSString* key, 108 NSUInteger modifier) { 109 base::scoped_nsobject<NSButton> button( 110 [[NSButton alloc] initWithFrame:*rect]); 111 [button setTitle:title]; 112 [button setBezelStyle:NSSmallSquareBezelStyle]; 113 [button setAutoresizingMask:(NSViewMaxXMargin | NSViewMinYMargin)]; 114 [button setTarget:target]; 115 [button setAction:@selector(performAction:)]; 116 [button setTag:control]; 117 [button setKeyEquivalent:key]; 118 [button setKeyEquivalentModifierMask:modifier]; 119 [parent addSubview:button]; 120 rect->origin.x += kButtonWidth; 121 } 122 123 } // namespace 124 125 namespace content { 126 127 void Shell::PlatformInitialize(const gfx::Size& default_window_size) { 128 } 129 130 void Shell::PlatformCleanUp() { 131 } 132 133 void Shell::PlatformEnableUIControl(UIControl control, bool is_enabled) { 134 if (headless_) 135 return; 136 137 int id; 138 switch (control) { 139 case BACK_BUTTON: 140 id = IDC_NAV_BACK; 141 break; 142 case FORWARD_BUTTON: 143 id = IDC_NAV_FORWARD; 144 break; 145 case STOP_BUTTON: 146 id = IDC_NAV_STOP; 147 break; 148 default: 149 NOTREACHED() << "Unknown UI control"; 150 return; 151 } 152 [[[window_ contentView] viewWithTag:id] setEnabled:is_enabled]; 153 } 154 155 void Shell::PlatformSetAddressBarURL(const GURL& url) { 156 if (headless_) 157 return; 158 159 NSString* url_string = base::SysUTF8ToNSString(url.spec()); 160 [url_edit_view_ setStringValue:url_string]; 161 } 162 163 void Shell::PlatformSetIsLoading(bool loading) { 164 } 165 166 void Shell::PlatformCreateWindow(int width, int height) { 167 if (headless_) { 168 content_width_ = width; 169 content_height_ = height; 170 return; 171 } 172 173 NSRect initial_window_bounds = 174 NSMakeRect(0, 0, width, height + kURLBarHeight); 175 NSRect content_rect = initial_window_bounds; 176 NSUInteger style_mask = NSTitledWindowMask | 177 NSClosableWindowMask | 178 NSMiniaturizableWindowMask | 179 NSResizableWindowMask; 180 CrShellWindow* window = 181 [[CrShellWindow alloc] initWithContentRect:content_rect 182 styleMask:style_mask 183 backing:NSBackingStoreBuffered 184 defer:NO]; 185 window_ = window; 186 [window setShell:this]; 187 [window_ setTitle:kWindowTitle]; 188 NSView* content = [window_ contentView]; 189 190 // If the window is allowed to get too small, it will wreck the view bindings. 191 NSSize min_size = NSMakeSize(kMinimumWindowWidth, kMinimumWindowHeight); 192 min_size = [content convertSize:min_size toView:nil]; 193 // Note that this takes window coordinates. 194 [window_ setContentMinSize:min_size]; 195 196 // Set the shell window to participate in Lion Fullscreen mode. Set 197 // Setting this flag has no effect on Snow Leopard or earlier. 198 NSUInteger collectionBehavior = [window_ collectionBehavior]; 199 collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; 200 [window_ setCollectionBehavior:collectionBehavior]; 201 202 // Rely on the window delegate to clean us up rather than immediately 203 // releasing when the window gets closed. We use the delegate to do 204 // everything from the autorelease pool so the shell isn't on the stack 205 // during cleanup (ie, a window close from javascript). 206 [window_ setReleasedWhenClosed:NO]; 207 208 // Create a window delegate to watch for when it's asked to go away. It will 209 // clean itself up so we don't need to hold a reference. 210 ContentShellWindowDelegate* delegate = 211 [[ContentShellWindowDelegate alloc] initWithShell:this]; 212 [window_ setDelegate:delegate]; 213 214 NSRect button_frame = 215 NSMakeRect(0, NSMaxY(initial_window_bounds) - kURLBarHeight, 216 kButtonWidth, kURLBarHeight); 217 218 MakeShellButton(&button_frame, @"Back", content, IDC_NAV_BACK, 219 (NSView*)delegate, @"[", NSCommandKeyMask); 220 MakeShellButton(&button_frame, @"Forward", content, IDC_NAV_FORWARD, 221 (NSView*)delegate, @"]", NSCommandKeyMask); 222 MakeShellButton(&button_frame, @"Reload", content, IDC_NAV_RELOAD, 223 (NSView*)delegate, @"r", NSCommandKeyMask); 224 MakeShellButton(&button_frame, @"Stop", content, IDC_NAV_STOP, 225 (NSView*)delegate, @".", NSCommandKeyMask); 226 227 button_frame.size.width = 228 NSWidth(initial_window_bounds) - NSMinX(button_frame); 229 base::scoped_nsobject<NSTextField> url_edit_view( 230 [[NSTextField alloc] initWithFrame:button_frame]); 231 [content addSubview:url_edit_view]; 232 [url_edit_view setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)]; 233 [url_edit_view setTarget:delegate]; 234 [url_edit_view setAction:@selector(takeURLStringValueFrom:)]; 235 [[url_edit_view cell] setWraps:NO]; 236 [[url_edit_view cell] setScrollable:YES]; 237 url_edit_view_ = url_edit_view.get(); 238 239 // show the window 240 [window_ makeKeyAndOrderFront:nil]; 241 } 242 243 void Shell::PlatformSetContents() { 244 NSView* web_view = web_contents_->GetView()->GetNativeView(); 245 [web_view setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; 246 247 if (headless_) { 248 SizeTo(content_width_, content_height_); 249 return; 250 } 251 252 NSView* content = [window_ contentView]; 253 [content addSubview:web_view]; 254 255 NSRect frame = [content bounds]; 256 frame.size.height -= kURLBarHeight; 257 [web_view setFrame:frame]; 258 [web_view setNeedsDisplay:YES]; 259 } 260 261 void Shell::SizeTo(int width, int height) { 262 if (!headless_) { 263 NOTREACHED(); 264 return; 265 } 266 NSView* web_view = web_contents_->GetView()->GetNativeView(); 267 NSRect frame = NSMakeRect(0, 0, width, height); 268 [web_view setFrame:frame]; 269 } 270 271 void Shell::PlatformResizeSubViews() { 272 // Not needed; subviews are bound. 273 } 274 275 void Shell::PlatformSetTitle(const string16& title) { 276 if (headless_) 277 return; 278 279 NSString* title_string = base::SysUTF16ToNSString(title); 280 [window_ setTitle:title_string]; 281 } 282 283 void Shell::Close() { 284 if (headless_) 285 delete this; 286 else 287 [window_ performClose:nil]; 288 } 289 290 void Shell::ActionPerformed(int control) { 291 switch (control) { 292 case IDC_NAV_BACK: 293 GoBackOrForward(-1); 294 break; 295 case IDC_NAV_FORWARD: 296 GoBackOrForward(1); 297 break; 298 case IDC_NAV_RELOAD: 299 Reload(); 300 break; 301 case IDC_NAV_STOP: 302 Stop(); 303 break; 304 } 305 } 306 307 void Shell::URLEntered(std::string url_string) { 308 if (!url_string.empty()) { 309 GURL url(url_string); 310 if (!url.has_scheme()) 311 url = GURL("http://" + url_string); 312 LoadURL(url); 313 } 314 } 315 316 void Shell::HandleKeyboardEvent(WebContents* source, 317 const NativeWebKeyboardEvent& event) { 318 if (event.skip_in_browser) 319 return; 320 321 // The event handling to get this strictly right is a tangle; cheat here a bit 322 // by just letting the menus have a chance at it. 323 if ([event.os_event type] == NSKeyDown) { 324 if (([event.os_event modifierFlags] & NSCommandKeyMask) && 325 [[event.os_event characters] isEqual:@"l"]) { 326 [window_ makeFirstResponder:url_edit_view_]; 327 return; 328 } 329 330 [[NSApp mainMenu] performKeyEquivalent:event.os_event]; 331 } 332 } 333 334 } // namespace content 335