| Index: ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.mm
|
| diff --git a/ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..1819df60fec8fa08248055596010fe43cdd11fc2
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.mm
|
| @@ -0,0 +1,1249 @@
|
| +// Copyright 2015 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 "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
|
| +
|
| +#include "base/ios/block_types.h"
|
| +#include "base/ios/weak_nsobject.h"
|
| +#include "base/mac/scoped_nsobject.h"
|
| +#include "base/metrics/user_metrics.h"
|
| +#include "base/metrics/user_metrics_action.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#include "components/browser_sync/profile_sync_service.h"
|
| +#include "components/sessions/core/tab_restore_service_helper.h"
|
| +#include "components/sync/driver/sync_service.h"
|
| +#include "components/sync_sessions/open_tabs_ui_delegate.h"
|
| +#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
|
| +#include "ios/chrome/browser/chrome_url_constants.h"
|
| +#import "ios/chrome/browser/metrics/tab_usage_recorder.h"
|
| +#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
|
| +#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
|
| +#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
|
| +#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h"
|
| +#import "ios/chrome/browser/tabs/tab.h"
|
| +#import "ios/chrome/browser/tabs/tab_model.h"
|
| +#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
|
| +#import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
|
| +#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
|
| +#import "ios/chrome/browser/ui/keyboard/UIKeyCommand+Chrome.h"
|
| +#include "ios/chrome/browser/ui/ntp/recent_tabs/synced_sessions.h"
|
| +#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_in_sync_off_view.h"
|
| +#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_in_sync_on_no_sessions_view.h"
|
| +#import "ios/chrome/browser/ui/ntp/recent_tabs/views/signed_out_view.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/session_changes.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_cache.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_header_view.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_model.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_cell.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_controller.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_overlay_view.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_panel_view.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_session_cell_data.h"
|
| +#include "ios/chrome/browser/ui/tab_switcher/tab_switcher_transition_context.h"
|
| +#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_view.h"
|
| +#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
|
| +#import "ios/chrome/browser/ui/toolbar/toolbar_owner.h"
|
| +#include "ios/chrome/browser/ui/ui_util.h"
|
| +#include "ios/chrome/grit/ios_strings.h"
|
| +#include "ios/chrome/grit/ios_theme_resources.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
|
| +#import "ios/web/public/navigation_manager.h"
|
| +#include "ios/web/public/referrer.h"
|
| +#import "ios/web/web_state/ui/crw_web_controller.h"
|
| +#include "ui/base/l10n/l10n_util.h"
|
| +#include "ui/base/l10n/l10n_util_mac.h"
|
| +#include "ui/base/resource/resource_bundle.h"
|
| +
|
| +namespace {
|
| +
|
| +// Offsets for computing the panels' indexes in the TabSwitcherView.
|
| +const int kSignInPromoPanelIndex = 2;
|
| +const int kHeaderDistantSessionIndexOffset = 2;
|
| +const int kLocalTabsOffTheRecordPanelIndex = 0;
|
| +const int kLocalTabsOnTheRecordPanelIndex = 1;
|
| +// The duration of the tab switcher toggle animation.
|
| +const CGFloat kTransitionAnimationDuration = 0.25;
|
| +// The height of the browser view controller header.
|
| +const CGFloat kHeaderHeight = 39;
|
| +
|
| +enum class TransitionType {
|
| + TRANSITION_PRESENT,
|
| + TRANSITION_DISMISS,
|
| +};
|
| +
|
| +enum class SnapshotViewOption {
|
| + SNAPSHOT_VIEW, // Try taking a snapshot view if possible.
|
| + CLIENT_RENDERING, // Use [UIView renderInContext:] to render a bitmap and set
|
| + // it as the backing store of a view's layer.
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +@interface TabSwitcherController ()<TabSwitcherModelDelegate,
|
| + TabSwitcherViewDelegate,
|
| + TabSwitcherHeaderViewDelegate,
|
| + TabSwitcherHeaderViewDataSource,
|
| + TabSwitcherPanelControllerDelegate> {
|
| + // weak.
|
| + ios::ChromeBrowserState* _browserState;
|
| + // weak.
|
| + id<TabSwitcherDelegate> _delegate;
|
| + // The model selected when the tab switcher was toggled.
|
| + // weak.
|
| + TabModel* _onLoadActiveModel;
|
| + // The view this controller manages.
|
| + base::scoped_nsobject<TabSwitcherView> _tabSwitcherView;
|
| + // The list of panels controllers for distant sessions.
|
| + base::scoped_nsobject<NSMutableArray> _controllersOfDistantSessions;
|
| + // The panel controllers for the local sessions.
|
| + base::scoped_nsobject<TabSwitcherPanelController> _onTheRecordSession;
|
| + base::scoped_nsobject<TabSwitcherPanelController> _offTheRecordSession;
|
| + // The model storing the state of what is shown by the tab switcher.
|
| + base::scoped_nsobject<TabSwitcherModel> _tabSwitcherModel;
|
| + // Stores the current sign-in panel type.
|
| + TabSwitcherSignInPanelsType _signInPanelType;
|
| + // Cache for the panel's cells.
|
| + base::scoped_nsobject<TabSwitcherCache> _cache;
|
| + // Stores the background color of the window when the tab switcher was
|
| + // presented.
|
| + base::scoped_nsobject<UIColor> _initialWindowBackgroundColor;
|
| + // Indicate whether a previous promo panel header cell should be removed or
|
| + // added.
|
| + BOOL _shouldRemovePromoPanelHeaderCell;
|
| + BOOL _shouldAddPromoPanelHeaderCell;
|
| +}
|
| +
|
| +// Updates the window background color to the tab switcher's background color.
|
| +// The original background color can be restored by calling
|
| +// -restoreWindowBackgroundColor.
|
| +- (void)updateWindowBackgroundColor;
|
| +
|
| +// Restores the tab switcher's window background color to the value it had
|
| +// before presenting it.
|
| +- (void)restoreWindowBackgroundColor;
|
| +
|
| +// Performs the tab switcher transition according to the |transitionType| and
|
| +// call the completion block at the end of the transition. The transition will
|
| +// be animated according to the |animated| parameter. |completion| block must
|
| +// not be nil.
|
| +- (void)performTabSwitcherTransition:(TransitionType)transitionType
|
| + withModel:(TabModel*)tabModel
|
| + animated:(BOOL)animated
|
| + withCompletion:(ProceduralBlock)completion;
|
| +
|
| +// Returns the index of the currently selected panel.
|
| +- (NSInteger)currentPanelIndex;
|
| +
|
| +// Returns the session type of the panel and the given index.
|
| +- (ios_internal::SessionType)sessionTypeForPanelIndex:(NSInteger)panelIndex;
|
| +
|
| +// Returns the tab model corresponding to the given session type.
|
| +// There is no tab model for distant sessions so it returns nil for distant
|
| +// sessions type.
|
| +- (TabModel*)tabModelForSessionType:(ios_internal::SessionType)sessionType;
|
| +
|
| +// Returns the tab model of the currently selected tab.
|
| +- (TabModel*)currentSelectedModel;
|
| +
|
| +// Calls tabSwitcherDismissWithModel:animated: with the |animated| parameter
|
| +// set to YES.
|
| +- (void)tabSwitcherDismissWithModel:(TabModel*)model;
|
| +
|
| +// Dismisses the tab switcher using the given tab model. The completion block is
|
| +// called at the end of the animation. The tab switcher delegate method
|
| +// -tabSwitcherPresentationTransitionDidEnd: must be called in the completion
|
| +// block. The dismissal of the tab switcher will be animated if the |animated|
|
| +// parameter is set to YES.
|
| +- (void)tabSwitcherDismissWithModel:(TabModel*)model
|
| + animated:(BOOL)animated
|
| + withCompletion:(ProceduralBlock)completion;
|
| +
|
| +// Dismisses the tab switcher using the currently selected tab's tab model.
|
| +- (void)tabSwitcherDismissWithCurrentSelectedModel;
|
| +
|
| +// Scrolls the scrollview to show the panel displaying the |selectedTabModel|.
|
| +- (void)selectPanelForTabModel:(TabModel*)selectedTabModel;
|
| +
|
| +// Dismisses the tab switcher and create a new tab using the url, position and
|
| +// transition on the given tab model.
|
| +- (Tab*)dismissWithNewTabAnimation:(const GURL&)url
|
| + atIndex:(NSUInteger)position
|
| + transition:(ui::PageTransition)transition
|
| + tabModel:(TabModel*)tabModel;
|
| +// Add a promo panel corresponding to the panel type argument.
|
| +// Should only be called from inititalizer and signInPanelChangedTo:.
|
| +- (void)addPromoPanelForSignInPanelType:(TabSwitcherSignInPanelsType)panelType;
|
| +
|
| +// Updates cells of local panels.
|
| +- (void)updateLocalPanelsCells;
|
| +
|
| +@end
|
| +
|
| +@implementation TabSwitcherController
|
| +
|
| +@synthesize transitionContext = _transitionContext;
|
| +
|
| +- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
|
| + mainTabModel:(TabModel*)mainTabModel
|
| + otrTabModel:(TabModel*)otrTabModel
|
| + activeTabModel:(TabModel*)activeTabModel {
|
| + DCHECK(mainTabModel);
|
| + DCHECK(otrTabModel);
|
| + DCHECK(activeTabModel == otrTabModel || activeTabModel == mainTabModel);
|
| + self = [super initWithNibName:nil bundle:nil];
|
| + if (self) {
|
| + _browserState = browserState;
|
| + _onLoadActiveModel = activeTabModel;
|
| + _cache.reset([[TabSwitcherCache alloc] init]);
|
| + [_cache setMainTabModel:mainTabModel otrTabModel:otrTabModel];
|
| + _tabSwitcherModel.reset([[TabSwitcherModel alloc]
|
| + initWithBrowserState:browserState
|
| + delegate:self
|
| + mainTabModel:mainTabModel
|
| + otrTabModel:otrTabModel
|
| + withCache:_cache]);
|
| + _controllersOfDistantSessions.reset([[NSMutableArray alloc] init]);
|
| + [self loadTabSwitcherView];
|
| + _onTheRecordSession.reset([[TabSwitcherPanelController alloc]
|
| + initWithModel:_tabSwitcherModel
|
| + forLocalSessionOfType:ios_internal::SessionType::REGULAR_SESSION
|
| + withCache:_cache
|
| + browserState:_browserState]);
|
| + [_onTheRecordSession setDelegate:self];
|
| + _offTheRecordSession.reset([[TabSwitcherPanelController alloc]
|
| + initWithModel:_tabSwitcherModel
|
| + forLocalSessionOfType:ios_internal::SessionType::OFF_THE_RECORD_SESSION
|
| + withCache:_cache
|
| + browserState:_browserState]);
|
| + [_offTheRecordSession setDelegate:self];
|
| + [_tabSwitcherView addPanelView:[_offTheRecordSession view]
|
| + atIndex:kLocalTabsOffTheRecordPanelIndex];
|
| + [_tabSwitcherView addPanelView:[_onTheRecordSession view]
|
| + atIndex:kLocalTabsOnTheRecordPanelIndex];
|
| + [self addPromoPanelForSignInPanelType:[_tabSwitcherModel signInPanelType]];
|
| + [[_tabSwitcherView headerView] reloadData];
|
| + [_tabSwitcherModel syncedSessionsChanged];
|
| + [self selectPanelForTabModel:_onLoadActiveModel];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +#pragma mark - UIViewController
|
| +
|
| +- (BOOL)prefersStatusBarHidden {
|
| + return NO;
|
| +}
|
| +
|
| +- (UIStatusBarStyle)preferredStatusBarStyle {
|
| + return UIStatusBarStyleLightContent;
|
| +}
|
| +
|
| +- (CGRect)tabSwitcherInitialFrame {
|
| + return [[UIScreen mainScreen] bounds];
|
| +}
|
| +
|
| +- (void)loadTabSwitcherView {
|
| + DCHECK(![_tabSwitcherView superview]);
|
| + _tabSwitcherView.reset(
|
| + [[TabSwitcherView alloc] initWithFrame:[self tabSwitcherInitialFrame]]);
|
| + [_tabSwitcherView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
|
| + UIViewAutoresizingFlexibleHeight];
|
| + [_tabSwitcherView setDelegate:self];
|
| + [[_tabSwitcherView headerView] setDelegate:self];
|
| + [[_tabSwitcherView headerView] setDataSource:self];
|
| +}
|
| +
|
| +- (void)viewDidLoad {
|
| + [super viewDidLoad];
|
| + [self.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
|
| + UIViewAutoresizingFlexibleHeight];
|
| + [_tabSwitcherView setFrame:self.view.bounds];
|
| + [self.view addSubview:_tabSwitcherView];
|
| +}
|
| +
|
| +#pragma mark - UIResponder
|
| +
|
| +- (NSArray*)keyCommands {
|
| + base::WeakNSObject<TabSwitcherController> weakSelf(self);
|
| + return @[
|
| + [UIKeyCommand
|
| + cr_keyCommandWithInput:@"t"
|
| + modifierFlags:UIKeyModifierCommand
|
| + title:l10n_util::GetNSStringWithFixup(
|
| + IDS_IOS_TOOLS_MENU_NEW_TAB)
|
| + action:^{
|
| + base::scoped_nsobject<TabSwitcherController>
|
| + strongSelf([weakSelf retain]);
|
| + if (!strongSelf)
|
| + return;
|
| + if ([strongSelf currentPanelIndex] ==
|
| + kLocalTabsOffTheRecordPanelIndex) {
|
| + [strongSelf openNewTabInPanelAtIndex:
|
| + kLocalTabsOffTheRecordPanelIndex];
|
| + } else {
|
| + [strongSelf openNewTabInPanelAtIndex:
|
| + kLocalTabsOnTheRecordPanelIndex];
|
| + }
|
| + }],
|
| + [UIKeyCommand
|
| + cr_keyCommandWithInput:@"n"
|
| + modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
|
| + title:l10n_util::GetNSStringWithFixup(
|
| + IDS_IOS_TOOLS_MENU_NEW_INCOGNITO_TAB)
|
| + action:^{
|
| + [weakSelf openNewTabInPanelAtIndex:
|
| + kLocalTabsOffTheRecordPanelIndex];
|
| + }],
|
| + [UIKeyCommand
|
| + cr_keyCommandWithInput:@"t"
|
| + modifierFlags:UIKeyModifierCommand | UIKeyModifierShift
|
| + title:l10n_util::GetNSStringWithFixup(
|
| + IDS_IOS_KEYBOARD_REOPEN_CLOSED_TAB)
|
| + action:^{
|
| + [weakSelf reopenClosedTab];
|
| + }],
|
| + [UIKeyCommand
|
| + cr_keyCommandWithInput:@"n"
|
| + modifierFlags:UIKeyModifierCommand
|
| + title:nil
|
| + action:^{
|
| + base::scoped_nsobject<TabSwitcherController>
|
| + strongSelf([weakSelf retain]);
|
| + if (!strongSelf)
|
| + return;
|
| + if ([strongSelf currentPanelIndex] ==
|
| + kLocalTabsOffTheRecordPanelIndex) {
|
| + [strongSelf openNewTabInPanelAtIndex:
|
| + kLocalTabsOffTheRecordPanelIndex];
|
| + } else {
|
| + [strongSelf openNewTabInPanelAtIndex:
|
| + kLocalTabsOnTheRecordPanelIndex];
|
| + }
|
| + }],
|
| + ];
|
| +}
|
| +
|
| +#pragma mark - TabSwitcher protocol implementation
|
| +
|
| +- (void)restoreInternalStateWithMainTabModel:(TabModel*)mainModel
|
| + otrTabModel:(TabModel*)otrModel
|
| + activeTabModel:(TabModel*)activeModel {
|
| + _onLoadActiveModel = activeModel;
|
| + [_cache setMainTabModel:mainModel otrTabModel:otrModel];
|
| + [_tabSwitcherModel setMainTabModel:mainModel otrTabModel:otrModel];
|
| + [self selectPanelForTabModel:activeModel];
|
| +}
|
| +
|
| +- (void)setOtrTabModel:(TabModel*)otrModel {
|
| + [_cache setMainTabModel:[_cache mainTabModel] otrTabModel:otrModel];
|
| + [_tabSwitcherModel setMainTabModel:[_tabSwitcherModel mainTabModel]
|
| + otrTabModel:otrModel];
|
| +}
|
| +
|
| +- (void)showWithSelectedTabAnimation {
|
| + // Stores the current tab's scroll position. Helps determine whether the
|
| + // current tab snapshot should be updated or not.
|
| + [_onLoadActiveModel.currentTab recordStateInHistory];
|
| +
|
| + [self updateWindowBackgroundColor];
|
| + [self performTabSwitcherTransition:TransitionType::TRANSITION_PRESENT
|
| + withModel:_onLoadActiveModel
|
| + animated:YES
|
| + withCompletion:^{
|
| + [self.delegate
|
| + tabSwitcherPresentationTransitionDidEnd:self];
|
| + }];
|
| +}
|
| +
|
| +- (Tab*)dismissWithNewTabAnimationToModel:(TabModel*)targetModel
|
| + withURL:(const GURL&)url
|
| + atIndex:(NSUInteger)position
|
| + transition:(ui::PageTransition)transition {
|
| + if (targetModel == [_tabSwitcherModel mainTabModel])
|
| + [_tabSwitcherView selectPanelAtIndex:kLocalTabsOnTheRecordPanelIndex];
|
| + else
|
| + [_tabSwitcherView selectPanelAtIndex:kLocalTabsOffTheRecordPanelIndex];
|
| + return [self dismissWithNewTabAnimation:url
|
| + atIndex:position
|
| + transition:transition
|
| + tabModel:targetModel];
|
| +}
|
| +
|
| +- (void)setDelegate:(id<TabSwitcherDelegate>)delegate {
|
| + _delegate = delegate;
|
| + if (delegate == nullptr)
|
| + [_tabSwitcherModel setMainTabModel:nil otrTabModel:nil];
|
| +}
|
| +
|
| +- (id<TabSwitcherDelegate>)delegate {
|
| + return _delegate;
|
| +}
|
| +
|
| +- (IBAction)chromeExecuteCommand:(id)sender {
|
| + int command = [sender tag];
|
| +
|
| + switch (command) {
|
| + case IDC_NEW_INCOGNITO_TAB: // fallthrough
|
| + case IDC_NEW_TAB: {
|
| + // Ensure that the right mode is showing.
|
| + NSInteger panelIndex = (command == IDC_NEW_TAB)
|
| + ? kLocalTabsOnTheRecordPanelIndex
|
| + : kLocalTabsOffTheRecordPanelIndex;
|
| + [_tabSwitcherView selectPanelAtIndex:panelIndex];
|
| +
|
| + const ios_internal::SessionType panelSessionType =
|
| + (command == IDC_NEW_TAB)
|
| + ? ios_internal::SessionType::REGULAR_SESSION
|
| + : ios_internal::SessionType::OFF_THE_RECORD_SESSION;
|
| +
|
| + TabModel* model = [self tabModelForSessionType:panelSessionType];
|
| + [self dismissWithNewTabAnimation:GURL(kChromeUINewTabURL)
|
| + atIndex:NSNotFound
|
| + transition:ui::PAGE_TRANSITION_TYPED
|
| + tabModel:model];
|
| + } break;
|
| + case IDC_TOGGLE_TAB_SWITCHER:
|
| + [self tabSwitcherDismissWithCurrentSelectedModel];
|
| + break;
|
| + default:
|
| + [super chromeExecuteCommand:sender];
|
| + break;
|
| + }
|
| +}
|
| +
|
| +#pragma mark - Private
|
| +
|
| +- (void)updateWindowBackgroundColor {
|
| + DCHECK(!_initialWindowBackgroundColor);
|
| + _initialWindowBackgroundColor.reset(
|
| + [self.view.window.backgroundColor retain]);
|
| + self.view.window.backgroundColor = [[MDCPalette greyPalette] tint900];
|
| +}
|
| +
|
| +- (void)restoreWindowBackgroundColor {
|
| + self.view.window.backgroundColor = _initialWindowBackgroundColor;
|
| + _initialWindowBackgroundColor.reset();
|
| +}
|
| +
|
| +- (UIView*)snapshotViewForView:(UIView*)inputView
|
| + withModel:(TabModel*)tabModel
|
| + option:(SnapshotViewOption)option {
|
| + if (inputView) {
|
| + if (option == SnapshotViewOption::SNAPSHOT_VIEW) {
|
| + UIView* view = [inputView snapshotViewAfterScreenUpdates:NO];
|
| + if (view)
|
| + return view;
|
| + }
|
| + // If the view has just been created, it has not been rendered by Core
|
| + // Animation and the snapshot view can't be generated. In that case we
|
| + // trigger a client side rendering of the view and use the rendered image
|
| + // as the backing store of a view layer.
|
| + UIGraphicsBeginImageContextWithOptions(inputView.bounds.size,
|
| + inputView.opaque, 0);
|
| + [inputView.layer renderInContext:UIGraphicsGetCurrentContext()];
|
| + UIImage* screenshot = UIGraphicsGetImageFromCurrentImageContext();
|
| + UIGraphicsEndImageContext();
|
| + UIView* view = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
|
| + [view layer].contents = static_cast<id>(screenshot.CGImage);
|
| + return view;
|
| + } else {
|
| + // When the input view is nil, we can't generate a snapshot so a placeholder
|
| + // is returned.
|
| + UIColor* backgroundColor = [tabModel isOffTheRecord]
|
| + ? [[MDCPalette greyPalette] tint700]
|
| + : [[MDCPalette greyPalette] tint100];
|
| + UIView* placeholdView =
|
| + [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
|
| + placeholdView.backgroundColor = backgroundColor;
|
| + return placeholdView;
|
| + }
|
| +}
|
| +
|
| +- (TabSwitcherTransitionContextContent*)transitionContextContextForTabModel:
|
| + (TabModel*)tabModel {
|
| + if ([tabModel isOffTheRecord])
|
| + return self.transitionContext.incognitoContent;
|
| + else
|
| + return self.transitionContext.regularContent;
|
| +}
|
| +
|
| +- (UIImage*)updateScreenshotForCellIfNeeded:(TabSwitcherLocalSessionCell*)cell
|
| + tabModel:(TabModel*)tabModel {
|
| + if (cell.screenshot)
|
| + return cell.screenshot;
|
| + UIColor* backgroundColor = [tabModel isOffTheRecord]
|
| + ? [[MDCPalette greyPalette] tint700]
|
| + : [UIColor whiteColor];
|
| + CGRect rect = CGRectMake(0, 0, 1, 1);
|
| + UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
|
| + [backgroundColor setFill];
|
| + CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
|
| + UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
|
| + UIGraphicsEndImageContext();
|
| + return image;
|
| +}
|
| +
|
| +- (void)performTabSwitcherTransition:(TransitionType)transitionType
|
| + withModel:(TabModel*)tabModel
|
| + animated:(BOOL)animated
|
| + withCompletion:(ProceduralBlock)completion {
|
| + switch (transitionType) {
|
| + case TransitionType::TRANSITION_PRESENT:
|
| + base::RecordAction(base::UserMetricsAction("MobileTabSwitcherPresented"));
|
| + break;
|
| + case TransitionType::TRANSITION_DISMISS:
|
| + base::RecordAction(base::UserMetricsAction("MobileTabSwitcherDismissed"));
|
| + break;
|
| + }
|
| + DCHECK(completion);
|
| + DCHECK([self transitionContext]);
|
| + [[self view] setUserInteractionEnabled:NO];
|
| +
|
| + TabSwitcherTransitionContextContent* transitionContextContent =
|
| + [self transitionContextContextForTabModel:tabModel];
|
| + DCHECK(transitionContextContent);
|
| +
|
| + ToolbarController* toolbarController =
|
| + [[self.delegate tabSwitcherTransitionToolbarOwner]
|
| + relinquishedToolbarController];
|
| + Tab* selectedTab = [tabModel currentTab];
|
| +
|
| + NSInteger selectedTabIndex = [tabModel indexOfTab:selectedTab];
|
| + TabSwitcherLocalSessionCell* selectedCell = nil;
|
| + CGRect selectedCellFrame = CGRectZero;
|
| + TabSwitcherPanelController* selectedPanel =
|
| + [self panelControllerForTabModel:tabModel];
|
| + // Force invalidation and update of the tab switcher view and collection view
|
| + // layout to take into account any changes in the size of the view controller
|
| + // while the tab switcher was not shown.
|
| + if (transitionType == TransitionType::TRANSITION_PRESENT) {
|
| + [[self view] layoutIfNeeded];
|
| + [[selectedPanel collectionView].collectionViewLayout invalidateLayout];
|
| + }
|
| + if (selectedTabIndex != NSNotFound) {
|
| + selectedCell =
|
| + [selectedPanel localSessionCellForTabAtIndex:selectedTabIndex];
|
| + selectedCellFrame =
|
| + [selectedPanel localSessionCellFrameForTabAtIndex:selectedTabIndex];
|
| +
|
| + const CGFloat collectionViewContentOffsetMinY =
|
| + [selectedPanel collectionView].contentOffset.y;
|
| + const CGFloat collectionViewContentOffsetMaxY =
|
| + collectionViewContentOffsetMinY +
|
| + [selectedPanel collectionView].bounds.size.height;
|
| + const BOOL isCellTotallyHidden =
|
| + CGRectGetMaxY(selectedCellFrame) < collectionViewContentOffsetMinY ||
|
| + CGRectGetMinY(selectedCellFrame) > collectionViewContentOffsetMaxY;
|
| + if (transitionType == TransitionType::TRANSITION_PRESENT ||
|
| + isCellTotallyHidden) {
|
| + [selectedPanel scrollTabIndexToVisible:selectedTabIndex
|
| + triggerLayout:YES];
|
| + }
|
| + } else {
|
| + CGRect collectionViewFrame = [selectedPanel.collectionView frame];
|
| + selectedCellFrame = CGRectMake(collectionViewFrame.size.width / 2,
|
| + collectionViewFrame.size.height / 2, 0, 0);
|
| + }
|
| + // Compute initial and final tab frames.
|
| + const CGFloat initialTabFrameOriginY = kHeaderHeight + StatusBarHeight();
|
| + CGRect initialTabFrame =
|
| + CGRectMake(0, initialTabFrameOriginY, self.view.bounds.size.width,
|
| + self.view.bounds.size.height - initialTabFrameOriginY);
|
| + CGRect finalTabFrame =
|
| + [[selectedPanel collectionView] convertRect:selectedCellFrame
|
| + toView:self.view];
|
| +
|
| + // Compute initial and final toolbar screenshot frames.
|
| + const CGRect initialToolbarFrame = toolbarController.view.frame;
|
| + CGRect initialToolbarScreenshotFrame = CGRectMake(
|
| + 0, 0, initialToolbarFrame.size.width, initialToolbarFrame.size.height);
|
| +
|
| + const CGFloat cellTopBarHeight = tabSwitcherLocalSessionCellTopBarHeight();
|
| + CGRect finalToolbarScreenshotFrame =
|
| + CGRectMake(0, 0, selectedCellFrame.size.width, cellTopBarHeight);
|
| +
|
| + base::scoped_nsobject<UIImageView> tabScreenshotImageView(
|
| + [[UIImageView alloc] initWithFrame:CGRectZero]);
|
| +
|
| + base::WeakNSObject<UIImageView> weakTabScreenshotImageView(
|
| + tabScreenshotImageView.get());
|
| +
|
| + if (self.transitionContext.initialTabModel != tabModel ||
|
| + transitionContextContent.initialSelectedTabIndex != selectedTabIndex) {
|
| + tabScreenshotImageView.get().image =
|
| + [self updateScreenshotForCellIfNeeded:selectedCell tabModel:tabModel];
|
| + [selectedTab retrieveSnapshot:^(UIImage* snapshot) {
|
| + [weakTabScreenshotImageView setImage:snapshot];
|
| + }];
|
| + } else {
|
| + tabScreenshotImageView.get().image =
|
| + self.transitionContext.tabSnapshotImage;
|
| + }
|
| +
|
| + const CGSize tabScreenshotImageSize = tabScreenshotImageView.get().image.size;
|
| +
|
| + CGRect initialTabScreenshotFrame = CGRectZero;
|
| + const CGSize toolbarSize = toolbarController.view.bounds.size;
|
| + CGSize initialTabTargetSize =
|
| + CGSizeMake(initialTabFrame.size.width,
|
| + initialTabFrame.size.height - toolbarSize.height);
|
| + CGSize revisedTargetSize = CGSizeZero;
|
| + CalculateProjection(tabScreenshotImageSize, initialTabTargetSize,
|
| + ProjectionMode::kAspectFill, revisedTargetSize,
|
| + initialTabScreenshotFrame);
|
| + initialTabScreenshotFrame.origin.y += toolbarSize.height;
|
| +
|
| + CGSize finalTabTargetSize =
|
| + CGSizeMake(selectedCellFrame.size.width,
|
| + selectedCellFrame.size.height - cellTopBarHeight);
|
| + CGRect finalTabScreenshotFrame;
|
| + CalculateProjection(tabScreenshotImageSize, finalTabTargetSize,
|
| + ProjectionMode::kAspectFill, revisedTargetSize,
|
| + finalTabScreenshotFrame);
|
| + finalTabScreenshotFrame.origin.y += cellTopBarHeight;
|
| +
|
| + TabSwitcherTabStripPlaceholderView* tabStripPlaceholderView =
|
| + [transitionContextContent generateTabStripPlaceholderView];
|
| + tabStripPlaceholderView.clipsToBounds = YES;
|
| + tabStripPlaceholderView.backgroundColor = [UIColor clearColor];
|
| + [self.view addSubview:tabStripPlaceholderView];
|
| +
|
| + CGRect tabStripInitialFrame =
|
| + CGRectMake(0, StatusBarHeight(), self.view.bounds.size.width,
|
| + tabStripPlaceholderView.frame.size.height);
|
| + CGRect tabStripFinalFrame =
|
| + CGRectMake(finalTabFrame.origin.x,
|
| + finalTabFrame.origin.y - tabStripInitialFrame.size.height,
|
| + finalTabFrame.size.width, tabStripInitialFrame.size.height);
|
| + CGRect shadowInitialFrame =
|
| + CGRectMake(0, CGRectGetMaxY(initialToolbarScreenshotFrame),
|
| + initialToolbarScreenshotFrame.size.width, 2);
|
| + CGRect shadowFinalFrame =
|
| + CGRectMake(0, CGRectGetMaxY(finalToolbarScreenshotFrame),
|
| + finalToolbarScreenshotFrame.size.width, 2);
|
| +
|
| + if (transitionType == TransitionType::TRANSITION_DISMISS) {
|
| + [tabStripPlaceholderView unfoldWithCompletion:nil];
|
| + std::swap(initialTabFrame, finalTabFrame);
|
| + std::swap(tabStripInitialFrame, tabStripFinalFrame);
|
| + std::swap(shadowInitialFrame, shadowFinalFrame);
|
| + std::swap(initialTabScreenshotFrame, finalTabScreenshotFrame);
|
| + std::swap(initialToolbarScreenshotFrame, finalToolbarScreenshotFrame);
|
| + } else {
|
| + [tabStripPlaceholderView foldWithCompletion:^{
|
| + [tabStripPlaceholderView removeFromSuperview];
|
| + }];
|
| + }
|
| +
|
| + tabStripPlaceholderView.frame = tabStripInitialFrame;
|
| +
|
| + // Create and setup placeholder view and subviews.
|
| + base::scoped_nsobject<UIView> placeholderView(
|
| + [[UIView alloc] initWithFrame:initialTabFrame]);
|
| + [placeholderView setClipsToBounds:YES];
|
| + [placeholderView setUserInteractionEnabled:NO];
|
| +
|
| + tabScreenshotImageView.get().frame = initialTabScreenshotFrame;
|
| + tabScreenshotImageView.get().contentMode = UIViewContentModeScaleToFill;
|
| + tabScreenshotImageView.get().autoresizingMask = UIViewAutoresizingNone;
|
| + [placeholderView addSubview:tabScreenshotImageView];
|
| +
|
| + // Try using a snapshot view for dismissal animation because it's faster and
|
| + // the collection view has already been rendered. Use a client rendering
|
| + // otherwise.
|
| + SnapshotViewOption snapshotOption = SnapshotViewOption::SNAPSHOT_VIEW;
|
| + if (transitionType == TransitionType::TRANSITION_PRESENT)
|
| + snapshotOption = SnapshotViewOption::CLIENT_RENDERING;
|
| +
|
| + UIView* finalToolbarScreenshotImageView =
|
| + [self snapshotViewForView:selectedCell.topBar
|
| + withModel:tabModel
|
| + option:snapshotOption];
|
| + finalToolbarScreenshotImageView.autoresizingMask = UIViewAutoresizingNone;
|
| + finalToolbarScreenshotImageView.frame = initialToolbarScreenshotFrame;
|
| + [placeholderView addSubview:finalToolbarScreenshotImageView];
|
| +
|
| + UIView* toolbarScreenshotImageView =
|
| + transitionContextContent.toolbarSnapshotView;
|
| + toolbarScreenshotImageView.autoresizingMask = UIViewAutoresizingNone;
|
| + toolbarScreenshotImageView.frame = initialToolbarScreenshotFrame;
|
| + [placeholderView addSubview:toolbarScreenshotImageView];
|
| +
|
| + base::scoped_nsobject<UIImageView> toolbarShadowImageView(
|
| + [[UIImageView alloc] initWithFrame:shadowInitialFrame]);
|
| + ResourceBundle& rb = ResourceBundle::GetSharedInstance();
|
| + gfx::Image shadow = rb.GetNativeImageNamed(IDR_IOS_TOOLBAR_SHADOW);
|
| + [toolbarShadowImageView setAutoresizingMask:UIViewAutoresizingNone];
|
| + [toolbarShadowImageView setImage:shadow.ToUIImage()];
|
| + [placeholderView addSubview:toolbarShadowImageView];
|
| +
|
| + [self.view addSubview:placeholderView];
|
| +
|
| + [selectedCell setHidden:YES];
|
| + toolbarScreenshotImageView.alpha =
|
| + (transitionType == TransitionType::TRANSITION_DISMISS) ? 0 : 1.0;
|
| +
|
| + base::WeakNSObject<TabSwitcherController> weakSelf(self);
|
| + void (^completionBlock)(BOOL) = ^(BOOL) {
|
| + base::scoped_nsobject<TabSwitcherController> strongSelf([weakSelf retain]);
|
| +
|
| + [tabStripPlaceholderView removeFromSuperview];
|
| + [toolbarScreenshotImageView removeFromSuperview];
|
| + [selectedCell setHidden:NO];
|
| + [placeholderView removeFromSuperview];
|
| +
|
| + if (transitionType == TransitionType::TRANSITION_DISMISS)
|
| + [strongSelf restoreWindowBackgroundColor];
|
| + [[[strongSelf delegate] tabSwitcherTransitionToolbarOwner]
|
| + reparentToolbarController];
|
| + [[strongSelf view] setUserInteractionEnabled:YES];
|
| + completion();
|
| + };
|
| +
|
| + ProceduralBlock animationBlock = ^{
|
| + toolbarScreenshotImageView.alpha =
|
| + transitionType == TransitionType::TRANSITION_DISMISS ? 1.0 : 0;
|
| + tabStripPlaceholderView.frame = tabStripFinalFrame;
|
| + toolbarShadowImageView.get().frame = shadowFinalFrame;
|
| + placeholderView.get().frame = finalTabFrame;
|
| + toolbarScreenshotImageView.frame = finalToolbarScreenshotFrame;
|
| + finalToolbarScreenshotImageView.frame = finalToolbarScreenshotFrame;
|
| + tabScreenshotImageView.get().frame = finalTabScreenshotFrame;
|
| + };
|
| +
|
| + [UIView animateWithDuration:animated ? kTransitionAnimationDuration : 0
|
| + delay:0
|
| + options:UIViewAnimationCurveEaseInOut
|
| + animations:animationBlock
|
| + completion:completionBlock];
|
| +}
|
| +
|
| +- (void)updateLocalPanelsCells {
|
| + auto mainTabPanel =
|
| + [self panelControllerForTabModel:[_tabSwitcherModel mainTabModel]];
|
| + auto otrTabPanel =
|
| + [self panelControllerForTabModel:[_tabSwitcherModel otrTabModel]];
|
| + [mainTabPanel reload];
|
| + [otrTabPanel reload];
|
| +}
|
| +
|
| +- (NSInteger)currentPanelIndex {
|
| + return [_tabSwitcherView currentPanelIndex];
|
| +}
|
| +
|
| +- (int)offsetToDistantSessionPanels {
|
| + if (_signInPanelType == TabSwitcherSignInPanelsType::NO_PANEL) {
|
| + return kSignInPromoPanelIndex;
|
| + }
|
| + return kSignInPromoPanelIndex + 1;
|
| +}
|
| +
|
| +- (ios_internal::SessionType)sessionTypeForPanelIndex:(NSInteger)panelIndex {
|
| + if (panelIndex == kLocalTabsOffTheRecordPanelIndex)
|
| + return ios_internal::SessionType::OFF_THE_RECORD_SESSION;
|
| + if (panelIndex == kLocalTabsOnTheRecordPanelIndex)
|
| + return ios_internal::SessionType::REGULAR_SESSION;
|
| + return ios_internal::SessionType::DISTANT_SESSION;
|
| +}
|
| +
|
| +- (TabModel*)tabModelForSessionType:(ios_internal::SessionType)sessionType {
|
| + switch (sessionType) {
|
| + case ios_internal::SessionType::REGULAR_SESSION:
|
| + return [_tabSwitcherModel mainTabModel];
|
| + break;
|
| + case ios_internal::SessionType::OFF_THE_RECORD_SESSION:
|
| + return [_tabSwitcherModel otrTabModel];
|
| + break;
|
| + case ios_internal::SessionType::DISTANT_SESSION:
|
| + return nil;
|
| + break;
|
| + }
|
| +}
|
| +
|
| +- (TabModel*)currentSelectedModel {
|
| + const NSInteger currentPanelIndex = [self currentPanelIndex];
|
| + const ios_internal::SessionType sessionType =
|
| + [self sessionTypeForPanelIndex:currentPanelIndex];
|
| + TabModel* model = [self tabModelForSessionType:sessionType];
|
| + if (!model)
|
| + model = _onLoadActiveModel;
|
| + return model;
|
| +}
|
| +
|
| +- (void)selectPanelForTabModel:(TabModel*)selectedTabModel {
|
| + DCHECK(selectedTabModel == [_tabSwitcherModel otrTabModel] ||
|
| + selectedTabModel == [_tabSwitcherModel mainTabModel]);
|
| + NSInteger selectedPanel =
|
| + (selectedTabModel == [_tabSwitcherModel otrTabModel])
|
| + ? kLocalTabsOffTheRecordPanelIndex
|
| + : kLocalTabsOnTheRecordPanelIndex;
|
| + [_tabSwitcherView selectPanelAtIndex:selectedPanel];
|
| +}
|
| +
|
| +- (TabSwitcherPanelController*)panelControllerForTabModel:(TabModel*)tabModel {
|
| + DCHECK(tabModel == [_tabSwitcherModel mainTabModel] ||
|
| + tabModel == [_tabSwitcherModel otrTabModel]);
|
| + if (tabModel == [_tabSwitcherModel mainTabModel])
|
| + return _onTheRecordSession.get();
|
| + if (tabModel == [_tabSwitcherModel otrTabModel])
|
| + return _offTheRecordSession.get();
|
| + return nil;
|
| +}
|
| +
|
| +- (void)tabSwitcherDismissWithModel:(TabModel*)model {
|
| + [self tabSwitcherDismissWithModel:model animated:YES];
|
| +}
|
| +
|
| +- (void)tabSwitcherDismissWithModel:(TabModel*)model animated:(BOOL)animated {
|
| + [self tabSwitcherDismissWithModel:model
|
| + animated:animated
|
| + withCompletion:^{
|
| + [self.delegate tabSwitcherDismissTransitionDidEnd:self];
|
| + }];
|
| +}
|
| +
|
| +- (void)tabSwitcherDismissWithModel:(TabModel*)model
|
| + animated:(BOOL)animated
|
| + withCompletion:(ProceduralBlock)completion {
|
| + DCHECK(completion);
|
| + DCHECK(model);
|
| + [[self presentedViewController] dismissViewControllerAnimated:NO
|
| + completion:nil];
|
| + [self.delegate tabSwitcher:self
|
| + dismissTransitionWillStartWithActiveModel:model];
|
| + [self performTabSwitcherTransition:TransitionType::TRANSITION_DISMISS
|
| + withModel:model
|
| + animated:animated
|
| + withCompletion:^{
|
| + [self.view removeFromSuperview];
|
| + completion();
|
| + }];
|
| +}
|
| +
|
| +- (void)tabSwitcherDismissWithCurrentSelectedModel {
|
| + TabModel* model = [self currentSelectedModel];
|
| + base::RecordAction(base::UserMetricsAction("MobileTabSwitcherClose"));
|
| + [self tabSwitcherDismissWithModel:model];
|
| +}
|
| +
|
| +- (Tab*)dismissWithNewTabAnimation:(const GURL&)URL
|
| + atIndex:(NSUInteger)position
|
| + transition:(ui::PageTransition)transition
|
| + tabModel:(TabModel*)tabModel {
|
| + web::NavigationManager::WebLoadParams params(URL);
|
| + params.referrer = web::Referrer();
|
| + params.transition_type = transition;
|
| +
|
| + DCHECK(tabModel.browserState);
|
| +
|
| + base::scoped_nsobject<Tab> tab([[Tab alloc]
|
| + initWithWindowName:nil
|
| + opener:nil
|
| + openedByDOM:NO
|
| + model:tabModel
|
| + browserState:tabModel.browserState]);
|
| + [tab webController].webUsageEnabled = tabModel.webUsageEnabled;
|
| +
|
| + ProceduralBlock dismissWithNewTab = ^{
|
| + NSUInteger tabIndex = position;
|
| + if (position > tabModel.count)
|
| + tabIndex = tabModel.count;
|
| + [tabModel insertTab:tab atIndex:tabIndex];
|
| +
|
| + if (tabModel.tabUsageRecorder)
|
| + tabModel.tabUsageRecorder->TabCreatedForSelection(tab);
|
| +
|
| + [[tab webController] loadWithParams:params];
|
| +
|
| + if (tabModel.webUsageEnabled) {
|
| + [tab setWebUsageEnabled:tabModel.webUsageEnabled];
|
| + [[tab webController] triggerPendingLoad];
|
| + }
|
| +
|
| + NSDictionary* userInfo = @{
|
| + kTabModelTabKey : tab,
|
| + kTabModelOpenInBackgroundKey : @(NO),
|
| + };
|
| +
|
| + [[NSNotificationCenter defaultCenter]
|
| + postNotificationName:kTabModelNewTabWillOpenNotification
|
| + object:self
|
| + userInfo:userInfo];
|
| +
|
| + [tabModel setCurrentTab:tab];
|
| +
|
| + [self
|
| + tabSwitcherDismissWithModel:tabModel
|
| + animated:YES
|
| + withCompletion:^{
|
| + [self.delegate tabSwitcherDismissTransitionDidEnd:self];
|
| + }];
|
| + };
|
| +
|
| + dismissWithNewTab();
|
| + return tab;
|
| +}
|
| +
|
| +- (BOOL)isPanelIndexForLocalSession:(NSUInteger)panelIndex {
|
| + return panelIndex == kLocalTabsOnTheRecordPanelIndex ||
|
| + panelIndex == kLocalTabsOffTheRecordPanelIndex;
|
| +}
|
| +
|
| +- (void)openTabWithContentOfDistantTab:
|
| + (synced_sessions::DistantTab const*)distantTab {
|
| + sync_sessions::OpenTabsUIDelegate* openTabs =
|
| + IOSChromeProfileSyncServiceFactory::GetForBrowserState(_browserState)
|
| + ->GetOpenTabsUIDelegate();
|
| + const sessions::SessionTab* toLoad = nullptr;
|
| + if (openTabs->GetForeignTab(distantTab->session_tag, distantTab->tab_id,
|
| + &toLoad)) {
|
| + TabModel* mainModel = [_tabSwitcherModel mainTabModel];
|
| + // Disable user interactions until the tab is inserted to prevent multiple
|
| + // concurrent tab model updates.
|
| + [_tabSwitcherView setUserInteractionEnabled:NO];
|
| + Tab* tab = [mainModel insertOrUpdateTabWithURL:GURL()
|
| + referrer:web::Referrer()
|
| + transition:ui::PAGE_TRANSITION_TYPED
|
| + windowName:nil
|
| + opener:nil
|
| + openedByDOM:NO
|
| + atIndex:NSNotFound
|
| + inBackground:NO];
|
| + [tab loadSessionTab:toLoad];
|
| + [mainModel setCurrentTab:tab];
|
| +
|
| + // Reenable touch events.
|
| + [_tabSwitcherView setUserInteractionEnabled:YES];
|
| + [self
|
| + tabSwitcherDismissWithModel:mainModel
|
| + animated:YES
|
| + withCompletion:^{
|
| + [self.delegate tabSwitcherDismissTransitionDidEnd:self];
|
| + }];
|
| + }
|
| +}
|
| +
|
| +- (void)removePromoPanelHeaderCellIfNeeded {
|
| + if (_shouldRemovePromoPanelHeaderCell) {
|
| + _shouldRemovePromoPanelHeaderCell = NO;
|
| + [[_tabSwitcherView headerView]
|
| + removeSessionsAtIndexes:@[ @(kSignInPromoPanelIndex) ]];
|
| + }
|
| +}
|
| +
|
| +- (void)addPromoPanelHeaderCellIfNeeded {
|
| + if (_shouldAddPromoPanelHeaderCell) {
|
| + _shouldAddPromoPanelHeaderCell = NO;
|
| + [[_tabSwitcherView headerView]
|
| + insertSessionsAtIndexes:@[ @(kSignInPromoPanelIndex) ]];
|
| + }
|
| +}
|
| +
|
| +- (void)reopenClosedTab {
|
| + sessions::TabRestoreService* const tabRestoreService =
|
| + IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
|
| + if (!tabRestoreService || tabRestoreService->entries().empty())
|
| + return;
|
| +
|
| + const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
|
| + tabRestoreService->entries().front();
|
| + // Only handle the TAB type.
|
| + if (entry->type != sessions::TabRestoreService::TAB)
|
| + return;
|
| +
|
| + [self chromeExecuteCommand:[GenericChromeCommand commandWithTag:IDC_NEW_TAB]];
|
| + TabRestoreServiceDelegateImplIOS* const delegate =
|
| + TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
|
| + _browserState);
|
| + tabRestoreService->RestoreEntryById(delegate, entry->id,
|
| + WindowOpenDisposition::CURRENT_TAB);
|
| +}
|
| +
|
| +#pragma mark - TabSwitcherModelDelegate
|
| +
|
| +- (void)distantSessionsRemovedAtSortedIndexes:(NSArray*)removedIndexes
|
| + insertedAtSortedIndexes:(NSArray*)insertedIndexes {
|
| + // Update the panels
|
| + int offset = [self offsetToDistantSessionPanels];
|
| +
|
| + // Iterate in reverse order so that indexes in |removedIndexes| stays synced
|
| + // with the indexes in |_controllersOfDistantSessions|.
|
| + for (NSNumber* objCIndex in [removedIndexes reverseObjectEnumerator]) {
|
| + int index = [objCIndex intValue];
|
| + [_tabSwitcherView removePanelViewAtIndex:index + offset];
|
| + [_controllersOfDistantSessions removeObjectAtIndex:index];
|
| + }
|
| +
|
| + for (NSNumber* objCIndex in insertedIndexes) {
|
| + int index = [objCIndex intValue];
|
| + std::string tag = [_tabSwitcherModel tagOfDistantSessionAtIndex:index];
|
| + base::scoped_nsobject<TabSwitcherPanelController> panelController(
|
| + [[TabSwitcherPanelController alloc] initWithModel:_tabSwitcherModel
|
| + forDistantSessionWithTag:tag
|
| + browserState:_browserState]);
|
| + [panelController setDelegate:self];
|
| + [_tabSwitcherView addPanelView:[panelController view]
|
| + atIndex:index + offset];
|
| + [_controllersOfDistantSessions insertObject:panelController.get()
|
| + atIndex:index];
|
| + }
|
| +
|
| + // Update the header view.
|
| + // The header view's content is created using the panel's content.
|
| + [[_tabSwitcherView headerView] reloadData];
|
| +}
|
| +
|
| +- (void)distantSessionMayNeedUpdate:(std::string const&)tag {
|
| + for (TabSwitcherPanelController* panel in _controllersOfDistantSessions
|
| + .get()) {
|
| + DCHECK([panel isKindOfClass:[TabSwitcherPanelController class]]);
|
| + if ([panel sessionTag] == tag) {
|
| + [panel updateCollectionViewIfNeeded];
|
| + return;
|
| + }
|
| + }
|
| +}
|
| +
|
| +- (void)localSessionMayNeedUpdate:(ios_internal::SessionType)type {
|
| + if (type == ios_internal::SessionType::REGULAR_SESSION) {
|
| + [_onTheRecordSession updateCollectionViewIfNeeded];
|
| + } else {
|
| + DCHECK(type == ios_internal::SessionType::OFF_THE_RECORD_SESSION);
|
| + [_offTheRecordSession updateCollectionViewIfNeeded];
|
| + }
|
| +}
|
| +
|
| +- (void)signInPanelChangedTo:(TabSwitcherSignInPanelsType)newPanelType {
|
| + if (_signInPanelType == newPanelType)
|
| + return;
|
| +
|
| + // Remove promo panel that is no longer relevant, if any.
|
| + if (_signInPanelType != TabSwitcherSignInPanelsType::NO_PANEL) {
|
| + _shouldRemovePromoPanelHeaderCell = YES;
|
| + BOOL updateScrollView =
|
| + [_tabSwitcherView currentPanelIndex] != kSignInPromoPanelIndex;
|
| + [_tabSwitcherView removePanelViewAtIndex:kSignInPromoPanelIndex
|
| + updateScrollView:updateScrollView];
|
| + } else {
|
| + _shouldAddPromoPanelHeaderCell = YES;
|
| + }
|
| + [self addPromoPanelForSignInPanelType:newPanelType];
|
| +}
|
| +
|
| +- (void)addPromoPanelForSignInPanelType:(TabSwitcherSignInPanelsType)panelType {
|
| + _signInPanelType = panelType;
|
| + if (panelType != TabSwitcherSignInPanelsType::NO_PANEL) {
|
| + TabSwitcherPanelOverlayView* panelView =
|
| + [[[TabSwitcherPanelOverlayView alloc] initWithFrame:CGRectZero
|
| + browserState:_browserState]
|
| + autorelease];
|
| + [panelView setOverlayType:PanelOverlayTypeFromSignInPanelsType(panelType)];
|
| + [_tabSwitcherView addPanelView:panelView atIndex:kSignInPromoPanelIndex];
|
| + }
|
| +}
|
| +
|
| +- (CGSize)sizeForItemAtIndex:(NSUInteger)index
|
| + inSession:(ios_internal::SessionType)session {
|
| + switch (session) {
|
| + case ios_internal::SessionType::OFF_THE_RECORD_SESSION:
|
| + return [[_offTheRecordSession view] cellSize];
|
| + case ios_internal::SessionType::REGULAR_SESSION:
|
| + return [[_onTheRecordSession view] cellSize];
|
| + case ios_internal::SessionType::DISTANT_SESSION:
|
| + NOTREACHED();
|
| + return {};
|
| + }
|
| +}
|
| +
|
| +#pragma mark - TabSwitcherHeaderViewDelegate
|
| +
|
| +- (void)tabSwitcherHeaderViewDismiss:(TabSwitcherHeaderView*)view {
|
| + [self tabSwitcherDismissWithCurrentSelectedModel];
|
| +}
|
| +
|
| +- (void)tabSwitcherHeaderViewDidSelectSessionAtIndex:(NSInteger)index {
|
| + switch (index) {
|
| + case kLocalTabsOffTheRecordPanelIndex:
|
| + base::RecordAction(base::UserMetricsAction(
|
| + "MobileTabSwitcherHeaderViewSelectIncognitoPanel"));
|
| + break;
|
| + case kLocalTabsOnTheRecordPanelIndex:
|
| + base::RecordAction(base::UserMetricsAction(
|
| + "MobileTabSwitcherHeaderViewSelectNonIncognitoPanel"));
|
| + break;
|
| + default:
|
| + base::RecordAction(base::UserMetricsAction(
|
| + "MobileTabSwitcherHeaderViewSelectDistantSessionPanel"));
|
| + break;
|
| + }
|
| + [_tabSwitcherView selectPanelAtIndex:index];
|
| +}
|
| +
|
| +#pragma mark - TabSwitcherHeaderViewDataSource
|
| +
|
| +- (NSInteger)tabSwitcherHeaderViewSessionCount {
|
| + NSInteger promoPanel = (![_tabSwitcherModel distantSessionCount]) ? 1 : 0;
|
| + return [_tabSwitcherModel sessionCount] + promoPanel;
|
| +}
|
| +
|
| +- (SessionCellData*)sessionCellDataAtIndex:(NSUInteger)index {
|
| + if (index == kLocalTabsOffTheRecordPanelIndex) {
|
| + // If has incognito tabs return incognito cell data.
|
| + return [SessionCellData incognitoSessionCellData];
|
| + } else if (index == kLocalTabsOnTheRecordPanelIndex) {
|
| + return [SessionCellData openTabSessionCellData];
|
| + } else {
|
| + if (![_tabSwitcherModel distantSessionCount]) {
|
| + // Display promo panel cell if there is no distant sessions.
|
| + return [SessionCellData otherDevicesSessionCellData];
|
| + } else {
|
| + index -= kHeaderDistantSessionIndexOffset;
|
| +
|
| + sync_sessions::SyncedSession::DeviceType deviceType =
|
| + sync_sessions::SyncedSession::TYPE_UNSET;
|
| + NSString* cellTitle = nil;
|
| +
|
| + if (index < _controllersOfDistantSessions.get().count) {
|
| + TabSwitcherPanelController* panel =
|
| + [_controllersOfDistantSessions objectAtIndex:index];
|
| + const synced_sessions::DistantSession* distantSession =
|
| + [panel distantSession];
|
| + deviceType = distantSession->device_type;
|
| + cellTitle = base::SysUTF8ToNSString(distantSession->name);
|
| + }
|
| + ios_internal::SessionCellType cellType;
|
| + switch (deviceType) {
|
| + case sync_sessions::SyncedSession::TYPE_PHONE:
|
| + cellType = ios_internal::kPhoneRemoteSessionCell;
|
| + break;
|
| + case sync_sessions::SyncedSession::TYPE_TABLET:
|
| + cellType = ios_internal::kTabletRemoteSessionCell;
|
| + break;
|
| + default:
|
| + cellType = ios_internal::kLaptopRemoteSessionCell;
|
| + break;
|
| + }
|
| + SessionCellData* sessionData = [[[SessionCellData alloc]
|
| + initWithSessionCellType:cellType] autorelease];
|
| + sessionData.title = cellTitle;
|
| + return sessionData;
|
| + }
|
| + }
|
| +}
|
| +
|
| +- (NSInteger)tabSwitcherHeaderViewSelectedPanelIndex {
|
| + return [_tabSwitcherView currentPanelIndex];
|
| +}
|
| +
|
| +#pragma mark - TabSwitcherViewDelegate
|
| +
|
| +- (void)openNewTabInPanelAtIndex:(NSInteger)panelIndex {
|
| + CHECK(panelIndex >= 0);
|
| + DCHECK([self isPanelIndexForLocalSession:panelIndex]);
|
| + const NSInteger tag = (panelIndex == kLocalTabsOnTheRecordPanelIndex)
|
| + ? IDC_NEW_TAB
|
| + : IDC_NEW_INCOGNITO_TAB;
|
| + if (tag == IDC_NEW_INCOGNITO_TAB) {
|
| + base::RecordAction(
|
| + base::UserMetricsAction("MobileTabSwitcherCreateIncognitoTab"));
|
| + } else {
|
| + base::RecordAction(
|
| + base::UserMetricsAction("MobileTabSwitcherCreateNonIncognitoTab"));
|
| + }
|
| + // Create and execute command to create the tab.
|
| + base::scoped_nsobject<GenericChromeCommand> command(
|
| + [[GenericChromeCommand alloc] initWithTag:tag]);
|
| + [self chromeExecuteCommand:command];
|
| +}
|
| +
|
| +- (ios_internal::NewTabButtonStyle)buttonStyleForPanelAtIndex:
|
| + (NSInteger)panelIndex {
|
| + CHECK(panelIndex >= 0);
|
| + switch (panelIndex) {
|
| + case kLocalTabsOnTheRecordPanelIndex:
|
| + if ([_onTheRecordSession shouldShowNewTabButton]) {
|
| + return ios_internal::NewTabButtonStyle::BLUE;
|
| + } else {
|
| + return ios_internal::NewTabButtonStyle::HIDDEN;
|
| + }
|
| + case kLocalTabsOffTheRecordPanelIndex:
|
| + if ([_offTheRecordSession shouldShowNewTabButton]) {
|
| + return ios_internal::NewTabButtonStyle::GRAY;
|
| + } else {
|
| + return ios_internal::NewTabButtonStyle::HIDDEN;
|
| + }
|
| + default:
|
| + return ios_internal::NewTabButtonStyle::HIDDEN;
|
| + }
|
| +}
|
| +
|
| +- (BOOL)shouldShowDismissButtonForPanelAtIndex:(NSInteger)panelIndex {
|
| + CHECK(panelIndex >= 0);
|
| + switch (panelIndex) {
|
| + case kLocalTabsOnTheRecordPanelIndex:
|
| + return [[_tabSwitcherModel mainTabModel] count] != 0;
|
| + case kLocalTabsOffTheRecordPanelIndex:
|
| + return [[_tabSwitcherModel otrTabModel] count] != 0;
|
| + default:
|
| + return [[self currentSelectedModel] count] != 0;
|
| + }
|
| +}
|
| +
|
| +- (void)tabSwitcherViewDelegateDismissTabSwitcher:(TabSwitcherView*)view {
|
| + [self tabSwitcherDismissWithCurrentSelectedModel];
|
| +}
|
| +
|
| +#pragma mark - TabSwitcherPanelControllerDelegate
|
| +
|
| +- (void)tabSwitcherPanelController:
|
| + (TabSwitcherPanelController*)tabSwitcherPanelController
|
| + didSelectDistantTab:(synced_sessions::DistantTab*)tab {
|
| + DCHECK(tab);
|
| + [self openTabWithContentOfDistantTab:tab];
|
| + base::RecordAction(
|
| + base::UserMetricsAction("MobileTabSwitcherOpenDistantTab"));
|
| +}
|
| +
|
| +- (void)tabSwitcherPanelController:
|
| + (TabSwitcherPanelController*)tabSwitcherPanelController
|
| + didSelectLocalTab:(Tab*)tab {
|
| + DCHECK(tab);
|
| + const ios_internal::SessionType panelSessionType =
|
| + tabSwitcherPanelController.sessionType;
|
| + TabModel* tabModel = [self tabModelForSessionType:panelSessionType];
|
| + [tabModel setCurrentTab:tab];
|
| + [self.delegate tabSwitcher:self
|
| + dismissTransitionWillStartWithActiveModel:tabModel];
|
| + [self tabSwitcherDismissWithModel:tabModel];
|
| + if (panelSessionType == ios_internal::SessionType::OFF_THE_RECORD_SESSION) {
|
| + base::RecordAction(
|
| + base::UserMetricsAction("MobileTabSwitcherOpenIncognitoTab"));
|
| + } else {
|
| + base::RecordAction(
|
| + base::UserMetricsAction("MobileTabSwitcherOpenNonIncognitoTab"));
|
| + }
|
| +}
|
| +
|
| +- (void)tabSwitcherPanelController:
|
| + (TabSwitcherPanelController*)tabSwitcherPanelController
|
| + didCloseLocalTab:(Tab*)tab {
|
| + DCHECK(tab);
|
| + const ios_internal::SessionType panelSessionType =
|
| + tabSwitcherPanelController.sessionType;
|
| + [tab close];
|
| + if (panelSessionType == ios_internal::SessionType::OFF_THE_RECORD_SESSION) {
|
| + base::RecordAction(
|
| + base::UserMetricsAction("MobileTabSwitcherCloseIncognitoTab"));
|
| + } else {
|
| + base::RecordAction(
|
| + base::UserMetricsAction("MobileTabSwitcherCloseNonIncognitoTab"));
|
| + }
|
| +}
|
| +
|
| +- (void)tabSwitcherPanelControllerDidUpdateOverlayViewVisibility:
|
| + (TabSwitcherPanelController*)tabSwitcherPanelController {
|
| + [_tabSwitcherView updateOverlayButtonState];
|
| +}
|
| +
|
| +@end
|
|
|