1// Copyright 2020 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 "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_mediator.h"
6
7#import "components/favicon/ios/web_favicon_driver.h"
8#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
9#import "ios/chrome/browser/chrome_url_util.h"
10#import "ios/chrome/browser/tabs/tab_title_util.h"
11#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_item.h"
12#import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_consumer.h"
13#import "ios/chrome/browser/web/tab_id_tab_helper.h"
14#import "ios/chrome/browser/web_state_list/all_web_state_observation_forwarder.h"
15#import "ios/chrome/browser/web_state_list/web_state_list.h"
16#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
17#import "ios/web/public/web_state.h"
18#import "ios/web/public/web_state_observer_bridge.h"
19#import "ui/gfx/image/image.h"
20
21#if !defined(__has_feature) || !__has_feature(objc_arc)
22#error "This file requires ARC support."
23#endif
24
25namespace {
26// Constructs a GridItem from a |web_state|.
27GridItem* CreateItem(web::WebState* web_state) {
28  TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
29  GridItem* item = [[GridItem alloc] initWithIdentifier:tab_helper->tab_id()];
30  // chrome://newtab (NTP) tabs have no title.
31  if (IsURLNtp(web_state->GetVisibleURL())) {
32    item.hidesTitle = YES;
33  }
34  item.title = tab_util::GetTabTitle(web_state);
35  return item;
36}
37
38// Constructs an array of GridItems from a |web_state_list|.
39NSArray* CreateItems(WebStateList* web_state_list) {
40  NSMutableArray* items = [[NSMutableArray alloc] init];
41  for (int i = 0; i < web_state_list->count(); i++) {
42    web::WebState* web_state = web_state_list->GetWebStateAt(i);
43    [items addObject:CreateItem(web_state)];
44  }
45  return [items copy];
46}
47
48// Returns the ID of the active tab in |web_state_list|.
49NSString* GetActiveTabId(WebStateList* web_state_list) {
50  if (!web_state_list)
51    return nil;
52
53  web::WebState* web_state = web_state_list->GetActiveWebState();
54  if (!web_state)
55    return nil;
56  TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
57  return tab_helper->tab_id();
58}
59
60// Returns the WebState with |identifier| in |web_state_list|. Returns |nullptr|
61// if not found.
62web::WebState* GetWebStateWithId(WebStateList* web_state_list,
63                                 NSString* identifier) {
64  for (int i = 0; i < web_state_list->count(); i++) {
65    web::WebState* web_state = web_state_list->GetWebStateAt(i);
66    TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
67    if ([identifier isEqualToString:tab_helper->tab_id()])
68      return web_state;
69  }
70  return nullptr;
71}
72
73}  // namespace
74
75@interface TabStripMediator () <CRWWebStateObserver, WebStateListObserving> {
76  // Bridge C++ WebStateListObserver methods to this TabStripController.
77  std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;
78  // Bridge C++ WebStateObserver methods to this TabStripController.
79  std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
80  // Forward observer methods for all WebStates in the WebStateList monitored
81  // by the TabStripMediator.
82  std::unique_ptr<AllWebStateObservationForwarder>
83      _allWebStateObservationForwarder;
84}
85
86// The consumer for this object.
87@property(nonatomic, weak) id<TabStripConsumer> consumer;
88
89@end
90
91@implementation TabStripMediator
92
93- (instancetype)initWithConsumer:(id<TabStripConsumer>)consumer {
94  if (self = [super init]) {
95    _consumer = consumer;
96  }
97  return self;
98}
99
100- (void)disconnect {
101  if (_webStateList) {
102    _allWebStateObservationForwarder.reset();
103    _webStateList->RemoveObserver(_webStateListObserver.get());
104    _webStateListObserver = nullptr;
105    _webStateList = nullptr;
106  }
107}
108
109#pragma mark - Public properties
110
111- (void)setWebStateList:(WebStateList*)webStateList {
112  if (_webStateList) {
113    _allWebStateObservationForwarder.reset();
114    _webStateList->RemoveObserver(_webStateListObserver.get());
115  }
116
117  _webStateList = webStateList;
118
119  if (_webStateList) {
120    DCHECK_GE(_webStateList->count(), 0);
121    _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
122    _webStateList->AddObserver(_webStateListObserver.get());
123
124    _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
125    // Observe all webStates of this |_webStateList|.
126    _allWebStateObservationForwarder =
127        std::make_unique<AllWebStateObservationForwarder>(
128            _webStateList, _webStateObserver.get());
129  }
130  [self populateConsumerItems];
131}
132
133#pragma mark - WebStateListObserving
134
135- (void)webStateList:(WebStateList*)webStateList
136    didDetachWebState:(web::WebState*)webState
137              atIndex:(int)atIndex {
138  [self populateConsumerItems];
139}
140
141- (void)webStateList:(WebStateList*)webStateList
142    didInsertWebState:(web::WebState*)webState
143              atIndex:(int)index
144           activating:(BOOL)activating {
145  [self populateConsumerItems];
146}
147
148#pragma mark - TabFaviconDataSource
149
150- (void)faviconForIdentifier:(NSString*)identifier
151                  completion:(void (^)(UIImage*))completion {
152  web::WebState* webState = GetWebStateWithId(_webStateList, identifier);
153  if (!webState) {
154    return;
155  }
156  // NTP tabs get no favicon.
157  if (IsURLNtp(webState->GetVisibleURL())) {
158    return;
159  }
160  UIImage* defaultFavicon =
161      webState->GetBrowserState()->IsOffTheRecord()
162          ? [UIImage imageNamed:@"default_world_favicon_incognito"]
163          : [UIImage imageNamed:@"default_world_favicon_regular"];
164  completion(defaultFavicon);
165
166  favicon::FaviconDriver* faviconDriver =
167      favicon::WebFaviconDriver::FromWebState(webState);
168  if (faviconDriver) {
169    gfx::Image favicon = faviconDriver->GetFavicon();
170    if (!favicon.IsEmpty())
171      completion(favicon.ToUIImage());
172  }
173}
174
175#pragma mark - Private
176
177// Calls |-populateItems:selectedItemID:| on the consumer.
178- (void)populateConsumerItems {
179  if (!self.webStateList)
180    return;
181  if (self.webStateList->count() > 0) {
182    [self.consumer populateItems:CreateItems(self.webStateList)
183                  selectedItemID:GetActiveTabId(self.webStateList)];
184  }
185}
186
187#pragma mark - CRWWebStateObserver
188
189- (void)webStateDidChangeTitle:(web::WebState*)webState {
190  // Assumption: the ID of the webState didn't change as a result of this load.
191  TabIdTabHelper* tabHelper = TabIdTabHelper::FromWebState(webState);
192  NSString* itemID = tabHelper->tab_id();
193  [self.consumer replaceItemID:itemID withItem:CreateItem(webState)];
194}
195
196@end
197