Home | History | Annotate | Download | only in tab_contents
      1 // Copyright (c) 2011 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 #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
      6 
      7 #include "base/memory/scoped_nsobject.h"
      8 #include "content/browser/renderer_host/render_view_host.h"
      9 #include "content/browser/renderer_host/render_widget_host_view.h"
     10 #include "content/browser/tab_contents/navigation_controller.h"
     11 #include "content/browser/tab_contents/tab_contents.h"
     12 #include "content/common/notification_details.h"
     13 #include "content/common/notification_observer.h"
     14 #include "content/common/notification_registrar.h"
     15 #include "content/common/notification_source.h"
     16 #include "content/common/notification_type.h"
     17 
     18 @interface TabContentsController(Private)
     19 // Forwards frame update to |delegate_| (ResizeNotificationView calls it).
     20 - (void)tabContentsViewFrameWillChange:(NSRect)frameRect;
     21 // Notification from TabContents (forwarded by TabContentsNotificationBridge).
     22 - (void)tabContentsRenderViewHostChanged:(RenderViewHost*)oldHost
     23                                  newHost:(RenderViewHost*)newHost;
     24 @end
     25 
     26 
     27 // A supporting C++ bridge object to register for TabContents notifications.
     28 
     29 class TabContentsNotificationBridge : public NotificationObserver {
     30  public:
     31   explicit TabContentsNotificationBridge(TabContentsController* controller);
     32 
     33   // Overriden from NotificationObserver.
     34   virtual void Observe(NotificationType type,
     35                        const NotificationSource& source,
     36                        const NotificationDetails& details);
     37   // Register for |contents|'s notifications, remove all prior registrations.
     38   void ChangeTabContents(TabContents* contents);
     39  private:
     40   NotificationRegistrar registrar_;
     41   TabContentsController* controller_;  // weak, owns us
     42 };
     43 
     44 TabContentsNotificationBridge::TabContentsNotificationBridge(
     45     TabContentsController* controller)
     46     : controller_(controller) {
     47 }
     48 
     49 void TabContentsNotificationBridge::Observe(
     50     NotificationType type,
     51     const NotificationSource& source,
     52     const NotificationDetails& details) {
     53   if (type == NotificationType::RENDER_VIEW_HOST_CHANGED) {
     54     RenderViewHostSwitchedDetails* switched_details =
     55         Details<RenderViewHostSwitchedDetails>(details).ptr();
     56     [controller_ tabContentsRenderViewHostChanged:switched_details->old_host
     57                                           newHost:switched_details->new_host];
     58   } else {
     59     NOTREACHED();
     60   }
     61 }
     62 
     63 void TabContentsNotificationBridge::ChangeTabContents(TabContents* contents) {
     64   registrar_.RemoveAll();
     65   if (contents) {
     66     registrar_.Add(this,
     67                    NotificationType::RENDER_VIEW_HOST_CHANGED,
     68                    Source<NavigationController>(&contents->controller()));
     69   }
     70 }
     71 
     72 
     73 // A custom view that notifies |controller| that view's frame is changing.
     74 
     75 @interface ResizeNotificationView : NSView {
     76   TabContentsController* controller_;
     77 }
     78 - (id)initWithController:(TabContentsController*)controller;
     79 @end
     80 
     81 @implementation ResizeNotificationView
     82 
     83 - (id)initWithController:(TabContentsController*)controller {
     84   if ((self = [super initWithFrame:NSZeroRect])) {
     85     controller_ = controller;
     86   }
     87   return self;
     88 }
     89 
     90 - (void)setFrame:(NSRect)frameRect {
     91   [controller_ tabContentsViewFrameWillChange:frameRect];
     92   [super setFrame:frameRect];
     93 }
     94 
     95 @end
     96 
     97 
     98 @implementation TabContentsController
     99 @synthesize tabContents = contents_;
    100 
    101 - (id)initWithContents:(TabContents*)contents
    102               delegate:(id<TabContentsControllerDelegate>)delegate {
    103   if ((self = [super initWithNibName:nil bundle:nil])) {
    104     contents_ = contents;
    105     delegate_ = delegate;
    106     tabContentsBridge_.reset(new TabContentsNotificationBridge(self));
    107     tabContentsBridge_->ChangeTabContents(contents);
    108   }
    109   return self;
    110 }
    111 
    112 - (void)dealloc {
    113   // make sure our contents have been removed from the window
    114   [[self view] removeFromSuperview];
    115   [super dealloc];
    116 }
    117 
    118 - (void)loadView {
    119   scoped_nsobject<ResizeNotificationView> view(
    120       [[ResizeNotificationView alloc] initWithController:self]);
    121   [view setAutoresizingMask:NSViewHeightSizable|NSViewWidthSizable];
    122   [self setView:view];
    123 }
    124 
    125 - (void)ensureContentsSizeDoesNotChange {
    126   if (contents_) {
    127     NSView* contentsContainer = [self view];
    128     NSArray* subviews = [contentsContainer subviews];
    129     if ([subviews count] > 0)
    130       [contents_->GetNativeView() setAutoresizingMask:NSViewNotSizable];
    131   }
    132 }
    133 
    134 // Call when the tab view is properly sized and the render widget host view
    135 // should be put into the view hierarchy.
    136 - (void)ensureContentsVisible {
    137   if (!contents_)
    138     return;
    139   NSView* contentsContainer = [self view];
    140   NSArray* subviews = [contentsContainer subviews];
    141   NSView* contentsNativeView = contents_->GetNativeView();
    142 
    143   NSRect contentsNativeViewFrame = [contentsContainer frame];
    144   contentsNativeViewFrame.origin = NSZeroPoint;
    145 
    146   [delegate_ tabContentsViewFrameWillChange:self
    147                                   frameRect:contentsNativeViewFrame];
    148 
    149   // Native view is resized to the actual size before it becomes visible
    150   // to avoid flickering.
    151   [contentsNativeView setFrame:contentsNativeViewFrame];
    152   if ([subviews count] == 0) {
    153     [contentsContainer addSubview:contentsNativeView];
    154   } else if ([subviews objectAtIndex:0] != contentsNativeView) {
    155     [contentsContainer replaceSubview:[subviews objectAtIndex:0]
    156                                  with:contentsNativeView];
    157   }
    158   // Restore autoresizing properties possibly stripped by
    159   // ensureContentsSizeDoesNotChange call.
    160   [contentsNativeView setAutoresizingMask:NSViewWidthSizable|
    161                                           NSViewHeightSizable];
    162 }
    163 
    164 - (void)changeTabContents:(TabContents*)newContents {
    165   contents_ = newContents;
    166   tabContentsBridge_->ChangeTabContents(contents_);
    167 }
    168 
    169 - (void)tabContentsViewFrameWillChange:(NSRect)frameRect {
    170   [delegate_ tabContentsViewFrameWillChange:self frameRect:frameRect];
    171 }
    172 
    173 - (void)tabContentsRenderViewHostChanged:(RenderViewHost*)oldHost
    174                                  newHost:(RenderViewHost*)newHost {
    175   if (oldHost && newHost && oldHost->view() && newHost->view()) {
    176     newHost->view()->set_reserved_contents_rect(
    177         oldHost->view()->reserved_contents_rect());
    178   } else {
    179     [delegate_ tabContentsViewFrameWillChange:self
    180                                     frameRect:[[self view] frame]];
    181   }
    182 }
    183 
    184 - (void)willBecomeUnselectedTab {
    185   // The RWHV is ripped out of the view hierarchy on tab switches, so it never
    186   // formally resigns first responder status.  Handle this by explicitly sending
    187   // a Blur() message to the renderer, but only if the RWHV currently has focus.
    188   RenderViewHost* rvh = [self tabContents]->render_view_host();
    189   if (rvh && rvh->view() && rvh->view()->HasFocus())
    190     rvh->Blur();
    191 }
    192 
    193 - (void)willBecomeSelectedTab {
    194   // Do not explicitly call Focus() here, as the RWHV may not actually have
    195   // focus (for example, if the omnibox has focus instead).  The TabContents
    196   // logic will restore focus to the appropriate view.
    197 }
    198 
    199 - (void)tabDidChange:(TabContents*)updatedContents {
    200   // Calling setContentView: here removes any first responder status
    201   // the view may have, so avoid changing the view hierarchy unless
    202   // the view is different.
    203   if ([self tabContents] != updatedContents) {
    204     [self changeTabContents:updatedContents];
    205     if ([self tabContents])
    206       [self ensureContentsVisible];
    207   }
    208 }
    209 
    210 @end
    211