| // Copyright (c) 2011 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 <Cocoa/Cocoa.h> |
| |
| #import "base/mac/scoped_nsobject.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/ui/cocoa/cocoa_test_helper.h" |
| #import "chrome/browser/ui/cocoa/tabs/media_indicator_view.h" |
| #import "chrome/browser/ui/cocoa/tabs/tab_controller.h" |
| #import "chrome/browser/ui/cocoa/tabs/tab_controller_target.h" |
| #import "chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.h" |
| #include "grit/theme_resources.h" |
| #include "grit/ui_resources.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #import "testing/gtest_mac.h" |
| #include "testing/platform_test.h" |
| #include "ui/base/resource/resource_bundle.h" |
| |
| // Implements the target interface for the tab, which gets sent messages when |
| // the tab is clicked on by the user and when its close box is clicked. |
| @interface TabControllerTestTarget : NSObject<TabControllerTarget> { |
| @private |
| bool selected_; |
| bool closed_; |
| base::scoped_nsobject<TabStripDragController> dragController_; |
| } |
| - (bool)selected; |
| - (bool)closed; |
| @end |
| |
| @implementation TabControllerTestTarget |
| - (id)init { |
| if ((self = [super init])) { |
| dragController_.reset( |
| [[TabStripDragController alloc] initWithTabStripController:nil]); |
| } |
| return self; |
| } |
| - (bool)selected { |
| return selected_; |
| } |
| - (bool)closed { |
| return closed_; |
| } |
| - (void)selectTab:(id)sender { |
| selected_ = true; |
| } |
| - (void)closeTab:(id)sender { |
| closed_ = true; |
| } |
| - (void)mouseTimer:(NSTimer*)timer { |
| // Fire the mouseUp to break the TabView drag loop. |
| NSEvent* current = [NSApp currentEvent]; |
| NSWindow* window = [timer userInfo]; |
| NSEvent* up = [NSEvent mouseEventWithType:NSLeftMouseUp |
| location:[current locationInWindow] |
| modifierFlags:0 |
| timestamp:[current timestamp] |
| windowNumber:[window windowNumber] |
| context:nil |
| eventNumber:0 |
| clickCount:1 |
| pressure:1.0]; |
| [window postEvent:up atStart:YES]; |
| } |
| - (void)commandDispatch:(TabStripModel::ContextMenuCommand)command |
| forController:(TabController*)controller { |
| } |
| - (BOOL)isCommandEnabled:(TabStripModel::ContextMenuCommand)command |
| forController:(TabController*)controller { |
| return NO; |
| } |
| - (ui::SimpleMenuModel*)contextMenuModelForController:(TabController*)controller |
| menuDelegate:(ui::SimpleMenuModel::Delegate*)delegate { |
| ui::SimpleMenuModel* model = new ui::SimpleMenuModel(delegate); |
| model->AddItem(1, ASCIIToUTF16("Hello World")); |
| model->AddItem(2, ASCIIToUTF16("Allays")); |
| model->AddItem(3, ASCIIToUTF16("Chromium")); |
| return model; |
| } |
| - (id<TabDraggingEventTarget>)dragController { |
| return dragController_.get(); |
| } |
| @end |
| |
| namespace { |
| |
| CGFloat LeftMargin(NSRect superFrame, NSRect subFrame) { |
| return NSMinX(subFrame) - NSMinX(superFrame); |
| } |
| |
| CGFloat RightMargin(NSRect superFrame, NSRect subFrame) { |
| return NSMaxX(superFrame) - NSMaxX(subFrame); |
| } |
| |
| // Helper to create an NSImageView that contains an image fetched from |
| // ui::ResourceBundle. |
| NSImageView* CreateImageViewFromResourceBundle(int resource_id) { |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| NSImage* const image = rb.GetNativeImageNamed(resource_id).ToNSImage(); |
| CHECK(!!image); |
| NSRect frame; |
| frame.size = [image size]; |
| NSImageView* const view = [[NSImageView alloc] initWithFrame:frame]; |
| [view setImage:image]; |
| return view; |
| } |
| |
| // The dragging code in TabView makes heavy use of autorelease pools so |
| // inherit from CocoaTest to have one created for us. |
| class TabControllerTest : public CocoaTest { |
| public: |
| TabControllerTest() { } |
| |
| static void CheckForExpectedLayoutAndVisibilityOfSubviews( |
| const TabController* controller) { |
| // Check whether subviews should be visible when they are supposed to be, |
| // given Tab size and TabRendererData state. |
| const TabMediaState indicatorState = |
| [[controller mediaIndicatorView] mediaState]; |
| if ([controller mini]) { |
| EXPECT_EQ(1, [controller iconCapacity]); |
| if (indicatorState != TAB_MEDIA_STATE_NONE) { |
| EXPECT_FALSE([controller shouldShowIcon]); |
| EXPECT_TRUE([controller shouldShowMediaIndicator]); |
| } else { |
| EXPECT_TRUE([controller shouldShowIcon]); |
| EXPECT_FALSE([controller shouldShowMediaIndicator]); |
| } |
| EXPECT_FALSE([controller shouldShowCloseButton]); |
| } else if ([controller selected]) { |
| EXPECT_TRUE([controller shouldShowCloseButton]); |
| switch ([controller iconCapacity]) { |
| case 0: |
| case 1: |
| EXPECT_FALSE([controller shouldShowIcon]); |
| EXPECT_FALSE([controller shouldShowMediaIndicator]); |
| break; |
| case 2: |
| if (indicatorState != TAB_MEDIA_STATE_NONE) { |
| EXPECT_FALSE([controller shouldShowIcon]); |
| EXPECT_TRUE([controller shouldShowMediaIndicator]); |
| } else { |
| EXPECT_TRUE([controller shouldShowIcon]); |
| EXPECT_FALSE([controller shouldShowMediaIndicator]); |
| } |
| break; |
| default: |
| EXPECT_LE(3, [controller iconCapacity]); |
| EXPECT_TRUE([controller shouldShowIcon]); |
| if (indicatorState != TAB_MEDIA_STATE_NONE) |
| EXPECT_TRUE([controller shouldShowMediaIndicator]); |
| else |
| EXPECT_FALSE([controller shouldShowMediaIndicator]); |
| break; |
| } |
| } else { // Tab not selected/active and not mini tab. |
| switch ([controller iconCapacity]) { |
| case 0: |
| EXPECT_FALSE([controller shouldShowCloseButton]); |
| EXPECT_FALSE([controller shouldShowIcon]); |
| EXPECT_FALSE([controller shouldShowMediaIndicator]); |
| break; |
| case 1: |
| EXPECT_FALSE([controller shouldShowCloseButton]); |
| if (indicatorState != TAB_MEDIA_STATE_NONE) { |
| EXPECT_FALSE([controller shouldShowIcon]); |
| EXPECT_TRUE([controller shouldShowMediaIndicator]); |
| } else { |
| EXPECT_TRUE([controller shouldShowIcon]); |
| EXPECT_FALSE([controller shouldShowMediaIndicator]); |
| } |
| break; |
| default: |
| EXPECT_LE(2, [controller iconCapacity]); |
| EXPECT_TRUE([controller shouldShowIcon]); |
| if (indicatorState != TAB_MEDIA_STATE_NONE) |
| EXPECT_TRUE([controller shouldShowMediaIndicator]); |
| else |
| EXPECT_FALSE([controller shouldShowMediaIndicator]); |
| break; |
| } |
| } |
| |
| // Make sure the NSView's "isHidden" state jives with the "shouldShowXXX." |
| EXPECT_TRUE([controller shouldShowIcon] == |
| (!![controller iconView] && ![[controller iconView] isHidden])); |
| EXPECT_TRUE([controller mini] == [[controller titleView] isHidden]); |
| EXPECT_TRUE([controller shouldShowMediaIndicator] == |
| ![[controller mediaIndicatorView] isHidden]); |
| EXPECT_TRUE([controller shouldShowCloseButton] != |
| [[controller closeButton] isHidden]); |
| |
| // Check positioning of elements with respect to each other, and that they |
| // are fully within the tab frame. |
| const NSRect tabFrame = [[controller view] frame]; |
| const NSRect titleFrame = [[controller titleView] frame]; |
| if ([controller shouldShowIcon]) { |
| const NSRect iconFrame = [[controller iconView] frame]; |
| EXPECT_LE(NSMinX(tabFrame), NSMinX(iconFrame)); |
| if (NSWidth(titleFrame) > 0) |
| EXPECT_LE(NSMaxX(iconFrame), NSMinX(titleFrame)); |
| EXPECT_LE(NSMinY(tabFrame), NSMinY(iconFrame)); |
| EXPECT_LE(NSMaxY(iconFrame), NSMaxY(tabFrame)); |
| } |
| if ([controller shouldShowIcon] && [controller shouldShowMediaIndicator]) { |
| EXPECT_LE(NSMaxX([[controller iconView] frame]), |
| NSMinX([[controller mediaIndicatorView] frame])); |
| } |
| if ([controller shouldShowMediaIndicator]) { |
| const NSRect mediaIndicatorFrame = |
| [[controller mediaIndicatorView] frame]; |
| if (NSWidth(titleFrame) > 0) |
| EXPECT_LE(NSMaxX(titleFrame), NSMinX(mediaIndicatorFrame)); |
| EXPECT_LE(NSMaxX(mediaIndicatorFrame), NSMaxX(tabFrame)); |
| EXPECT_LE(NSMinY(tabFrame), NSMinY(mediaIndicatorFrame)); |
| EXPECT_LE(NSMaxY(mediaIndicatorFrame), NSMaxY(tabFrame)); |
| } |
| if ([controller shouldShowMediaIndicator] && |
| [controller shouldShowCloseButton]) { |
| EXPECT_LE(NSMaxX([[controller mediaIndicatorView] frame]), |
| NSMinX([[controller closeButton] frame])); |
| } |
| if ([controller shouldShowCloseButton]) { |
| const NSRect closeButtonFrame = [[controller closeButton] frame]; |
| if (NSWidth(titleFrame) > 0) |
| EXPECT_LE(NSMaxX(titleFrame), NSMinX(closeButtonFrame)); |
| EXPECT_LE(NSMaxX(closeButtonFrame), NSMaxX(tabFrame)); |
| EXPECT_LE(NSMinY(tabFrame), NSMinY(closeButtonFrame)); |
| EXPECT_LE(NSMaxY(closeButtonFrame), NSMaxY(tabFrame)); |
| } |
| } |
| }; |
| |
| // Tests creating the controller, sticking it in a window, and removing it. |
| TEST_F(TabControllerTest, Creation) { |
| NSWindow* window = test_window(); |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| [[window contentView] addSubview:[controller view]]; |
| EXPECT_TRUE([controller tabView]); |
| EXPECT_EQ([[controller view] window], window); |
| [[controller view] display]; // Test drawing to ensure nothing leaks/crashes. |
| [[controller view] removeFromSuperview]; |
| } |
| |
| // Tests sending it a close message and ensuring that the target/action get |
| // called. Mimics the user clicking on the close button in the tab. |
| TEST_F(TabControllerTest, Close) { |
| NSWindow* window = test_window(); |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| [[window contentView] addSubview:[controller view]]; |
| |
| base::scoped_nsobject<TabControllerTestTarget> target( |
| [[TabControllerTestTarget alloc] init]); |
| EXPECT_FALSE([target closed]); |
| [controller setTarget:target]; |
| EXPECT_EQ(target.get(), [controller target]); |
| |
| [controller closeTab:nil]; |
| EXPECT_TRUE([target closed]); |
| |
| [[controller view] removeFromSuperview]; |
| } |
| |
| // Tests setting the |selected| property via code. |
| TEST_F(TabControllerTest, APISelection) { |
| NSWindow* window = test_window(); |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| [[window contentView] addSubview:[controller view]]; |
| |
| EXPECT_FALSE([controller selected]); |
| [controller setSelected:YES]; |
| EXPECT_TRUE([controller selected]); |
| |
| [[controller view] removeFromSuperview]; |
| } |
| |
| // Tests setting the |loading| property via code. |
| TEST_F(TabControllerTest, Loading) { |
| NSWindow* window = test_window(); |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| [[window contentView] addSubview:[controller view]]; |
| |
| EXPECT_EQ(kTabDone, [controller loadingState]); |
| [controller setLoadingState:kTabWaiting]; |
| EXPECT_EQ(kTabWaiting, [controller loadingState]); |
| [controller setLoadingState:kTabLoading]; |
| EXPECT_EQ(kTabLoading, [controller loadingState]); |
| [controller setLoadingState:kTabDone]; |
| EXPECT_EQ(kTabDone, [controller loadingState]); |
| |
| [[controller view] removeFromSuperview]; |
| } |
| |
| // Tests selecting the tab with the mouse click and ensuring the target/action |
| // get called. |
| TEST_F(TabControllerTest, UserSelection) { |
| NSWindow* window = test_window(); |
| |
| // Create a tab at a known location in the window that we can click on |
| // to activate selection. |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| [[window contentView] addSubview:[controller view]]; |
| NSRect frame = [[controller view] frame]; |
| frame.size.width = [TabController minTabWidth]; |
| frame.origin = NSZeroPoint; |
| [[controller view] setFrame:frame]; |
| |
| // Set the target and action. |
| base::scoped_nsobject<TabControllerTestTarget> target( |
| [[TabControllerTestTarget alloc] init]); |
| EXPECT_FALSE([target selected]); |
| [controller setTarget:target]; |
| [controller setAction:@selector(selectTab:)]; |
| EXPECT_EQ(target.get(), [controller target]); |
| EXPECT_EQ(@selector(selectTab:), [controller action]); |
| |
| // In order to track a click, we have to fake a mouse down and a mouse |
| // up, but the down goes into a tight drag loop. To break the loop, we have |
| // to fire a timer that sends a mouse up event while the "drag" is ongoing. |
| [NSTimer scheduledTimerWithTimeInterval:0.1 |
| target:target.get() |
| selector:@selector(mouseTimer:) |
| userInfo:window |
| repeats:NO]; |
| NSEvent* current = [NSApp currentEvent]; |
| NSPoint click_point = NSMakePoint(frame.size.width / 2, |
| frame.size.height / 2); |
| NSEvent* down = [NSEvent mouseEventWithType:NSLeftMouseDown |
| location:click_point |
| modifierFlags:0 |
| timestamp:[current timestamp] |
| windowNumber:[window windowNumber] |
| context:nil |
| eventNumber:0 |
| clickCount:1 |
| pressure:1.0]; |
| [[controller view] mouseDown:down]; |
| |
| // Check our target was told the tab got selected. |
| EXPECT_TRUE([target selected]); |
| |
| [[controller view] removeFromSuperview]; |
| } |
| |
| TEST_F(TabControllerTest, IconCapacity) { |
| NSWindow* window = test_window(); |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| [[window contentView] addSubview:[controller view]]; |
| int cap = [controller iconCapacity]; |
| EXPECT_GE(cap, 1); |
| |
| NSRect frame = [[controller view] frame]; |
| frame.size.width += 500; |
| [[controller view] setFrame:frame]; |
| int newcap = [controller iconCapacity]; |
| EXPECT_GT(newcap, cap); |
| } |
| |
| TEST_F(TabControllerTest, ShouldShowIcon) { |
| NSWindow* window = test_window(); |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| [[window contentView] addSubview:[controller view]]; |
| int cap = [controller iconCapacity]; |
| EXPECT_GT(cap, 0); |
| |
| // Tab is minimum width, both icon and close box should be hidden. |
| NSRect frame = [[controller view] frame]; |
| frame.size.width = [TabController minTabWidth]; |
| [[controller view] setFrame:frame]; |
| EXPECT_FALSE([controller shouldShowIcon]); |
| EXPECT_FALSE([controller shouldShowCloseButton]); |
| |
| // Setting the icon when tab is at min width should not show icon (bug 18359). |
| base::scoped_nsobject<NSView> newIcon( |
| [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 16, 16)]); |
| [controller setIconView:newIcon.get()]; |
| EXPECT_TRUE([newIcon isHidden]); |
| |
| // Tab is at selected minimum width. Since it's selected, the close box |
| // should be visible. |
| [controller setSelected:YES]; |
| frame = [[controller view] frame]; |
| frame.size.width = [TabController minSelectedTabWidth]; |
| [[controller view] setFrame:frame]; |
| EXPECT_FALSE([controller shouldShowIcon]); |
| EXPECT_TRUE([newIcon isHidden]); |
| EXPECT_TRUE([controller shouldShowCloseButton]); |
| |
| // Test expanding the tab to max width and ensure the icon and close box |
| // get put back, even when de-selected. |
| frame.size.width = [TabController maxTabWidth]; |
| [[controller view] setFrame:frame]; |
| EXPECT_TRUE([controller shouldShowIcon]); |
| EXPECT_FALSE([newIcon isHidden]); |
| EXPECT_TRUE([controller shouldShowCloseButton]); |
| [controller setSelected:NO]; |
| EXPECT_TRUE([controller shouldShowIcon]); |
| EXPECT_TRUE([controller shouldShowCloseButton]); |
| |
| cap = [controller iconCapacity]; |
| EXPECT_GT(cap, 0); |
| } |
| |
| TEST_F(TabControllerTest, Menu) { |
| NSWindow* window = test_window(); |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| base::scoped_nsobject<TabControllerTestTarget> target( |
| [[TabControllerTestTarget alloc] init]); |
| [controller setTarget:target]; |
| |
| [[window contentView] addSubview:[controller view]]; |
| int cap = [controller iconCapacity]; |
| EXPECT_GT(cap, 0); |
| |
| // Asking the view for its menu should yield a valid menu. |
| NSMenu* menu = [[controller view] menu]; |
| EXPECT_TRUE(menu); |
| EXPECT_EQ(3, [menu numberOfItems]); |
| } |
| |
| // Tests that the title field is correctly positioned and sized when the |
| // view is resized. |
| TEST_F(TabControllerTest, TitleViewLayout) { |
| NSWindow* window = test_window(); |
| |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| [[window contentView] addSubview:[controller view]]; |
| NSRect tabFrame = [[controller view] frame]; |
| tabFrame.size.width = [TabController maxTabWidth]; |
| [[controller view] setFrame:tabFrame]; |
| |
| const NSRect originalTabFrame = [[controller view] frame]; |
| const NSRect originalIconFrame = [[controller iconView] frame]; |
| const NSRect originalCloseFrame = [[controller closeButton] frame]; |
| const NSRect originalTitleFrame = [[controller titleView] frame]; |
| |
| // Sanity check the start state. |
| EXPECT_FALSE([[controller iconView] isHidden]); |
| EXPECT_FALSE([[controller closeButton] isHidden]); |
| EXPECT_GT(NSWidth([[controller view] frame]), |
| NSWidth([[controller titleView] frame])); |
| |
| // Resize the tab so that that the it shrinks. |
| tabFrame.size.width = [TabController minTabWidth]; |
| [[controller view] setFrame:tabFrame]; |
| |
| // The icon view and close button should be hidden and the title view should |
| // be resize to take up their space. |
| EXPECT_TRUE([[controller iconView] isHidden]); |
| EXPECT_TRUE([[controller closeButton] isHidden]); |
| EXPECT_GT(NSWidth([[controller view] frame]), |
| NSWidth([[controller titleView] frame])); |
| EXPECT_EQ(LeftMargin(originalTabFrame, originalIconFrame), |
| LeftMargin([[controller view] frame], |
| [[controller titleView] frame])); |
| EXPECT_EQ(RightMargin(originalTabFrame, originalCloseFrame), |
| RightMargin([[controller view] frame], |
| [[controller titleView] frame])); |
| |
| // Resize the tab so that that the it grows. |
| tabFrame.size.width = static_cast<int>([TabController maxTabWidth] * 0.75); |
| [[controller view] setFrame:tabFrame]; |
| |
| // The icon view and close button should be visible again and the title view |
| // should be resized to make room for them. |
| EXPECT_FALSE([[controller iconView] isHidden]); |
| EXPECT_FALSE([[controller closeButton] isHidden]); |
| EXPECT_GT(NSWidth([[controller view] frame]), |
| NSWidth([[controller titleView] frame])); |
| EXPECT_EQ(LeftMargin(originalTabFrame, originalTitleFrame), |
| LeftMargin([[controller view] frame], |
| [[controller titleView] frame])); |
| EXPECT_EQ(RightMargin(originalTabFrame, originalTitleFrame), |
| RightMargin([[controller view] frame], |
| [[controller titleView] frame])); |
| } |
| |
| // A comprehensive test of the layout and visibility of all elements (favicon, |
| // throbber indicators, titile text, audio indicator, and close button) over all |
| // relevant combinations of tab state. This test overlaps with parts of the |
| // other tests above. |
| // Flaky: https://code.google.com/p/chromium/issues/detail?id=311668 |
| TEST_F(TabControllerTest, DISABLED_LayoutAndVisibilityOfSubviews) { |
| static const TabMediaState kMediaStatesToTest[] = { |
| TAB_MEDIA_STATE_NONE, TAB_MEDIA_STATE_CAPTURING, |
| TAB_MEDIA_STATE_AUDIO_PLAYING |
| }; |
| |
| NSWindow* const window = test_window(); |
| |
| // Create TabController instance and place its view into the test window. |
| base::scoped_nsobject<TabController> controller([[TabController alloc] init]); |
| [[window contentView] addSubview:[controller view]]; |
| |
| // Create favicon and media indicator views. Disable animation in the media |
| // indicator view so that TabController's "what should be shown" logic can be |
| // tested effectively. If animations were left enabled, the |
| // shouldShowMediaIndicator method would return true during fade-out |
| // transitions. |
| base::scoped_nsobject<NSImageView> faviconView( |
| CreateImageViewFromResourceBundle(IDR_DEFAULT_FAVICON)); |
| base::scoped_nsobject<MediaIndicatorView> mediaIndicatorView( |
| [[MediaIndicatorView alloc] init]); |
| [mediaIndicatorView disableAnimations]; |
| [controller setMediaIndicatorView:mediaIndicatorView]; |
| |
| // Perform layout over all possible combinations, checking for correct |
| // results. |
| for (int isMiniTab = 0; isMiniTab < 2; ++isMiniTab) { |
| for (int isActiveTab = 0; isActiveTab < 2; ++isActiveTab) { |
| for (size_t mediaStateIndex = 0; |
| mediaStateIndex < arraysize(kMediaStatesToTest); |
| ++mediaStateIndex) { |
| const TabMediaState mediaState = kMediaStatesToTest[mediaStateIndex]; |
| SCOPED_TRACE(::testing::Message() |
| << (isActiveTab ? "Active" : "Inactive") << ' ' |
| << (isMiniTab ? "Mini " : "") |
| << "Tab with media indicator state " << mediaState); |
| |
| // Simulate what tab_strip_controller would do to set up the |
| // TabController state. |
| [controller setMini:(isMiniTab ? YES : NO)]; |
| [controller setActive:(isActiveTab ? YES : NO)]; |
| [[controller mediaIndicatorView] updateIndicator:mediaState]; |
| [controller setIconView:faviconView]; |
| |
| // Test layout for every width from maximum to minimum. |
| NSRect tabFrame = [[controller view] frame]; |
| int minWidth; |
| if (isMiniTab) { |
| tabFrame.size.width = minWidth = [TabController miniTabWidth]; |
| } else { |
| tabFrame.size.width = [TabController maxTabWidth]; |
| minWidth = isActiveTab ? [TabController minSelectedTabWidth] : |
| [TabController minTabWidth]; |
| } |
| while (NSWidth(tabFrame) >= minWidth) { |
| SCOPED_TRACE(::testing::Message() << "width=" << tabFrame.size.width); |
| [[controller view] setFrame:tabFrame]; |
| CheckForExpectedLayoutAndVisibilityOfSubviews(controller); |
| --tabFrame.size.width; |
| } |
| } |
| } |
| } |
| } |
| |
| } // namespace |