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