Home | History | Annotate | Download | only in browser
      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