find_bar_cocoa_controller.mm revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1// Copyright (c) 2012 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 <Cocoa/Cocoa.h>
6
7#include "base/auto_reset.h"
8#include "base/mac/bundle_locations.h"
9#include "base/mac/mac_util.h"
10#include "base/strings/sys_string_conversions.h"
11#include "grit/theme_resources.h"
12#include "grit/ui_resources.h"
13#include "chrome/browser/ui/browser_finder.h"
14#include "chrome/browser/ui/browser_window.h"
15#include "chrome/browser/ui/cocoa/browser_window_controller.h"
16#import "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h"
17#import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
18#import "chrome/browser/ui/cocoa/find_bar/find_bar_text_field.h"
19#import "chrome/browser/ui/cocoa/find_bar/find_bar_text_field_cell.h"
20#import "chrome/browser/ui/cocoa/image_button_cell.h"
21#import "chrome/browser/ui/cocoa/nsview_additions.h"
22#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
23#include "chrome/browser/ui/find_bar/find_bar_controller.h"
24#include "chrome/browser/ui/find_bar/find_tab_helper.h"
25#include "content/public/browser/render_view_host.h"
26#include "content/public/browser/web_contents.h"
27#include "content/public/browser/web_contents_view.h"
28#import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
29#import "ui/base/cocoa/find_pasteboard.h"
30#import "ui/base/cocoa/focus_tracker.h"
31
32using content::NativeWebKeyboardEvent;
33
34const float kFindBarOpenDuration = 0.2;
35const float kFindBarCloseDuration = 0.15;
36const float kFindBarMoveDuration = 0.15;
37const float kRightEdgeOffset = 25;
38
39
40@interface FindBarCocoaController (PrivateMethods) <NSAnimationDelegate>
41// Returns the appropriate frame for a hidden find bar.
42- (NSRect)hiddenFindBarFrame;
43
44// Animates the given |view| to the given |endFrame| within |duration| seconds.
45// Returns a new NSViewAnimation.
46- (NSViewAnimation*)createAnimationForView:(NSView*)view
47                                   toFrame:(NSRect)endFrame
48                                  duration:(float)duration;
49
50// Sets the frame of |findBarView_|.  |duration| is ignored if |animate| is NO.
51- (void)setFindBarFrame:(NSRect)endFrame
52                animate:(BOOL)animate
53               duration:(float)duration;
54
55// Returns the horizontal position the FindBar should use in order to avoid
56// overlapping with the current find result, if there's one.
57- (float)findBarHorizontalPosition;
58
59// Adjusts the horizontal position if necessary to avoid overlapping with the
60// current find result.
61- (void)moveFindBarIfNecessary:(BOOL)animate;
62
63// Puts |text| into the find bar and enables the buttons, but doesn't start a
64// new search for |text|.
65- (void)prepopulateText:(NSString*)text;
66
67// Clears the find results for all tabs in browser associated with this find
68// bar. If |suppressPboardUpdateActions_| is true then the current tab is not
69// cleared.
70- (void)clearFindResultsForCurrentBrowser;
71
72- (BrowserWindowController*)browserWindowController;
73@end
74
75@implementation FindBarCocoaController
76
77@synthesize findBarView = findBarView_;
78
79- (id)initWithBrowser:(Browser*)browser {
80  if ((self = [super initWithNibName:@"FindBar"
81                              bundle:base::mac::FrameworkBundle()])) {
82    [[NSNotificationCenter defaultCenter]
83        addObserver:self
84           selector:@selector(findPboardUpdated:)
85               name:kFindPasteboardChangedNotification
86             object:[FindPasteboard sharedInstance]];
87    browser_ = browser;
88  }
89  return self;
90}
91
92- (void)dealloc {
93  // All animations should have been explicitly stopped before a tab is closed.
94  DCHECK(!showHideAnimation_.get());
95  DCHECK(!moveAnimation_.get());
96  [[NSNotificationCenter defaultCenter] removeObserver:self];
97  [super dealloc];
98}
99
100- (void)setFindBarBridge:(FindBarBridge*)findBarBridge {
101  DCHECK(!findBarBridge_);  // should only be called once.
102  findBarBridge_ = findBarBridge;
103}
104
105- (void)awakeFromNib {
106  [[closeButton_ cell] setImageID:IDR_CLOSE_1
107                   forButtonState:image_button_cell::kDefaultState];
108  [[closeButton_ cell] setImageID:IDR_CLOSE_1_H
109                   forButtonState:image_button_cell::kHoverState];
110  [[closeButton_ cell] setImageID:IDR_CLOSE_1_P
111                   forButtonState:image_button_cell::kPressedState];
112  [[closeButton_ cell] setImageID:IDR_CLOSE_1
113                   forButtonState:image_button_cell::kDisabledState];
114
115  [findBarView_ setFrame:[self hiddenFindBarFrame]];
116  defaultWidth_ = NSWidth([findBarView_ frame]);
117
118  [self prepopulateText:[[FindPasteboard sharedInstance] findText]];
119}
120
121- (IBAction)close:(id)sender {
122  if (findBarBridge_)
123    findBarBridge_->GetFindBarController()->EndFindSession(
124        FindBarController::kKeepSelectionOnPage,
125        FindBarController::kKeepResultsInFindBox);
126
127  // Turn off hover state on close button else the button will remain
128  // hovered when we bring the find bar back up.
129  // crbug.com/227424
130  [[closeButton_ cell] setIsMouseInside:NO];
131}
132
133- (IBAction)previousResult:(id)sender {
134  if (findBarBridge_) {
135    FindTabHelper* findTabHelper = FindTabHelper::FromWebContents(
136        findBarBridge_->GetFindBarController()->web_contents());
137    findTabHelper->StartFinding(
138        base::SysNSStringToUTF16([findText_ stringValue]),
139        false, false);
140  }
141}
142
143- (IBAction)nextResult:(id)sender {
144  if (findBarBridge_) {
145    FindTabHelper* findTabHelper = FindTabHelper::FromWebContents(
146        findBarBridge_->GetFindBarController()->web_contents());
147    findTabHelper->StartFinding(
148        base::SysNSStringToUTF16([findText_ stringValue]),
149        true, false);
150  }
151}
152
153- (void)findPboardUpdated:(NSNotification*)notification {
154  if (!suppressPboardUpdateActions_)
155    [self prepopulateText:[[FindPasteboard sharedInstance] findText]];
156  [self clearFindResultsForCurrentBrowser];
157}
158
159- (void)positionFindBarViewAtMaxY:(CGFloat)maxY maxWidth:(CGFloat)maxWidth {
160  NSView* containerView = [self view];
161  CGFloat containerHeight = NSHeight([containerView frame]);
162  CGFloat containerWidth = std::min(maxWidth, defaultWidth_);
163
164  // Adjust where we'll actually place the find bar.
165  maxY += [containerView cr_lineWidth];
166  maxY_ = maxY;
167  CGFloat x = [self findBarHorizontalPosition];
168  NSRect newFrame = NSMakeRect(x, maxY - containerHeight,
169                               containerWidth, containerHeight);
170
171  if (moveAnimation_.get() != nil) {
172    NSRect frame = [containerView frame];
173    [moveAnimation_ stopAnimation];
174    // Restore to the X position before the animation was stopped. The Y
175    // position is immediately adjusted.
176    frame.origin.y = newFrame.origin.y;
177    [containerView setFrame:frame];
178    moveAnimation_.reset([self createAnimationForView:containerView
179                                              toFrame:newFrame
180                                             duration:kFindBarMoveDuration]);
181  } else {
182    [containerView setFrame:newFrame];
183  }
184}
185
186- (BOOL)isOffTheRecordProfile {
187  return browser_ && browser_->profile() &&
188         browser_->profile()->IsOffTheRecord();
189}
190
191// NSControl delegate method.
192- (void)controlTextDidChange:(NSNotification*)aNotification {
193  if (!findBarBridge_)
194    return;
195
196  content::WebContents* webContents =
197      findBarBridge_->GetFindBarController()->web_contents();
198  if (!webContents)
199    return;
200  FindTabHelper* findTabHelper = FindTabHelper::FromWebContents(webContents);
201
202  NSString* findText = [findText_ stringValue];
203  if (![self isOffTheRecordProfile]) {
204    base::AutoReset<BOOL> suppressReset(&suppressPboardUpdateActions_, YES);
205    [[FindPasteboard sharedInstance] setFindText:findText];
206  }
207
208  if ([findText length] > 0) {
209    findTabHelper->
210        StartFinding(base::SysNSStringToUTF16(findText), true, false);
211  } else {
212    // The textbox is empty so we reset.
213    findTabHelper->StopFinding(FindBarController::kClearSelectionOnPage);
214    [self updateUIForFindResult:findTabHelper->find_result()
215                       withText:base::string16()];
216  }
217}
218
219// NSControl delegate method
220- (BOOL)control:(NSControl*)control
221    textView:(NSTextView*)textView
222    doCommandBySelector:(SEL)command {
223  if (command == @selector(insertNewline:)) {
224    // Pressing Return
225    NSEvent* event = [NSApp currentEvent];
226
227    if ([event modifierFlags] & NSShiftKeyMask)
228      [previousButton_ performClick:nil];
229    else
230      [nextButton_ performClick:nil];
231
232    return YES;
233  } else if (command == @selector(insertLineBreak:)) {
234    // Pressing Ctrl-Return
235    if (findBarBridge_) {
236      findBarBridge_->GetFindBarController()->EndFindSession(
237          FindBarController::kActivateSelectionOnPage,
238          FindBarController::kClearResultsInFindBox);
239    }
240    return YES;
241  } else if (command == @selector(pageUp:) ||
242             command == @selector(pageUpAndModifySelection:) ||
243             command == @selector(scrollPageUp:) ||
244             command == @selector(pageDown:) ||
245             command == @selector(pageDownAndModifySelection:) ||
246             command == @selector(scrollPageDown:) ||
247             command == @selector(scrollToBeginningOfDocument:) ||
248             command == @selector(scrollToEndOfDocument:) ||
249             command == @selector(moveUp:) ||
250             command == @selector(moveDown:)) {
251    content::WebContents* web_contents =
252        findBarBridge_->GetFindBarController()->web_contents();
253    if (!web_contents)
254      return NO;
255
256    // Sanity-check to make sure we got a keyboard event.
257    NSEvent* event = [NSApp currentEvent];
258    if ([event type] != NSKeyDown && [event type] != NSKeyUp)
259      return NO;
260
261    // Forward the event to the renderer.
262    // TODO(rohitrao): Should this call -[BaseView keyEvent:]?  Is there code in
263    // that function that we want to keep or avoid? Calling
264    // |ForwardKeyboardEvent()| directly ignores edit commands, which breaks
265    // cmd-up/down if we ever decide to include |moveToBeginningOfDocument:| in
266    // the list above.
267    content::RenderViewHost* render_view_host =
268        web_contents->GetRenderViewHost();
269    render_view_host->ForwardKeyboardEvent(NativeWebKeyboardEvent(event));
270    return YES;
271  }
272
273  return NO;
274}
275
276// Methods from FindBar
277- (void)showFindBar:(BOOL)animate {
278  // Save the currently-focused view.  |findBarView_| is in the view
279  // hierarchy by now.  showFindBar can be called even when the
280  // findbar is already open, so do not overwrite an already saved
281  // view.
282  if (!focusTracker_.get())
283    focusTracker_.reset(
284        [[FocusTracker alloc] initWithWindow:[findBarView_ window]]);
285
286  // The browser window might have changed while the FindBar was hidden.
287  // Update its position now.
288  [[self browserWindowController] layoutSubviews];
289
290  // Move to the correct horizontal position first, to prevent the FindBar
291  // from jumping around when switching tabs.
292  // Prevent jumping while the FindBar is animating (hiding, then showing) too.
293  if (![self isFindBarVisible])
294    [self moveFindBarIfNecessary:NO];
295
296  // Animate the view into place.
297  NSRect frame = [findBarView_ frame];
298  frame.origin = NSZeroPoint;
299  [self setFindBarFrame:frame animate:animate duration:kFindBarOpenDuration];
300}
301
302- (void)hideFindBar:(BOOL)animate {
303  NSRect frame = [self hiddenFindBarFrame];
304  [self setFindBarFrame:frame animate:animate duration:kFindBarCloseDuration];
305}
306
307- (void)stopAnimation {
308  if (showHideAnimation_.get()) {
309    [showHideAnimation_ stopAnimation];
310    showHideAnimation_.reset(nil);
311  }
312  if (moveAnimation_.get()) {
313    [moveAnimation_ stopAnimation];
314    moveAnimation_.reset(nil);
315  }
316}
317
318- (void)setFocusAndSelection {
319  [[findText_ window] makeFirstResponder:findText_];
320
321  // Enable the buttons if the find text is non-empty.
322  BOOL buttonsEnabled = ([[findText_ stringValue] length] > 0) ? YES : NO;
323  [previousButton_ setEnabled:buttonsEnabled];
324  [nextButton_ setEnabled:buttonsEnabled];
325}
326
327- (void)restoreSavedFocus {
328  if (!(focusTracker_.get() &&
329        [focusTracker_ restoreFocusInWindow:[findBarView_ window]])) {
330    // Fall back to giving focus to the tab contents.
331    findBarBridge_->GetFindBarController()->web_contents()->GetView()->Focus();
332  }
333  focusTracker_.reset(nil);
334}
335
336- (void)setFindText:(NSString*)findText
337      selectedRange:(const NSRange&)selectedRange {
338  [findText_ setStringValue:findText];
339
340  if (![self isOffTheRecordProfile]) {
341    // Make sure the text in the find bar always ends up in the find pasteboard
342    // (and, via notifications, in the other find bars too).
343    base::AutoReset<BOOL> suppressReset(&suppressPboardUpdateActions_, YES);
344    [[FindPasteboard sharedInstance] setFindText:findText];
345  }
346
347  NSText* editor = [findText_ currentEditor];
348  if (selectedRange.location != NSNotFound)
349    [editor setSelectedRange:selectedRange];
350}
351
352- (NSString*)findText {
353  return [findText_ stringValue];
354}
355
356- (NSRange)selectedRange {
357  NSText* editor = [findText_ currentEditor];
358  return (editor != nil) ? [editor selectedRange] : NSMakeRange(NSNotFound, 0);
359}
360
361- (NSString*)matchCountText {
362  return [[findText_ findBarTextFieldCell] resultsString];
363}
364
365- (void)updateFindBarForChangedWebContents {
366  content::WebContents* contents =
367      findBarBridge_->GetFindBarController()->web_contents();
368  if (!contents)
369    return;
370  FindTabHelper* findTabHelper = FindTabHelper::FromWebContents(contents);
371
372  // If the find UI is visible but the results are cleared then also clear
373  // the results label and update the buttons.
374  if (findTabHelper->find_ui_active() &&
375      findTabHelper->previous_find_text().empty()) {
376    BOOL buttonsEnabled = [[findText_ stringValue] length] > 0 ? YES : NO;
377    [previousButton_ setEnabled:buttonsEnabled];
378    [nextButton_ setEnabled:buttonsEnabled];
379    [[findText_ findBarTextFieldCell] clearResults];
380  }
381}
382
383- (void)clearResults:(const FindNotificationDetails&)results {
384  // Just call updateUIForFindResult, which will take care of clearing
385  // the search text and the results label.
386  [self updateUIForFindResult:results withText:base::string16()];
387}
388
389- (void)updateUIForFindResult:(const FindNotificationDetails&)result
390                     withText:(const base::string16&)findText {
391  // If we don't have any results and something was passed in, then
392  // that means someone pressed Cmd-G while the Find box was
393  // closed. In that case we need to repopulate the Find box with what
394  // was passed in.
395  if ([[findText_ stringValue] length] == 0 && !findText.empty()) {
396    [findText_ setStringValue:base::SysUTF16ToNSString(findText)];
397    [findText_ selectText:self];
398  }
399
400  // Make sure Find Next and Find Previous are enabled if we found any matches.
401  BOOL buttonsEnabled = result.number_of_matches() > 0;
402  [previousButton_ setEnabled:buttonsEnabled];
403  [nextButton_ setEnabled:buttonsEnabled];
404
405  // Update the results label.
406  BOOL validRange = result.active_match_ordinal() != -1 &&
407                    result.number_of_matches() != -1;
408  NSString* searchString = [findText_ stringValue];
409  if ([searchString length] > 0 && validRange) {
410    [[findText_ findBarTextFieldCell]
411        setActiveMatch:result.active_match_ordinal()
412                    of:result.number_of_matches()];
413  } else {
414    // If there was no text entered, we don't show anything in the results area.
415    [[findText_ findBarTextFieldCell] clearResults];
416  }
417
418  [findText_ resetFieldEditorFrameIfNeeded];
419
420  // If we found any results, reset the focus tracker, so we always
421  // restore focus to the tab contents.
422  if (result.number_of_matches() > 0)
423    focusTracker_.reset(nil);
424
425  // Adjust the FindBar position, even when there are no matches (so that it
426  // goes back to the default position, if required).
427  [self moveFindBarIfNecessary:[self isFindBarVisible]];
428}
429
430- (BOOL)isFindBarVisible {
431  // Find bar is visible if any part of it is on the screen.
432  return NSIntersectsRect([[self view] bounds], [findBarView_ frame]);
433}
434
435- (BOOL)isFindBarAnimating {
436  return (showHideAnimation_.get() != nil) || (moveAnimation_.get() != nil);
437}
438
439// NSAnimation delegate methods.
440- (void)animationDidEnd:(NSAnimation*)animation {
441  // Autorelease the animations (cannot use release because the animation object
442  // is still on the stack.
443  if (animation == showHideAnimation_.get()) {
444    [showHideAnimation_.release() autorelease];
445  } else if (animation == moveAnimation_.get()) {
446    [moveAnimation_.release() autorelease];
447  } else {
448    NOTREACHED();
449  }
450
451  // If the find bar is not visible, make it actually hidden, so it'll no longer
452  // respond to key events.
453  [findBarView_ setHidden:![self isFindBarVisible]];
454  [[self browserWindowController] onFindBarVisibilityChanged];
455}
456
457- (gfx::Point)findBarWindowPosition {
458  gfx::Rect viewRect(NSRectToCGRect([[self view] frame]));
459  // Convert Cocoa coordinates (Y growing up) to Y growing down.
460  // Offset from |maxY_|, which represents the content view's top, instead
461  // of from the superview, which represents the whole browser window.
462  viewRect.set_y(maxY_ - viewRect.bottom());
463  return viewRect.origin();
464}
465
466- (int)findBarWidth {
467  return NSWidth([[self view] frame]);
468}
469
470@end
471
472@implementation FindBarCocoaController (PrivateMethods)
473
474- (NSRect)hiddenFindBarFrame {
475  NSRect frame = [findBarView_ frame];
476  NSRect containerBounds = [[self view] bounds];
477  frame.origin = NSMakePoint(NSMinX(containerBounds), NSMaxY(containerBounds));
478  return frame;
479}
480
481- (NSViewAnimation*)createAnimationForView:(NSView*)view
482                                   toFrame:(NSRect)endFrame
483                                  duration:(float)duration {
484  NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
485      view, NSViewAnimationTargetKey,
486      [NSValue valueWithRect:endFrame], NSViewAnimationEndFrameKey, nil];
487
488  NSViewAnimation* animation =
489      [[NSViewAnimation alloc]
490        initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
491  [animation gtm_setDuration:duration
492                   eventMask:NSLeftMouseUpMask];
493  [animation setDelegate:self];
494  [animation startAnimation];
495  return animation;
496}
497
498- (void)setFindBarFrame:(NSRect)endFrame
499                animate:(BOOL)animate
500               duration:(float)duration {
501  // Save the current frame.
502  NSRect startFrame = [findBarView_ frame];
503
504  // Stop any existing animations.
505  [showHideAnimation_ stopAnimation];
506
507  if (!animate) {
508    [findBarView_ setFrame:endFrame];
509    [findBarView_ setHidden:![self isFindBarVisible]];
510    [[self browserWindowController] onFindBarVisibilityChanged];
511    showHideAnimation_.reset(nil);
512    return;
513  }
514
515  // If animating, ensure that the find bar is not hidden. Hidden status will be
516  // updated at the end of the animation.
517  [findBarView_ setHidden:NO];
518  //[[self browserWindowController] onFindBarVisibilityChanged];
519
520  // Reset the frame to what was saved above.
521  [findBarView_ setFrame:startFrame];
522
523  [[self browserWindowController] onFindBarVisibilityChanged];
524
525  showHideAnimation_.reset([self createAnimationForView:findBarView_
526                                                toFrame:endFrame
527                                               duration:duration]);
528}
529
530- (float)findBarHorizontalPosition {
531  // Get the rect of the FindBar.
532  NSView* view = [self view];
533  NSRect frame = [view frame];
534  gfx::Rect viewRect(NSRectToCGRect(frame));
535
536  if (!findBarBridge_ || !findBarBridge_->GetFindBarController())
537    return frame.origin.x;
538  content::WebContents* contents =
539      findBarBridge_->GetFindBarController()->web_contents();
540  if (!contents)
541    return frame.origin.x;
542
543  // Get the size of the container.
544  gfx::Rect containerRect(contents->GetView()->GetContainerSize());
545
546  // Position the FindBar on the top right corner.
547  viewRect.set_x(
548      containerRect.width() - viewRect.width() - kRightEdgeOffset);
549  // Convert from Cocoa coordinates (Y growing up) to Y growing down.
550  // Notice that the view frame's Y offset is relative to the whole window,
551  // while GetLocationForFindbarView() expects it relative to the
552  // content's boundaries. |maxY_| has the correct placement in Cocoa coords,
553  // so we just have to invert the Y coordinate.
554  viewRect.set_y(maxY_ - viewRect.bottom());
555
556  // Get the rect of the current find result, if there is one.
557  const FindNotificationDetails& findResult =
558      FindTabHelper::FromWebContents(contents)->find_result();
559  if (findResult.number_of_matches() == 0)
560    return viewRect.x();
561  gfx::Rect selectionRect(findResult.selection_rect());
562
563  // Adjust |view_rect| to avoid the |selection_rect| within |container_rect|.
564  gfx::Rect newPos = FindBarController::GetLocationForFindbarView(
565      viewRect, containerRect, selectionRect);
566
567  return newPos.x();
568}
569
570- (void)moveFindBarIfNecessary:(BOOL)animate {
571  // Don't animate during tests.
572  if (FindBarBridge::disable_animations_during_testing_)
573    animate = NO;
574
575  NSView* view = [self view];
576  NSRect frame = [view frame];
577  float x = [self findBarHorizontalPosition];
578  if (frame.origin.x == x)
579    return;
580
581  if (animate) {
582    [moveAnimation_ stopAnimation];
583    // Restore to the position before the animation was stopped.
584    [view setFrame:frame];
585    frame.origin.x = x;
586    moveAnimation_.reset([self createAnimationForView:view
587                                              toFrame:frame
588                                             duration:kFindBarMoveDuration]);
589  } else {
590    frame.origin.x = x;
591    [view setFrame:frame];
592  }
593}
594
595- (void)prepopulateText:(NSString*)text {
596  [self setFindText:text selectedRange:NSMakeRange(NSNotFound, 0)];
597
598  // Has to happen after |ClearResults()| above.
599  BOOL buttonsEnabled = [text length] > 0 ? YES : NO;
600  [previousButton_ setEnabled:buttonsEnabled];
601  [nextButton_ setEnabled:buttonsEnabled];
602}
603
604- (void)clearFindResultsForCurrentBrowser {
605  if (!browser_)
606    return;
607
608  content::WebContents* activeWebContents =
609      findBarBridge_->GetFindBarController()->web_contents();
610
611  TabStripModel* tabStripModel = browser_->tab_strip_model();
612  for (int i = 0; i < tabStripModel->count(); ++i) {
613    content::WebContents* webContents = tabStripModel->GetWebContentsAt(i);
614    if (suppressPboardUpdateActions_ && activeWebContents == webContents)
615      continue;
616    FindTabHelper* findTabHelper =
617        FindTabHelper::FromWebContents(webContents);
618    findTabHelper->StopFinding(FindBarController::kClearSelectionOnPage);
619    findBarBridge_->ClearResults(findTabHelper->find_result());
620  }
621}
622
623- (BrowserWindowController*)browserWindowController {
624  if (!browser_)
625    return nil;
626  return [BrowserWindowController
627      browserWindowControllerForWindow:browser_->window()->GetNativeWindow()];
628}
629
630@end
631