Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(154)

Side by Side Diff: chrome/browser/ui/cocoa/website_settings/chooser_bubble_ui_cocoa.mm

Issue 1473393003: Add chooser permission UI code for Mac (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@wpu_1
Patch Set: merged changes from upstream, address reillyg@'s comments Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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/website_settings/chooser_bubble_ui_cocoa.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <vector>
10
11 #include "base/mac/scoped_nsobject.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_finder.h"
16 #include "chrome/browser/ui/browser_window.h"
17 #import "chrome/browser/ui/chrome_style.h"
18 #import "chrome/browser/ui/cocoa/base_bubble_controller.h"
19 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
20 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
21 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_button.h"
22 #import "chrome/browser/ui/cocoa/hover_close_button.h"
23 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
24 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
25 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
26 #include "chrome/browser/ui/website_settings/chooser_bubble_delegate.h"
27 #include "chrome/grit/generated_resources.h"
28 #include "content/public/browser/native_web_keyboard_event.h"
29 #include "ui/base/cocoa/window_size_constants.h"
30 #include "ui/base/l10n/l10n_util_mac.h"
31
32 namespace {
33
34 // Chooser bubble width.
35 const CGFloat kChooserBubbleWidth = 320.0f;
36
37 // Chooser bubble height.
38 const CGFloat kChooserBubbleHeight = 220.0f;
39
40 // Distance between the bubble border and the view that is closest to the
41 // border.
42 const CGFloat kMarginX = 20.0f;
43 const CGFloat kMarginY = 20.0f;
44
45 // Distance between two views inside the bubble.
46 const CGFloat kHorizontalPadding = 10.0f;
47 const CGFloat kVerticalPadding = 10.0f;
48
49 const CGFloat kTitlePaddingX = 50.0f;
50 }
51
52 scoped_ptr<BubbleUi> ChooserBubbleDelegate::BuildBubbleUi() {
53 return make_scoped_ptr(new ChooserBubbleUiCocoa(browser_, this));
54 }
55
56 @interface ChooserBubbleUiController
57 : BaseBubbleController<NSTableViewDataSource, NSTableViewDelegate> {
58 @private
59 // Bridge to the C++ class that created this object.
60 ChooserBubbleUiCocoa* bridge_;
61
62 base::scoped_nsobject<NSTextField> titleView_;
63 base::scoped_nsobject<NSButton> closeButton_;
64 base::scoped_nsobject<NSScrollView> scrollView_;
65 base::scoped_nsobject<NSTableColumn> tableColumn_;
66 base::scoped_nsobject<NSTableView> tableView_;
67 base::scoped_nsobject<NSButton> connectButton_;
68 base::scoped_nsobject<NSButton> cancelButton_;
69
70 Browser* browser_; // Weak.
71 ChooserBubbleDelegate* chooser_bubble_delegate_; // Weak.
72 }
73
74 // Designated initializer. |browser| and |bridge| must both be non-nil.
75 - (id)initWithBrowser:(Browser*)browser
76 initWithChooserBubbleDelegate:
77 (ChooserBubbleDelegate*)chooser_bubble_delegate
78 bridge:(ChooserBubbleUiCocoa*)bridge;
79
80 // Makes the bubble visible.
81 - (void)show;
82
83 // Will reposition the bubble based in case the anchor or parent should change.
84 - (void)updateAnchorPosition;
85
86 // Will calculate the expected anchor point for this bubble.
87 // Should only be used outside this class for tests.
88 - (NSPoint)getExpectedAnchorPoint;
89
90 // Returns true of the browser has support for the location bar.
91 // Should only be used outside this class for tests.
92 - (bool)hasLocationBar;
93
94 // Update table view when chooser options were initialized.
95 - (void)onOptionsInitialized;
96
97 // Update table view when chooser option was added.
98 - (void)onOptionAdded:(NSInteger)index;
99
100 // Update table view when chooser option was removed.
101 - (void)onOptionRemoved:(NSInteger)index;
102
103 // Update table view when chooser options changed.
104 - (void)updateTableView;
105
106 // Determines if the bubble has an anchor in a corner or no anchor at all.
107 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation;
108
109 // Returns the expected parent for this bubble.
110 - (NSWindow*)getExpectedParentWindow;
111
112 // Returns a scoped_nsobject of NSTextField displaying the title for the bubble.
113 - (base::scoped_nsobject<NSTextField>)bubbleTitle;
114
115 // Returns a scoped_nsobject of NSButton displaying the close 'x' button.
116 - (base::scoped_nsobject<NSButton>)closeButton;
117
118 // Returns a scoped_nsobject of NSButton of a button with |title| and |action|.
119 - (base::scoped_nsobject<NSButton>)buttonWithTitle:(NSString*)title
120 action:(SEL)action;
121
122 // Returns an autoreleased NSButton displaying a connect button.
123 - (base::scoped_nsobject<NSButton>)connectButton;
124
125 // Returns an autoreleased NSButton displaying a cancel button.
Reilly Grant (use Gerrit) 2015/12/09 18:56:41 nit: This comment is out of date and not very usef
juncai 2015/12/09 23:13:56 Done.
126 - (base::scoped_nsobject<NSButton>)cancelButton;
127
128 // Called when the 'Connect' button is pressed.
129 - (void)onConnect:(id)sender;
130
131 // Called when the 'Cancel' button is pressed.
132 - (void)onCancel:(id)sender;
133
134 // Called when the 'close' button is pressed.
135 - (void)onClose:(id)sender;
136
137 @end
138
139 @implementation ChooserBubbleUiController
140
141 - (id)initWithBrowser:(Browser*)browser
142 initWithChooserBubbleDelegate:
143 (ChooserBubbleDelegate*)chooser_bubble_delegate
144 bridge:(ChooserBubbleUiCocoa*)bridge {
145 DCHECK(browser);
146 DCHECK(chooser_bubble_delegate);
147 DCHECK(bridge);
148 browser_ = browser;
149 chooser_bubble_delegate_ = chooser_bubble_delegate;
150
151 base::scoped_nsobject<InfoBubbleWindow> window([[InfoBubbleWindow alloc]
152 initWithContentRect:ui::kWindowSizeDeterminedLater
153 styleMask:NSBorderlessWindowMask
154 backing:NSBackingStoreBuffered
155 defer:NO]);
156 [window setAllowedAnimations:info_bubble::kAnimateNone];
157 [window setReleasedWhenClosed:NO];
158 if ((self = [super initWithWindow:window
159 parentWindow:[self getExpectedParentWindow]
160 anchoredAt:NSZeroPoint])) {
161 [self setShouldCloseOnResignKey:NO];
162 [self setShouldOpenAsKeyWindow:YES];
163 [[self bubble] setArrowLocation:[self getExpectedArrowLocation]];
164 bridge_ = bridge;
165 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
166 [center addObserver:self
167 selector:@selector(parentWindowDidMove:)
168 name:NSWindowDidMoveNotification
169 object:[self getExpectedParentWindow]];
170 }
171 return self;
172 }
173
174 - (void)windowWillClose:(NSNotification*)notification {
175 bridge_->OnBubbleClosing();
176 [super windowWillClose:notification];
177 }
178
179 - (void)parentWindowWillToggleFullScreen:(NSNotification*)notification {
180 // Override the base class implementation, which would have closed the bubble.
181 }
182
183 - (void)parentWindowDidResize:(NSNotification*)notification {
184 DCHECK(bridge_);
185 [self setAnchorPoint:[self getExpectedAnchorPoint]];
186 }
187
188 - (void)parentWindowDidMove:(NSNotification*)notification {
189 DCHECK(bridge_);
190 [self setAnchorPoint:[self getExpectedAnchorPoint]];
191 }
192
193 - (void)show {
194 NSView* view = [[self window] contentView];
195 [view setSubviews:@[]];
196
197 // ------------------------------------
198 // | Chooser bubble title x |
199 // | -------------------------------- |
200 // | | option 0 | |
201 // | | option 1 | |
202 // | | option 2 | |
203 // | | | |
204 // | | | |
205 // | | | |
206 // | -------------------------------- |
207 // | [ Connect] [ Cancel ] |
208 // ------------------------------------
209
210 // Determine the dimensions of the bubble.
211 // Once the height and width are set, the buttons and permission menus can
212 // be laid out correctly.
213 NSRect bubbleFrame =
214 NSMakeRect(0, 0, kChooserBubbleWidth, kChooserBubbleHeight);
215
216 // Create the views.
217 // Title.
218 titleView_ = [self bubbleTitle];
219 CGFloat titleOriginX = kMarginX;
220 CGFloat titleHeight = NSHeight([titleView_ frame]);
221 CGFloat titleOriginY = kChooserBubbleHeight - kMarginY - titleHeight;
222 [titleView_ setFrameOrigin:NSMakePoint(titleOriginX, titleOriginY)];
223 [view addSubview:titleView_];
224
225 // Close button.
226 // 'x' button in the upper-right-hand corner.
227 closeButton_ = [self closeButton];
228 CGFloat closeButtonOriginX =
229 kChooserBubbleWidth - kMarginX - NSWidth([closeButton_ frame]);
230 CGFloat closeButtonOriginY =
231 kChooserBubbleHeight - kMarginY - NSHeight([closeButton_ frame]);
232 [closeButton_
233 setFrameOrigin:NSMakePoint(closeButtonOriginX, closeButtonOriginY)];
234 [view addSubview:closeButton_];
235
236 // Connect button.
237 connectButton_ = [self connectButton];
238 // Cancel button.
239 cancelButton_ = [self cancelButton];
240 CGFloat connectButtonWidth = NSWidth([connectButton_ frame]);
241 CGFloat connectButtonHeight = NSHeight([connectButton_ frame]);
242 CGFloat cancelButtonWidth = NSWidth([cancelButton_ frame]);
243
244 // ScollView embedding with TableView.
245 CGFloat scrollViewWidth = kChooserBubbleWidth - 2 * kMarginX;
246 CGFloat scrollViewHeight = kChooserBubbleHeight - 2 * kMarginY -
247 2 * kVerticalPadding - titleHeight -
248 connectButtonHeight;
249 NSRect scrollFrame =
250 NSMakeRect(kMarginX, kMarginY + connectButtonHeight + kVerticalPadding,
251 scrollViewWidth, scrollViewHeight);
252 scrollView_.reset([[NSScrollView alloc] initWithFrame:scrollFrame]);
253 [scrollView_ setBorderType:NSBezelBorder];
254 [scrollView_ setHasVerticalScroller:YES];
255 [scrollView_ setHasHorizontalScroller:YES];
256 [scrollView_ setAutohidesScrollers:YES];
257
258 // TableView.
259 tableView_.reset([[NSTableView alloc] initWithFrame:NSZeroRect]);
260 tableColumn_.reset([[NSTableColumn alloc] initWithIdentifier:@""]);
261 [tableView_ addTableColumn:tableColumn_];
262 [tableView_ setDelegate:self];
263 [tableView_ setDataSource:self];
264 // Make the column title invisible.
265 [tableView_ setHeaderView:nil];
266 [tableView_ setFocusRingType:NSFocusRingTypeNone];
267
268 [scrollView_ setDocumentView:tableView_];
269 [view addSubview:scrollView_];
270
271 // Set connect button and cancel button to the right place.
272 CGFloat connectButtonOriginX = kChooserBubbleWidth - kMarginX -
273 kHorizontalPadding - connectButtonWidth -
274 cancelButtonWidth;
275 CGFloat connectButtonOriginY = kMarginY;
276 [connectButton_
277 setFrameOrigin:NSMakePoint(connectButtonOriginX, connectButtonOriginY)];
278 [connectButton_ setEnabled:NO];
279 [view addSubview:connectButton_];
280
281 CGFloat cancelButtonOriginX =
282 kChooserBubbleWidth - kMarginX - cancelButtonWidth;
283 CGFloat cancelButtonOriginY = kMarginY;
284 [cancelButton_
285 setFrameOrigin:NSMakePoint(cancelButtonOriginX, cancelButtonOriginY)];
286 [view addSubview:cancelButton_];
287
288 bubbleFrame = [[self window] frameRectForContentRect:bubbleFrame];
289 if ([[self window] isVisible]) {
290 // Unfortunately, calling -setFrame followed by -setFrameOrigin (called
291 // within -setAnchorPoint) causes flickering. Avoid the flickering by
292 // manually adjusting the new frame's origin so that the top left stays the
293 // same, and only calling -setFrame.
294 NSRect currentWindowFrame = [[self window] frame];
295 bubbleFrame.origin = currentWindowFrame.origin;
296 bubbleFrame.origin.y = bubbleFrame.origin.y +
297 currentWindowFrame.size.height -
298 bubbleFrame.size.height;
299 [[self window] setFrame:bubbleFrame display:YES];
300 } else {
301 [[self window] setFrame:bubbleFrame display:NO];
302 [self setAnchorPoint:[self getExpectedAnchorPoint]];
303 [self showWindow:nil];
304 [[self window] makeFirstResponder:nil];
305 [[self window] setInitialFirstResponder:connectButton_.get()];
306 }
307 }
308
309 - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView {
310 const std::vector<base::string16>& device_names =
311 chooser_bubble_delegate_->GetOptions();
312 if (device_names.empty()) {
313 return 1;
314 } else {
315 return static_cast<NSInteger>(device_names.size());
316 }
317 }
318
319 - (id)tableView:(NSTableView*)tableView
320 objectValueForTableColumn:(NSTableColumn*)tableColumn
321 row:(NSInteger)rowIndex {
322 const std::vector<base::string16>& device_names =
323 chooser_bubble_delegate_->GetOptions();
324 if (device_names.empty()) {
325 DCHECK(rowIndex == 0);
326 return l10n_util::GetNSString(IDS_CHOOSER_BUBBLE_NO_DEVICES_FOUND_PROMPT);
327 } else {
328 if (rowIndex >= 0 && rowIndex < static_cast<int>(device_names.size())) {
329 return base::SysUTF16ToNSString(device_names[rowIndex]);
330 } else {
331 return @"";
332 }
333 }
334 }
335
336 - (BOOL)tableView:(NSTableView*)aTableView
337 shouldEditTableColumn:(NSTableColumn*)aTableColumn
338 row:(NSInteger)rowIndex {
339 return NO;
340 }
341
342 - (void)onOptionsInitialized {
343 [self updateTableView];
344 }
345
346 - (void)onOptionAdded:(NSInteger)index {
347 [self updateTableView];
348 }
349
350 - (void)onOptionRemoved:(NSInteger)index {
351 // Table view will automatically selects the next item if the current
352 // item is removed, so here it tracks if the removed item is the item
353 // that was previously selected, if so, deselect it.
354 if ([tableView_ selectedRow] == index)
355 [tableView_ deselectRow:index];
356
357 [self updateTableView];
358 }
359
360 - (void)updateTableView {
361 const std::vector<base::string16>& device_names =
362 chooser_bubble_delegate_->GetOptions();
363 [tableView_ setEnabled:!device_names.empty()];
364
365 CGFloat tableColumnWidth = 0.0f;
366 if (device_names.empty()) {
367 [tableView_ setEnabled:NO];
368 NSString* text =
369 l10n_util::GetNSString(IDS_CHOOSER_BUBBLE_NO_DEVICES_FOUND_PROMPT);
370 CGSize textSize = [text sizeWithAttributes:@{
371 NSFontAttributeName : [NSFont systemFontOfSize:[NSFont systemFontSize]]
372 }];
373 tableColumnWidth = textSize.width;
374 } else {
375 [tableView_ setEnabled:YES];
376 for (const auto& device_name : device_names) {
377 NSString* text = base::SysUTF16ToNSString(device_name);
378 CGSize textSize = [text sizeWithAttributes:@{
379 NSFontAttributeName : [NSFont systemFontOfSize:[NSFont systemFontSize]]
380 }];
381 tableColumnWidth = std::max(tableColumnWidth, textSize.width);
382 }
383 }
384 [tableColumn_ setWidth:tableColumnWidth];
385
386 [tableView_ reloadData];
387 }
388
389 - (void)tableViewSelectionDidChange:(NSNotification*)aNotification {
390 [connectButton_ setEnabled:[tableView_ numberOfSelectedRows] > 0];
391 }
392
393 - (void)updateAnchorPosition {
394 [self setParentWindow:[self getExpectedParentWindow]];
395 [self setAnchorPoint:[self getExpectedAnchorPoint]];
396 }
397
398 - (NSPoint)getExpectedAnchorPoint {
399 NSPoint anchor;
400 if ([self hasLocationBar]) {
401 LocationBarViewMac* location_bar =
402 [[[self getExpectedParentWindow] windowController] locationBarBridge];
403 anchor = location_bar->GetPageInfoBubblePoint();
404 } else {
405 // Center the bubble if there's no location bar.
406 NSRect contentFrame = [[[self getExpectedParentWindow] contentView] frame];
407 anchor = NSMakePoint(NSMidX(contentFrame), NSMaxY(contentFrame));
408 }
409
410 return [[self getExpectedParentWindow] convertBaseToScreen:anchor];
411 }
412
413 - (bool)hasLocationBar {
414 return browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR);
415 }
416
417 - (info_bubble::BubbleArrowLocation)getExpectedArrowLocation {
418 return [self hasLocationBar] ? info_bubble::kTopLeft : info_bubble::kNoArrow;
419 }
420
421 - (NSWindow*)getExpectedParentWindow {
422 DCHECK(browser_->window());
423 return browser_->window()->GetNativeWindow();
424 }
425
426 - (base::scoped_nsobject<NSTextField>)bubbleTitle {
427 base::scoped_nsobject<NSTextField> titleView(
428 [[NSTextField alloc] initWithFrame:NSZeroRect]);
429 [titleView setDrawsBackground:NO];
430 [titleView setBezeled:NO];
431 [titleView setEditable:NO];
432 [titleView setSelectable:NO];
433 [titleView setStringValue:l10n_util::GetNSString(IDS_CHOOSER_BUBBLE_PROMPT)];
434 [titleView setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
435 [titleView sizeToFit];
436 NSRect titleFrame = [titleView frame];
437 [titleView setFrameSize:NSMakeSize(NSWidth(titleFrame) + kTitlePaddingX,
438 NSHeight(titleFrame))];
439 return titleView;
440 }
441
442 - (base::scoped_nsobject<NSButton>)closeButton {
443 int dimension = chrome_style::GetCloseButtonSize();
444 NSRect frame = NSMakeRect(0, 0, dimension, dimension);
445 base::scoped_nsobject<NSButton> button(
446 [[WebUIHoverCloseButton alloc] initWithFrame:frame]);
447 [button setAction:@selector(onClose:)];
448 [button setTarget:self];
449 return button;
450 }
451
452 - (base::scoped_nsobject<NSButton>)buttonWithTitle:(NSString*)title
453 action:(SEL)action {
454 base::scoped_nsobject<NSButton> button(
455 [[ConstrainedWindowButton alloc] initWithFrame:NSZeroRect]);
456 [button setButtonType:NSMomentaryPushInButton];
457 [button setTitle:title];
458 [button setTarget:self];
459 [button setAction:action];
460 [button sizeToFit];
461 return button;
462 }
463
464 - (base::scoped_nsobject<NSButton>)connectButton {
465 NSString* connectTitle =
466 l10n_util::GetNSString(IDS_CHOOSER_BUBBLE_CONNECT_BUTTON_TEXT);
467 return [self buttonWithTitle:connectTitle action:@selector(onConnect:)];
468 }
469
470 - (base::scoped_nsobject<NSButton>)cancelButton {
471 NSString* cancelTitle =
472 l10n_util::GetNSString(IDS_CHOOSER_BUBBLE_CANCEL_BUTTON_TEXT);
473 return [self buttonWithTitle:cancelTitle action:@selector(onCancel:)];
474 }
475
476 + (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB {
477 NSRect frameA = [viewA frame];
478 NSRect frameB = [viewB frame];
479 CGFloat width = std::max(NSWidth(frameA), NSWidth(frameB));
480 [viewA setFrameSize:NSMakeSize(width, NSHeight(frameA))];
481 [viewB setFrameSize:NSMakeSize(width, NSHeight(frameB))];
482 return width;
483 }
484
485 + (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB {
486 NSRect frameA = [viewA frame];
487 NSRect frameB = [viewB frame];
488 frameA.origin.y =
489 NSMinY(frameB) + std::floor((NSHeight(frameB) - NSHeight(frameA)) / 2);
490 [viewA setFrameOrigin:frameA.origin];
491 }
492
493 - (void)onConnect:(id)sender {
494 NSInteger row = [tableView_ selectedRow];
495 chooser_bubble_delegate_->Select(row);
496 [self close];
497 }
498
499 - (void)onCancel:(id)sender {
500 chooser_bubble_delegate_->Cancel();
501 [self close];
502 }
503
504 - (void)onClose:(id)sender {
505 chooser_bubble_delegate_->Close();
506 [self close];
507 }
508
509 @end
510
511 ChooserBubbleUiCocoa::ChooserBubbleUiCocoa(
512 Browser* browser,
513 ChooserBubbleDelegate* chooser_bubble_delegate)
514 : browser_(browser),
515 chooser_bubble_delegate_(chooser_bubble_delegate),
516 chooser_bubble_ui_controller_(nil) {
517 DCHECK(browser);
518 DCHECK(chooser_bubble_delegate);
519 chooser_bubble_delegate_->set_observer(this);
520 }
521
522 ChooserBubbleUiCocoa::~ChooserBubbleUiCocoa() {
523 chooser_bubble_delegate_->set_observer(nullptr);
524 if (chooser_bubble_ui_controller_) {
525 [chooser_bubble_ui_controller_ close];
526 chooser_bubble_ui_controller_ = nil;
527 }
528 }
529
530 void ChooserBubbleUiCocoa::Show(BubbleReference bubble_reference) {
531 if (!chooser_bubble_ui_controller_) {
532 chooser_bubble_ui_controller_ = [[ChooserBubbleUiController alloc]
533 initWithBrowser:browser_
534 initWithChooserBubbleDelegate:chooser_bubble_delegate_
535 bridge:this];
536 }
537
538 [chooser_bubble_ui_controller_ show];
539 [chooser_bubble_ui_controller_ updateTableView];
540 }
541
542 void ChooserBubbleUiCocoa::Close() {
543 if (chooser_bubble_ui_controller_) {
544 [chooser_bubble_ui_controller_ close];
545 chooser_bubble_ui_controller_ = nil;
546 }
547 }
548
549 void ChooserBubbleUiCocoa::UpdateAnchorPosition() {
550 [chooser_bubble_ui_controller_ updateAnchorPosition];
551 }
552
553 void ChooserBubbleUiCocoa::OnOptionsInitialized() {
554 [chooser_bubble_ui_controller_ onOptionsInitialized];
555 }
556
557 void ChooserBubbleUiCocoa::OnOptionAdded(int index) {
558 [chooser_bubble_ui_controller_ onOptionAdded:index];
559 }
560
561 void ChooserBubbleUiCocoa::OnOptionRemoved(int index) {
562 [chooser_bubble_ui_controller_ onOptionRemoved:index];
563 }
564
565 void ChooserBubbleUiCocoa::OnBubbleClosing() {
566 chooser_bubble_ui_controller_ = nil;
567 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698