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