summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/tab_contents_controller.mm
blob: c7b5cf80665b745f5fc258ae8df7ab09c1aa75a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/ui/cocoa/tab_contents_controller.h"

#include "base/mac_util.h"
#include "base/scoped_nsobject.h"
#include "chrome/browser/renderer_host/render_view_host.h"
#include "chrome/browser/renderer_host/render_widget_host_view.h"
#include "chrome/browser/tab_contents/navigation_controller.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/notification_details.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_source.h"
#include "chrome/common/notification_type.h"


@interface TabContentsController(Private)
// Forwards frame update to |delegate_| (ResizeNotificationView calls it).
- (void)tabContentsViewFrameWillChange:(NSRect)frameRect;
// Notification from TabContents (forwarded by TabContentsNotificationBridge).
- (void)tabContentsRenderViewHostChanged:(RenderViewHost*)oldHost
                                 newHost:(RenderViewHost*)newHost;
@end


// A supporting C++ bridge object to register for TabContents notifications.

class TabContentsNotificationBridge : public NotificationObserver {
 public:
  explicit TabContentsNotificationBridge(TabContentsController* controller);

  // Overriden from NotificationObserver.
  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details);
  // Register for |contents|'s notifications, remove all prior registrations.
  void ChangeTabContents(TabContents* contents);
 private:
  NotificationRegistrar registrar_;
  TabContentsController* controller_;  // weak, owns us
};

TabContentsNotificationBridge::TabContentsNotificationBridge(
    TabContentsController* controller)
    : controller_(controller) {
}

void TabContentsNotificationBridge::Observe(
    NotificationType type,
    const NotificationSource& source,
    const NotificationDetails& details) {
  if (type == NotificationType::RENDER_VIEW_HOST_CHANGED) {
    RenderViewHostSwitchedDetails* switched_details =
        Details<RenderViewHostSwitchedDetails>(details).ptr();
    [controller_ tabContentsRenderViewHostChanged:switched_details->old_host
                                          newHost:switched_details->new_host];
  } else {
    NOTREACHED();
  }
}

void TabContentsNotificationBridge::ChangeTabContents(TabContents* contents) {
  registrar_.RemoveAll();
  if (contents) {
    registrar_.Add(this,
                   NotificationType::RENDER_VIEW_HOST_CHANGED,
                   Source<NavigationController>(&contents->controller()));
  }
}


// A custom view that notifies |controller| that view's frame is changing.

@interface ResizeNotificationView : NSView {
  TabContentsController* controller_;
}
- (id)initWithController:(TabContentsController*)controller;
@end

@implementation ResizeNotificationView

- (id)initWithController:(TabContentsController*)controller {
  if ((self = [super initWithFrame:NSZeroRect])) {
    controller_ = controller;
  }
  return self;
}

- (void)setFrame:(NSRect)frameRect {
  [controller_ tabContentsViewFrameWillChange:frameRect];
  [super setFrame:frameRect];
}

@end


@implementation TabContentsController
@synthesize tabContents = contents_;

- (id)initWithContents:(TabContents*)contents
              delegate:(id<TabContentsControllerDelegate>)delegate {
  if ((self = [super initWithNibName:nil bundle:nil])) {
    contents_ = contents;
    delegate_ = delegate;
    tabContentsBridge_.reset(new TabContentsNotificationBridge(self));
    tabContentsBridge_->ChangeTabContents(contents);
  }
  return self;
}

- (void)dealloc {
  // make sure our contents have been removed from the window
  [[self view] removeFromSuperview];
  [super dealloc];
}

- (void)loadView {
  scoped_nsobject<ResizeNotificationView> view(
      [[ResizeNotificationView alloc] initWithController:self]);
  [view setAutoresizingMask:NSViewHeightSizable|NSViewWidthSizable];
  [self setView:view];
}

- (void)ensureContentsSizeDoesNotChange {
  if (contents_) {
    NSView* contentsContainer = [self view];
    NSArray* subviews = [contentsContainer subviews];
    if ([subviews count] > 0)
      [contents_->GetNativeView() setAutoresizingMask:NSViewNotSizable];
  }
}

// Call when the tab view is properly sized and the render widget host view
// should be put into the view hierarchy.
- (void)ensureContentsVisible {
  if (!contents_)
    return;
  NSView* contentsContainer = [self view];
  NSArray* subviews = [contentsContainer subviews];
  NSView* contentsNativeView = contents_->GetNativeView();

  NSRect contentsNativeViewFrame = [contentsContainer frame];
  contentsNativeViewFrame.origin = NSZeroPoint;

  [delegate_ tabContentsViewFrameWillChange:self
                                  frameRect:contentsNativeViewFrame];

  // Native view is resized to the actual size before it becomes visible
  // to avoid flickering.
  [contentsNativeView setFrame:contentsNativeViewFrame];
  if ([subviews count] == 0) {
    [contentsContainer addSubview:contentsNativeView];
  } else if ([subviews objectAtIndex:0] != contentsNativeView) {
    [contentsContainer replaceSubview:[subviews objectAtIndex:0]
                                 with:contentsNativeView];
  }
  // Restore autoresizing properties possibly stripped by
  // ensureContentsSizeDoesNotChange call.
  [contentsNativeView setAutoresizingMask:NSViewWidthSizable|
                                          NSViewHeightSizable];
}

- (void)changeTabContents:(TabContents*)newContents {
  contents_ = newContents;
  tabContentsBridge_->ChangeTabContents(contents_);
}

- (void)tabContentsViewFrameWillChange:(NSRect)frameRect {
  [delegate_ tabContentsViewFrameWillChange:self frameRect:frameRect];
}

- (void)tabContentsRenderViewHostChanged:(RenderViewHost*)oldHost
                                 newHost:(RenderViewHost*)newHost {
  if (oldHost && newHost && oldHost->view() && newHost->view()) {
    newHost->view()->set_reserved_contents_rect(
        oldHost->view()->reserved_contents_rect());
  } else {
    [delegate_ tabContentsViewFrameWillChange:self
                                    frameRect:[[self view] frame]];
  }
}

- (void)willBecomeUnselectedTab {
  // The RWHV is ripped out of the view hierarchy on tab switches, so it never
  // formally resigns first responder status.  Handle this by explicitly sending
  // a Blur() message to the renderer, but only if the RWHV currently has focus.
  RenderViewHost* rvh = [self tabContents]->render_view_host();
  if (rvh && rvh->view() && rvh->view()->HasFocus())
    rvh->Blur();
}

- (void)willBecomeSelectedTab {
  // Do not explicitly call Focus() here, as the RWHV may not actually have
  // focus (for example, if the omnibox has focus instead).  The TabContents
  // logic will restore focus to the appropriate view.
}

- (void)tabDidChange:(TabContents*)updatedContents {
  // Calling setContentView: here removes any first responder status
  // the view may have, so avoid changing the view hierarchy unless
  // the view is different.
  if ([self tabContents] != updatedContents) {
    [self changeTabContents:updatedContents];
    if ([self tabContents])
      [self ensureContentsVisible];
  }
}

@end