1// Copyright 2018 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/popup_menu/popup_menu_coordinator.h" 6 7#include "base/check.h" 8#include "base/metrics/histogram_macros.h" 9#include "base/metrics/user_metrics.h" 10#include "base/metrics/user_metrics_action.h" 11#include "ios/chrome/browser/application_context.h" 12#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h" 13#include "ios/chrome/browser/browser_state/chrome_browser_state.h" 14#include "ios/chrome/browser/feature_engagement/tracker_factory.h" 15#import "ios/chrome/browser/main/browser.h" 16#import "ios/chrome/browser/overlays/public/overlay_presenter.h" 17#include "ios/chrome/browser/reading_list/reading_list_model_factory.h" 18#import "ios/chrome/browser/search_engines/template_url_service_factory.h" 19#import "ios/chrome/browser/ui/browser_container/browser_container_mediator.h" 20#import "ios/chrome/browser/ui/bubble/bubble_presenter.h" 21#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h" 22#import "ios/chrome/browser/ui/commands/browser_commands.h" 23#import "ios/chrome/browser/ui/commands/command_dispatcher.h" 24#import "ios/chrome/browser/ui/commands/find_in_page_commands.h" 25#import "ios/chrome/browser/ui/commands/popup_menu_commands.h" 26#import "ios/chrome/browser/ui/popup_menu/popup_menu_action_handler.h" 27#import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h" 28#import "ios/chrome/browser/ui/popup_menu/popup_menu_mediator.h" 29#import "ios/chrome/browser/ui/popup_menu/public/popup_menu_presenter.h" 30#import "ios/chrome/browser/ui/popup_menu/public/popup_menu_presenter_delegate.h" 31#import "ios/chrome/browser/ui/popup_menu/public/popup_menu_table_view_controller.h" 32#import "ios/chrome/browser/ui/presenters/contained_presenter_delegate.h" 33#import "ios/chrome/browser/ui/util/layout_guide_names.h" 34 35#if !defined(__has_feature) || !__has_feature(objc_arc) 36#error "This file requires ARC support." 37#endif 38 39namespace { 40// Returns the corresponding command type for a Popup menu |type|. 41PopupMenuCommandType CommandTypeFromPopupType(PopupMenuType type) { 42 if (type == PopupMenuTypeToolsMenu) 43 return PopupMenuCommandTypeToolsMenu; 44 return PopupMenuCommandTypeDefault; 45} 46} // namespace 47 48@interface PopupMenuCoordinator () <PopupMenuCommands, 49 PopupMenuPresenterDelegate> 50 51// Presenter for the popup menu, managing the animations. 52@property(nonatomic, strong) PopupMenuPresenter* presenter; 53// Mediator for the popup menu. 54@property(nonatomic, strong) PopupMenuMediator* mediator; 55// Mediator to that alerts the main |mediator| when the web content area 56// is blocked by an overlay. 57@property(nonatomic, strong) BrowserContainerMediator* contentBlockerMediator; 58// ViewController for this mediator. 59@property(nonatomic, strong) PopupMenuTableViewController* viewController; 60// Handles user interaction with the popup menu items. 61@property(nonatomic, strong) PopupMenuActionHandler* actionHandler; 62// Time when the presentation of the popup menu is requested. 63@property(nonatomic, assign) NSTimeInterval requestStartTime; 64 65@end 66 67@implementation PopupMenuCoordinator 68 69@synthesize mediator = _mediator; 70@synthesize presenter = _presenter; 71@synthesize requestStartTime = _requestStartTime; 72@synthesize UIUpdater = _UIUpdater; 73@synthesize bubblePresenter = _bubblePresenter; 74@synthesize viewController = _viewController; 75 76#pragma mark - ChromeCoordinator 77 78- (void)start { 79 [self.browser->GetCommandDispatcher() 80 startDispatchingToTarget:self 81 forProtocol:@protocol(PopupMenuCommands)]; 82 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; 83 [defaultCenter addObserver:self 84 selector:@selector(applicationDidEnterBackground:) 85 name:UIApplicationDidEnterBackgroundNotification 86 object:nil]; 87} 88 89- (void)stop { 90 [self.browser->GetCommandDispatcher() stopDispatchingToTarget:self]; 91 [self.mediator disconnect]; 92 self.mediator = nil; 93 self.viewController = nil; 94} 95 96#pragma mark - Public 97 98- (BOOL)isShowingPopupMenu { 99 return self.presenter != nil; 100} 101 102#pragma mark - PopupMenuCommands 103 104- (void)showNavigationHistoryBackPopupMenu { 105 base::RecordAction( 106 base::UserMetricsAction("MobileToolbarShowTabHistoryMenu")); 107 [self presentPopupOfType:PopupMenuTypeNavigationBackward 108 fromNamedGuide:kBackButtonGuide]; 109} 110 111- (void)showNavigationHistoryForwardPopupMenu { 112 base::RecordAction( 113 base::UserMetricsAction("MobileToolbarShowTabHistoryMenu")); 114 [self presentPopupOfType:PopupMenuTypeNavigationForward 115 fromNamedGuide:kForwardButtonGuide]; 116} 117 118- (void)showToolsMenuPopup { 119 // The metric is registered at the toolbar level. 120 [self presentPopupOfType:PopupMenuTypeToolsMenu 121 fromNamedGuide:kToolsMenuGuide]; 122} 123 124- (void)showTabGridButtonPopup { 125 base::RecordAction(base::UserMetricsAction("MobileToolbarShowTabGridMenu")); 126 [self presentPopupOfType:PopupMenuTypeTabGrid 127 fromNamedGuide:kTabSwitcherGuide]; 128} 129 130- (void)showNewTabButtonPopup { 131 base::RecordAction(base::UserMetricsAction("MobileToolbarShowNewTabMenu")); 132 [self presentPopupOfType:PopupMenuTypeNewTab 133 fromNamedGuide:kNewTabButtonGuide]; 134} 135 136- (void)dismissPopupMenuAnimated:(BOOL)animated { 137 [self.UIUpdater updateUIForMenuDismissed]; 138 [self.presenter dismissAnimated:animated]; 139 self.presenter = nil; 140 [self.mediator disconnect]; 141 self.mediator = nil; 142 self.viewController = nil; 143} 144 145#pragma mark - PopupMenuLongPressDelegate 146 147- (void)longPressFocusPointChangedTo:(CGPoint)point { 148 [self.viewController focusRowAtPoint:point]; 149} 150 151- (void)longPressEndedAtPoint:(CGPoint)point { 152 [self.viewController selectRowAtPoint:point]; 153} 154 155#pragma mark - ContainedPresenterDelegate 156 157- (void)containedPresenterDidPresent:(id<ContainedPresenter>)presenter { 158 if (presenter != self.presenter) 159 return; 160 161 if (self.requestStartTime != 0) { 162 base::TimeDelta elapsed = base::TimeDelta::FromSecondsD( 163 [NSDate timeIntervalSinceReferenceDate] - self.requestStartTime); 164 UMA_HISTOGRAM_TIMES("Toolbar.ShowToolsMenuResponsiveness", elapsed); 165 // Reset the start time to ensure that whatever happens, we only record 166 // this once. 167 self.requestStartTime = 0; 168 } 169} 170 171#pragma mark - PopupMenuPresenterDelegate 172 173- (void)popupMenuPresenterWillDismiss:(PopupMenuPresenter*)presenter { 174 [self dismissPopupMenuAnimated:NO]; 175} 176 177#pragma mark - Notification callback 178 179- (void)applicationDidEnterBackground:(NSNotification*)note { 180 [self dismissPopupMenuAnimated:NO]; 181} 182 183#pragma mark - Private 184 185// Presents a popup menu of type |type| with an animation starting from 186// |guideName|. 187- (void)presentPopupOfType:(PopupMenuType)type 188 fromNamedGuide:(GuideName*)guideName { 189 if (self.presenter) 190 [self dismissPopupMenuAnimated:YES]; 191 192 // TODO(crbug.com/1045047): Use HandlerForProtocol after commands protocol 193 // clean up. 194 id<BrowserCommands> callableDispatcher = 195 static_cast<id<BrowserCommands>>(self.browser->GetCommandDispatcher()); 196 [callableDispatcher 197 prepareForPopupMenuPresentation:CommandTypeFromPopupType(type)]; 198 199 self.requestStartTime = [NSDate timeIntervalSinceReferenceDate]; 200 201 PopupMenuTableViewController* tableViewController = 202 [[PopupMenuTableViewController alloc] init]; 203 tableViewController.baseViewController = self.baseViewController; 204 if (type == PopupMenuTypeToolsMenu) { 205 tableViewController.tableView.accessibilityIdentifier = 206 kPopupMenuToolsMenuTableViewId; 207 } else if (type == PopupMenuTypeNavigationBackward || 208 type == PopupMenuTypeNavigationForward) { 209 tableViewController.tableView.accessibilityIdentifier = 210 kPopupMenuNavigationTableViewId; 211 } 212 213 self.viewController = tableViewController; 214 215 BOOL triggerNewIncognitoTabTip = NO; 216 if (type == PopupMenuTypeToolsMenu) { 217 triggerNewIncognitoTabTip = 218 self.bubblePresenter.incognitoTabTipBubblePresenter 219 .triggerFollowUpAction; 220 self.bubblePresenter.incognitoTabTipBubblePresenter.triggerFollowUpAction = 221 NO; 222 } 223 224 self.mediator = [[PopupMenuMediator alloc] 225 initWithType:type 226 isIncognito:self.browser->GetBrowserState() 227 ->IsOffTheRecord() 228 readingListModel:ReadingListModelFactory::GetForBrowserState( 229 self.browser->GetBrowserState()) 230 triggerNewIncognitoTabTip:triggerNewIncognitoTabTip 231 browserPolicyConnector:GetApplicationContext() 232 ->GetBrowserPolicyConnector()]; 233 self.mediator.engagementTracker = 234 feature_engagement::TrackerFactory::GetForBrowserState( 235 self.browser->GetBrowserState()); 236 self.mediator.webStateList = self.browser->GetWebStateList(); 237 self.mediator.dispatcher = 238 static_cast<id<BrowserCommands>>(self.browser->GetCommandDispatcher()); 239 self.mediator.bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState( 240 self.browser->GetBrowserState()); 241 self.mediator.prefService = self.browser->GetBrowserState()->GetPrefs(); 242 self.mediator.templateURLService = 243 ios::TemplateURLServiceFactory::GetForBrowserState( 244 self.browser->GetBrowserState()); 245 self.mediator.popupMenu = tableViewController; 246 OverlayPresenter* overlayPresenter = OverlayPresenter::FromBrowser( 247 self.browser, OverlayModality::kWebContentArea); 248 self.mediator.webContentAreaOverlayPresenter = overlayPresenter; 249 250 self.contentBlockerMediator = [[BrowserContainerMediator alloc] 251 initWithWebStateList:self.browser->GetWebStateList() 252 webContentAreaOverlayPresenter:overlayPresenter]; 253 self.contentBlockerMediator.consumer = self.mediator; 254 255 self.actionHandler = [[PopupMenuActionHandler alloc] init]; 256 self.actionHandler.baseViewController = self.baseViewController; 257 self.actionHandler.dispatcher = 258 static_cast<id<ApplicationCommands, BrowserCommands, FindInPageCommands, 259 LoadQueryCommands, TextZoomCommands>>( 260 self.browser->GetCommandDispatcher()); 261 self.actionHandler.commandHandler = self.mediator; 262 tableViewController.delegate = self.actionHandler; 263 264 self.presenter = [[PopupMenuPresenter alloc] init]; 265 self.presenter.baseViewController = self.baseViewController; 266 self.presenter.presentedViewController = tableViewController; 267 self.presenter.guideName = guideName; 268 self.presenter.delegate = self; 269 270 [self.UIUpdater updateUIForMenuDisplayed:type]; 271 272 [self.presenter prepareForPresentation]; 273 [self.presenter presentAnimated:YES]; 274 return; 275} 276 277@end 278