blob: f795bab8d5e936a2357b736ff974a33e526019d5 [file] [log] [blame]
sdefresnee65fd872016-12-19 13:38:131// Copyright 2012 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/browser_view_controller.h"
6
7#import <AssetsLibrary/AssetsLibrary.h>
8#import <MobileCoreServices/MobileCoreServices.h>
9#import <PassKit/PassKit.h>
10#import <Photos/Photos.h>
11#import <QuartzCore/QuartzCore.h>
12
13#include <stdint.h>
14#include <cmath>
15#include <memory>
16
17#include "base/base64.h"
18#include "base/command_line.h"
mathp9b4c11d2017-07-06 20:24:1319#include "base/feature_list.h"
gambard9efce7a2017-02-09 18:53:1720#include "base/files/file_path.h"
sdefresnee65fd872016-12-19 13:38:1321#include "base/format_macros.h"
22#include "base/i18n/rtl.h"
23#include "base/ios/block_types.h"
24#include "base/ios/ios_util.h"
sdefresnee65fd872016-12-19 13:38:1325#include "base/logging.h"
26#include "base/mac/bind_objc_block.h"
27#include "base/mac/bundle_locations.h"
28#include "base/mac/foundation_util.h"
sdefresnee65fd872016-12-19 13:38:1329#include "base/macros.h"
30#include "base/memory/ptr_util.h"
asvitkinef1899e32017-01-27 16:30:2931#include "base/metrics/histogram_macros.h"
sdefresnee65fd872016-12-19 13:38:1332#include "base/metrics/user_metrics.h"
33#include "base/metrics/user_metrics_action.h"
sdefresnee65fd872016-12-19 13:38:1334#include "base/strings/sys_string_conversions.h"
rohitraocd324eb72017-04-04 15:36:3935#include "base/strings/utf_string_conversions.h"
Sylvain Defresnefd3ecf22017-07-12 18:47:2436#include "base/task_scheduler/post_task.h"
tzik14236032017-02-15 06:41:0137#include "base/threading/sequenced_worker_pool.h"
Sylvain Defresnefd3ecf22017-07-12 18:47:2438#include "base/threading/thread_restrictions.h"
sdefresnee65fd872016-12-19 13:38:1339#include "components/bookmarks/browser/base_bookmark_model_observer.h"
40#include "components/bookmarks/browser/bookmark_model.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2341#include "components/feature_engagement/public/event_constants.h"
Cooper Knaake4f495cf2017-07-27 23:30:0342#include "components/feature_engagement/public/feature_constants.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2343#include "components/feature_engagement/public/tracker.h"
gambardbdc07cc2017-02-03 16:43:1144#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
sdefresnee65fd872016-12-19 13:38:1345#include "components/infobars/core/infobar_manager.h"
mathp9b4c11d2017-07-06 20:24:1346#include "components/payments/core/features.h"
sdefresnee65fd872016-12-19 13:38:1347#include "components/prefs/pref_service.h"
olivierrobin52b6cd6ec2017-03-23 13:55:5448#include "components/reading_list/core/reading_list_model.h"
sdefresnee65fd872016-12-19 13:38:1349#include "components/search_engines/search_engines_pref_names.h"
50#include "components/search_engines/template_url_service.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0551#include "components/sessions/core/session_types.h"
sdefresnee65fd872016-12-19 13:38:1352#include "components/sessions/core/tab_restore_service_helper.h"
53#include "components/strings/grit/components_strings.h"
54#include "components/toolbar/toolbar_model_impl.h"
55#include "ios/chrome/app/tests_hook.h"
56#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
57#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
58#include "ios/chrome/browser/chrome_url_constants.h"
59#include "ios/chrome/browser/chrome_url_util.h"
60#include "ios/chrome/browser/experimental_flags.h"
61#import "ios/chrome/browser/favicon/favicon_loader.h"
62#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2363#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
64#include "ios/chrome/browser/feature_engagement/tracker_util.h"
sdefresnee65fd872016-12-19 13:38:1365#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
66#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3667#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1368#include "ios/chrome/browser/first_run/first_run.h"
69#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
70#include "ios/chrome/browser/infobars/infobar_container_ios.h"
71#include "ios/chrome/browser/infobars/infobar_container_view.h"
Rohit Raoaf46af92017-08-10 12:52:3072#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
sdefresnee65fd872016-12-19 13:38:1373#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
74#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
sdefresnee65fd872016-12-19 13:38:1375#import "ios/chrome/browser/open_url_util.h"
76#import "ios/chrome/browser/passwords/password_controller.h"
sdefresnee65fd872016-12-19 13:38:1377#include "ios/chrome/browser/pref_names.h"
Rohit Rao44f204302017-08-10 14:49:5478#import "ios/chrome/browser/prerender/preload_controller_delegate.h"
79#import "ios/chrome/browser/prerender/prerender_service.h"
80#import "ios/chrome/browser/prerender/prerender_service_factory.h"
olivierrobin013ba672017-03-01 21:16:2481#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1382#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
83#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
84#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0585#include "ios/chrome/browser/sessions/session_util.h"
sdefresnee65fd872016-12-19 13:38:1386#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
87#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
88#import "ios/chrome/browser/snapshots/snapshot_cache.h"
89#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
90#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
Mike Doughertya1ec26402017-08-23 19:46:3191#import "ios/chrome/browser/ssl/ios_captive_portal_blocking_page_delegate.h"
pkld6e73e52017-03-08 15:56:5192#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:2893#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1394#import "ios/chrome/browser/tabs/tab.h"
95#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:1996#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:1397#import "ios/chrome/browser/tabs/tab_model.h"
98#import "ios/chrome/browser/tabs/tab_model_observer.h"
Sylvain Defresne72c530e42017-08-25 15:28:1699#import "ios/chrome/browser/tabs/tab_private.h"
sdefresnee65fd872016-12-19 13:38:13100#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
Rohit Rao01e0e002017-08-14 20:49:43101#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
102#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
103#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_snackbar.h"
sdefresnee65fd872016-12-19 13:38:13104#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
105#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
106#import "ios/chrome/browser/ui/background_generator.h"
107#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
108#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13109#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
Cooper Knaak33f9f402017-08-09 18:04:38110#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
Mike Doughertya1ec26402017-08-23 19:46:31111#import "ios/chrome/browser/ui/captive_portal/captive_portal_login_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13112#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
113#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h"
Mark Cogan5e3da152017-07-11 15:57:30114#import "ios/chrome/browser/ui/commands/application_commands.h"
Mark Cogan6c58ea92017-07-06 13:08:24115#import "ios/chrome/browser/ui/commands/browser_commands.h"
edchin9badb062017-08-16 18:47:54116#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
sdefresnee65fd872016-12-19 13:38:13117#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
Mark Cogandfcdea72017-07-18 13:47:38118#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13119#import "ios/chrome/browser/ui/commands/open_url_command.h"
120#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
121#import "ios/chrome/browser/ui/commands/show_mail_composer_command.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00122#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23123#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13124#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13125#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
126#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
127#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
128#import "ios/chrome/browser/ui/external_file_controller.h"
129#import "ios/chrome/browser/ui/external_file_remover.h"
sdefresnee65fd872016-12-19 13:38:13130#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
131#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
132#import "ios/chrome/browser/ui/fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38133#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13134#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13135#import "ios/chrome/browser/ui/key_commands_provider.h"
Gauthier Ambard33720dd2017-09-06 15:36:51136#import "ios/chrome/browser/ui/ntp/incognito_view_controller.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10137#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13138#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42139#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13140#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27141#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
142#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13143#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37144#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13145#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11146#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
147#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13148#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03149#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13150#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13151#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53152#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13153#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
154#import "ios/chrome/browser/ui/stack_view/card_view.h"
155#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
156#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
157#import "ios/chrome/browser/ui/sync/sync_util.h"
158#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
159#import "ios/chrome/browser/ui/tabs/tab_strip_controller.h"
160#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
161#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
162#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
sczsbbad1632017-07-29 03:48:00163#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13164#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
165#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
166#include "ios/chrome/browser/ui/ui_util.h"
167#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28168#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13169#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
170#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51171#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13172#import "ios/chrome/browser/web/error_page_content.h"
173#import "ios/chrome/browser/web/passkit_dialog_provider.h"
eugenebutcae3d9e62017-01-27 20:01:05174#import "ios/chrome/browser/web/repost_form_tab_helper.h"
sczs6ae47ad2017-09-06 17:26:53175#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
sdefresne62a00bb2017-04-10 15:36:05176#import "ios/chrome/browser/web_state_list/web_state_list.h"
177#import "ios/chrome/browser/web_state_list/web_state_opener.h"
sdefresnee65fd872016-12-19 13:38:13178#include "ios/chrome/grit/ios_chromium_strings.h"
179#include "ios/chrome/grit/ios_strings.h"
180#import "ios/net/request_tracker.h"
181#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
182#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
183#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
184#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
185#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
186#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
187#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
188#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
sdefresnee65fd872016-12-19 13:38:13189#include "ios/web/public/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:13190#include "ios/web/public/navigation_item.h"
191#import "ios/web/public/navigation_manager.h"
192#include "ios/web/public/referrer_util.h"
193#include "ios/web/public/ssl_status.h"
194#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39195#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13196#include "ios/web/public/web_client.h"
197#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13198#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29199#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
sdefresnee65fd872016-12-19 13:38:13200#include "ios/web/public/web_state/web_state.h"
201#import "ios/web/public/web_state/web_state_delegate_bridge.h"
202#include "ios/web/public/web_thread.h"
203#import "ios/web/web_state/ui/crw_web_controller.h"
204#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17205#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13206#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
207#include "net/ssl/ssl_info.h"
208#include "net/url_request/url_request_context_getter.h"
209#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
210#include "ui/base/l10n/l10n_util.h"
211#include "ui/base/l10n/l10n_util_mac.h"
212#include "ui/base/page_transition_types.h"
213#include "url/gurl.h"
214
stkhapuginf58b10d02017-04-10 13:36:17215#if !defined(__has_feature) || !__has_feature(objc_arc)
216#error "This file requires ARC support."
217#endif
218
sdefresnee65fd872016-12-19 13:38:13219using base::UserMetricsAction;
220using bookmarks::BookmarkNode;
221
222class BrowserBookmarkModelBridge;
223class InfoBarContainerDelegateIOS;
224
sdefresnee65fd872016-12-19 13:38:13225NSString* const kLocationBarBecomesFirstResponderNotification =
226 @"kLocationBarBecomesFirstResponderNotification";
227NSString* const kLocationBarResignsFirstResponderNotification =
228 @"kLocationBarResignsFirstResponderNotification";
sdefresnee65fd872016-12-19 13:38:13229
230namespace {
231
232typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
233 // Note: these values must match the ContextMenuOption enum in histograms.xml.
234 ACTION_OPEN_IN_NEW_TAB = 0,
235 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
236 ACTION_COPY_LINK_ADDRESS = 2,
237 ACTION_SAVE_IMAGE = 6,
238 ACTION_OPEN_IMAGE = 7,
239 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
240 ACTION_SEARCH_BY_IMAGE = 11,
241 ACTION_OPEN_JAVASCRIPT = 21,
242 ACTION_READ_LATER = 22,
243 NUM_ACTIONS = 23,
244};
245
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28246void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13247 if (is_image) {
248 if (is_link) {
249 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
250 NUM_ACTIONS);
251 } else {
252 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
253 NUM_ACTIONS);
254 }
255 } else {
256 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
257 NUM_ACTIONS);
258 }
259}
260
sdefresnee65fd872016-12-19 13:38:13261const CGFloat kVoiceSearchBarHeight = 59.0;
262
263// Dimensions to use when downsizing an image for search-by-image.
264const CGFloat kSearchByImageMaxImageArea = 90000.0;
265const CGFloat kSearchByImageMaxImageWidth = 600.0;
266const CGFloat kSearchByImageMaxImageHeight = 400.0;
267
268// The delay, in seconds, after startup before cleaning up the files received
269// from other applications that are not bookmarked nor referenced by an open or
270// recently closed tab.
271const int kExternalFilesCleanupDelaySeconds = 60;
272
273enum HeaderBehaviour {
274 // The header moves completely out of the screen.
275 Hideable = 0,
276 // This header stays on screen and doesn't overlap with the content.
277 Visible,
278 // This header stay on screen and covers part of the content.
279 Overlap
280};
281
sdefresnee65fd872016-12-19 13:38:13282const CGFloat kIPadFindBarOverlap = 11;
283
284bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24285 // Most URLs are allowed in incognito; the following is an exception.
286 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13287}
288
rohitrao005a6432017-03-16 20:52:42289} // namespace
sdefresnee65fd872016-12-19 13:38:13290
stkhapugin952ecef2017-04-11 12:11:45291#pragma mark - HeaderDefinition helper
292
293@interface HeaderDefinition : NSObject
294
295// The header view.
296@property(nonatomic, strong) UIView* view;
297// How to place the view, and its behaviour when the headers move.
298@property(nonatomic, assign) HeaderBehaviour behaviour;
299// Reduces the height of a header to adjust for shadows.
300@property(nonatomic, assign) CGFloat heightAdjustement;
301// Nudges that particular header up by this number of points.
302@property(nonatomic, assign) CGFloat inset;
303
304- (instancetype)initWithView:(UIView*)view
305 headerBehaviour:(HeaderBehaviour)behaviour
306 heightAdjustment:(CGFloat)heightAdjustment
307 inset:(CGFloat)inset;
308
309+ (instancetype)definitionWithView:(UIView*)view
310 headerBehaviour:(HeaderBehaviour)behaviour
311 heightAdjustment:(CGFloat)heightAdjustment
312 inset:(CGFloat)inset;
313
314@end
315
316@implementation HeaderDefinition
317@synthesize view = _view;
318@synthesize behaviour = _behaviour;
319@synthesize heightAdjustement = _heightAdjustement;
320@synthesize inset = _inset;
321
322+ (instancetype)definitionWithView:(UIView*)view
323 headerBehaviour:(HeaderBehaviour)behaviour
324 heightAdjustment:(CGFloat)heightAdjustment
325 inset:(CGFloat)inset {
326 return [[self alloc] initWithView:view
327 headerBehaviour:behaviour
328 heightAdjustment:heightAdjustment
329 inset:inset];
330}
331
332- (instancetype)initWithView:(UIView*)view
333 headerBehaviour:(HeaderBehaviour)behaviour
334 heightAdjustment:(CGFloat)heightAdjustment
335 inset:(CGFloat)inset {
336 self = [super init];
337 if (self) {
338 _view = view;
339 _behaviour = behaviour;
340 _heightAdjustement = heightAdjustment;
341 _inset = inset;
342 }
343 return self;
344}
345
346@end
347
348#pragma mark - BVC
349
Rohit Rao01e0e002017-08-14 20:49:43350@interface BrowserViewController ()<ActivityServicePresentation,
351 ActivityServiceSnackbar,
352 AppRatingPromptDelegate,
sdefresnee65fd872016-12-19 13:38:13353 CRWNativeContentProvider,
354 CRWWebStateDelegate,
355 DialogPresenterDelegate,
356 FullScreenControllerDelegate,
Gauthier Ambard8160349c2017-09-06 14:43:24357 IncognitoViewControllerDelegate,
Mike Doughertya1ec26402017-08-23 19:46:31358 IOSCaptivePortalBlockingPageDelegate,
sdefresnee65fd872016-12-19 13:38:13359 KeyCommandsPlumbing,
360 MFMailComposeViewControllerDelegate,
361 NewTabPageControllerObserver,
362 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27363 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13364 PassKitDialogProvider,
365 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11366 QRScannerPresenting,
sdefresnee65fd872016-12-19 13:38:13367 SKStoreProductViewControllerDelegate,
368 SnapshotOverlayProvider,
369 StoreKitLauncher,
370 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19371 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38372 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13373 TabModelObserver,
374 TabSnapshottingDelegate,
375 UIGestureRecognizerDelegate,
376 UpgradeCenterClientProtocol,
377 VoiceSearchBarDelegate,
378 VoiceSearchBarOwner> {
379 // The dependency factory passed on initialization. Used to vend objects used
380 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27381 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13382
383 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27384 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13385
386 // Facade objects used by |_toolbarController|.
387 // Must outlive |_toolbarController|.
388 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
389 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
390
sdefresnee65fd872016-12-19 13:38:13391 // The WebToolbarController used to display the omnibox.
stkhapuginc9eee7b2017-04-10 15:49:27392 WebToolbarController* _toolbarController;
sdefresnee65fd872016-12-19 13:38:13393
394 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27395 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13396
Mike Doughertya1ec26402017-08-23 19:46:31397 // Handles displaying the captive portal login page.
398 CaptivePortalLoginCoordinator* _captivePortalLoginCoordinator;
399
sdefresnee65fd872016-12-19 13:38:13400 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27401 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13402
403 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27404 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13405
406 // Handles presentation of JavaScript dialogs.
407 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
408
justincohen75011c32017-04-28 16:31:39409 // Handles command dispatching.
410 CommandDispatcher* _dispatcher;
411
sdefresnee65fd872016-12-19 13:38:13412 // Keyboard commands provider. It offloads most of the keyboard commands
413 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27414 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13415
416 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
417 // to |-reparentToolbarController| will reset it to NO.
418 BOOL _isToolbarControllerRelinquished;
419
420 // The controller that owns the currently relinquished toolbar controller.
421 // The reference is weak because it's possible for the toolbar owner to be
422 // deallocated mid-animation due to memory pressure or a tab being closed
423 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27424 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13425
426 // Always present on tablet; always nil on phone.
stkhapuginc9eee7b2017-04-10 15:49:27427 TabStripController* _tabStripController;
sdefresnee65fd872016-12-19 13:38:13428
sdefresnee65fd872016-12-19 13:38:13429 // Used to inject Javascript implementing the PaymentRequest API and to
430 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27431 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13432
sdefresnee65fd872016-12-19 13:38:13433 // Used to display the Voice Search UI. Nil if not visible.
434 scoped_refptr<VoiceSearchController> _voiceSearchController;
435
gambard6299cc1d2017-02-21 13:06:03436 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27437 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03438
sdefresnee65fd872016-12-19 13:38:13439 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27440 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13441
sdefresnee65fd872016-12-19 13:38:13442 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27443 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13444
445 // Records the set of domains for which full screen alert has already been
446 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27447 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13448
449 // Adapter to let BVC be the delegate for WebState.
450 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
451
452 // YES if new tab is animating in.
453 BOOL _inNewTabAnimation;
454
455 // YES if Voice Search should be started when the new tab animation is
456 // finished.
457 BOOL _startVoiceSearchAfterNewTabAnimation;
458
459 // YES if the user interacts with the location bar.
460 BOOL _locationBarHasFocus;
461 // YES if a load was cancelled due to typing in the location bar.
462 BOOL _locationBarEditCancelledLoad;
463 // YES if waiting for a foreground tab due to expectNewForegroundTab.
464 BOOL _expectingForegroundTab;
465
Sylvain Defresne41170aa2017-06-15 10:25:20466 // Whether or not -shutdown has been called.
467 BOOL _isShutdown;
468
sdefresnee65fd872016-12-19 13:38:13469 // The ChromeBrowserState associated with this BVC.
470 ios::ChromeBrowserState* _browserState; // weak
471
472 // Whether or not Incognito* is enabled.
473 BOOL _isOffTheRecord;
474
475 // The last point within |_contentArea| that's received a touch.
476 CGPoint _lastTapPoint;
477
478 // The time at which |_lastTapPoint| was most recently set.
479 CFTimeInterval _lastTapTime;
480
481 // A single infobar container handles all infobars in all tabs. It keeps
482 // track of infobars for current tab (accessed via infobar helper of
483 // the current tab).
484 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
485
486 // Bridge class to deliver container change notifications to BVC.
487 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
488
489 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37490 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27491 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13492
493 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11494 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13495
496 // Card side swipe view.
stkhapuginc9eee7b2017-04-10 15:49:27497 CardSideSwipeView* _sideSwipeView;
sdefresnee65fd872016-12-19 13:38:13498
sdefresnee65fd872016-12-19 13:38:13499 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27500 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13501
502 // Bridge to register for bookmark changes.
503 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
504
505 // Cached pointer to the bookmarks model.
506 bookmarks::BookmarkModel* _bookmarkModel; // weak
507
508 // The controller that shows the bookmarking UI after the user taps the star
509 // button.
stkhapuginc9eee7b2017-04-10 15:49:27510 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13511
512 // Used to remove unreferenced external files.
513 std::unique_ptr<ExternalFileRemover> _externalFileRemover;
514
515 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27516 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13517
Eugene But56efc322017-08-11 14:03:44518 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28519 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13520
521 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27522 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13523
Jean-François Geyelin3d47c212017-08-03 09:24:09524 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27525 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13526
Rohit Rao01e0e002017-08-14 20:49:43527 // Coordinator for the share menu (Activity Services).
528 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
529
sdefresnee65fd872016-12-19 13:38:13530 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27531 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38532
Rohit Raocda0a992017-08-16 15:37:11533 // Coordinator for the QR scanner.
534 QRScannerLegacyCoordinator* _qrScannerCoordinator;
535
sczsdd860eba2017-08-10 01:55:38536 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13537 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53538
539 // Coordinator for displaying Sad Tab.
540 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27541
542 // Coordinator for Page Info UI.
543 PageInfoLegacyCoordinator* _pageInfoCoordinator;
sdefresnee65fd872016-12-19 13:38:13544}
545
546// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17547@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13548// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17549@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13550// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17551@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13552// Whether the current tab can enable the request desktop menu item.
553@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
554// Whether the sharing menu should be enabled.
555@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
556// Helper method to check web controller canShowFindBar method.
557@property(nonatomic, assign, readonly) BOOL canShowFindBar;
558// Whether the controller's view is currently available.
559// YES from viewWillAppear to viewWillDisappear.
560@property(nonatomic, assign, getter=isVisible) BOOL visible;
561// Whether the controller's view is currently visible.
562// YES from viewDidAppear to viewWillDisappear.
563@property(nonatomic, assign) BOOL viewVisible;
564// Whether the controller is currently dismissing a presented view controller.
565@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
566// Returns YES if the toolbar has not been scrolled out by fullscreen.
567@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
568 BOOL toolbarOnScreen;
569// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12570@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13571// Whether BVC prefers to hide the status bar. This value is used to determine
572// the response from the |prefersStatusBarHidden| method.
573@property(nonatomic, assign) BOOL hideStatusBar;
574// Whether the VoiceSearchBar should be displayed.
575@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
576// Coordinator for displaying a modal overlay with activity indicator to prevent
577// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17578@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13579 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39580// A block to be run when the |tabWasAdded:| method completes the animation
581// for the presentation of a new tab. Can be used to record performance metrics.
582@property(nonatomic, strong, nullable)
583 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42584// Coordinator for Recent Tabs.
585@property(nonatomic, strong)
586 RecentTabsHandsetCoordinator* recentTabsCoordinator;
sdefresnee65fd872016-12-19 13:38:13587
liaoyukeea9f3ee62017-03-07 22:05:39588// The user agent type used to load the currently visible page. User agent type
589// is NONE if there is no visible page or visible page is a native page.
590@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
591
stkhapugin952ecef2017-04-11 12:11:45592// Returns the header views, all the chrome on top of the page, including the
593// ones that cannot be scrolled off screen by full screen.
594@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
595
Cooper Knaakd0a974cd2017-08-10 18:05:47596// Used to display the new tab tip in-product help promotion bubble. |nil| if
597// the new tab tip bubble has not yet been presented. Once the bubble is
598// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38599@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47600 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38601
Helen Yang9175bd52017-08-12 00:28:40602// Used to display the new incognito tab tip in-product help promotion bubble.
603@property(nonatomic, strong)
604 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
605
sdefresnee65fd872016-12-19 13:38:13606// BVC initialization:
607// If the BVC is initialized with a valid browser state & tab model immediately,
608// the path is straightforward: functionality is enabled, and the UI is built
609// when -viewDidLoad is called.
610// If the BVC is initialized without a browser state or tab model, the tab model
611// and browser state may or may not be provided before -viewDidLoad is called.
612// In most cases, they will not, to improve startup performance.
613// In order to handle this, initialization of various aspects of BVC have been
614// broken out into the following functions, which have expectations (enforced
615// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
616
617// Registers for notifications.
618- (void)registerForNotifications;
619// Called when a tab is starting to load. If it's a link click or form
620// submission, the user is navigating away from any entries in the forward
621// history. Tell the toolbar so it can update the UI appropriately.
622// See the warning on [Tab webWillStartLoadingURL] about invocation of this
623// method sequence by malicious pages.
624- (void)pageLoadStarting:(NSNotification*)notify;
625// Called when a tab actually starts loading.
626- (void)pageLoadStarted:(NSNotification*)notify;
627// Called when a tab finishes loading. Update the Omnibox with the url and
628// stop any page load progess display.
629- (void)pageLoadComplete:(NSNotification*)notify;
630// Called when a tab is deselected in the model.
631// This notification also occurs when a tab is closed.
632- (void)tabDeselected:(NSNotification*)notify;
633// Animates sliding current tab and rotate-entering new tab while new tab loads
634// in background on the iPhone only.
635- (void)tabWasAdded:(NSNotification*)notify;
636
637// Updates non-view-related functionality with the given browser state and tab
638// model.
639// Does not matter whether or not the view has been loaded.
640- (void)updateWithTabModel:(TabModel*)model
641 browserState:(ios::ChromeBrowserState*)browserState;
642// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
643// the status bar to mimic this layout.
644- (void)installFakeStatusBar;
645// Builds the UI parts of tab strip and the toolbar. Does not matter whether
646// or not browser state and tab model are valid.
647- (void)buildToolbarAndTabStrip;
648// Updates view-related functionality with the given tab model and browser
649// state. The view must have been loaded. Uses |_browserState| and |_model|.
650- (void)addUIFunctionalityForModelAndBrowserState;
Julien Brianceaub7e590ac2017-08-01 17:30:22651// Sets the correct frame and hierarchy for subviews and helper views.
sdefresnee65fd872016-12-19 13:38:13652- (void)setUpViewLayout;
653// Sets the correct frame for the tab strip based on the given maximum width.
654- (void)layoutTabStripForWidth:(CGFloat)maxWidth;
655// Makes |tab| the currently visible tab, displaying its view. Calls
656// -selectedTabChanged on the toolbar only if |newSelection| is YES.
657- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
658// Initializes the bookmark interaction controller if not already initialized.
659- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13660// Add all delegates to the provided |tab|.
661- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14662// Remove delegates from the provided |tab|.
663- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13664// Closes the current tab, with animation if applicable.
665- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13666// Show the bookmarks page.
667- (void)showAllBookmarks;
668// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23669- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13670// Dismisses the "rate this app" dialog.
671- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32672// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13673- (BOOL)isTabNativePage:(Tab*)tab;
674// Returns the view to use when animating a page in or out, positioning it to
675// fill the content area but not actually adding it to the view hierarchy.
676- (UIImageView*)pageOpenCloseAnimationView;
677// Returns the view to use when animating full screen NTP paper in, filling the
678// entire screen but not actually adding it to the view hierarchy.
679- (UIImageView*)pageFullScreenOpenCloseAnimationView;
680// Updates the toolbar display based on the current tab.
681- (void)updateToolbar;
682// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12683// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13684- (void)updateDialogPresenterActiveState;
685// Dismisses popups and modal dialogs that are displayed above the BVC upon size
686// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
687// is performed.
688// TODO(crbug.com/522721): Support size changes for all popups and modal
689// dialogs.
690- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47691
692// Returns a bubble associated with an in-product help promotion if
693// it is valid to show the promotion and |nil| otherwise. |feature| is the
694// base::Feature object associated with the given promotion. |direction| is the
695// direction the bubble's arrow is pointing. |alignment| is the alignment of the
696// arrow on the button. |text| is the text displayed by the bubble.
697- (BubbleViewControllerPresenter*)
698bubblePresenterForFeature:(const base::Feature&)feature
699 direction:(BubbleArrowDirection)direction
700 alignment:(BubbleAlignment)alignment
701 text:(NSString*)text;
702
Cooper Knaak120cee5e2017-08-10 20:57:00703// Waits to present a bubble associated with the new tab tip in-product help
704// promotion until the feature engagement tracker database is fully initialized.
705// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
706// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11707// |userEngaged|. Does not present the bubble if the feature engagement tracker
708// determines it is not valid to present it.
Cooper Knaak120cee5e2017-08-10 20:57:00709- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11710// Optionally presents a bubble associated with the new tab tip in-product help
711// promotion. If the feature engagement tracker determines it is valid to show
712// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
713// the bubble. If it is not valid to show the new tab tip,
714// |tabTipBubblePresenter| is set to |nil| and no bubble is shown.
Cooper Knaak120cee5e2017-08-10 20:57:00715- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40716// Waits to present a bubble associated with the new incognito tab tip
717// in-product help promotion until the feature engagement tracker database is
718// fully initialized.
719- (void)presentNewIncognitoTabTipBubbleOnInitialized;
720// Presents a bubble associated with the new incognito tab tip in-product help
721// promotion.
722- (void)presentNewIncognitoTabTipBubble;
Cooper Knaak120cee5e2017-08-10 20:57:00723
sdefresnee65fd872016-12-19 13:38:13724// Update find bar with model data. If |shouldFocus| is set to YES, the text
725// field will become first responder.
726- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13727// Hide find bar.
728- (void)hideFindBarWithAnimation:(BOOL)animate;
729// Shows find bar. If |selectText| is YES, all text inside the Find Bar
730// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
731// set to be first responder.
732- (void)showFindBarWithAnimation:(BOOL)animate
733 selectText:(BOOL)selectText
734 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36735
sdefresnee65fd872016-12-19 13:38:13736// The infobar state (typically height) has changed.
737- (void)infoBarContainerStateChanged:(bool)is_animating;
738// Adds a CardView on top of the contentArea either taking the size of the full
739// screen or just the size of the space under the header.
740// Returns the CardView that was added.
741- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
742// Called when either a tab finishes loading or when a tab with finished content
743// is added directly to the model via pre-rendering. The tab must be non-nil and
744// must be a member of the tab model controlled by this BrowserViewController.
745- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
746// Evaluates Javascript asynchronously using the current page context.
747- (void)openJavascript:(NSString*)javascript;
sdefresnee65fd872016-12-19 13:38:13748// Shows a self-dismissing snackbar displaying |message|.
749- (void)showSnackbar:(NSString*)message;
750// Induces an intentional crash in the browser process.
751- (void)induceBrowserCrash;
752// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17753- (void)managePermissionAndSaveImage:(NSData*)data
754 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13755// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24756// saved as a temporary file on disk then saved in photos. Saving will happen
757// on a background sequence and the completion block will be invoked on that
758// sequence.
759- (void)saveImage:(NSData*)data
760 withFileExtension:(NSString*)fileExtension
761 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13762// Called when Chrome has been denied access to the photos or videos and the
763// user can change it.
764// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
765// settings. Dismiss previous alert if it has not been dismissed yet.
766- (void)displayImageErrorAlertWithSettingsOnMainQueue;
767// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
768// previous alert if it has not been dismissed yet.
769- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
770// Called when Chrome has been denied access to the photos or videos and the
771// user cannot change it.
772// Shows a privacy alert on the main queue, with errorContent as the message.
773// Dismisses previous alert if it has not been dismissed yet.
774- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
775// Called with the results of saving a picture in the photo album. If error is
776// nil the save succeeded.
777- (void)finishSavingImageWithError:(NSError*)error;
778// Provides a view that encompasses currently displayed infobar(s) or nil
779// if no infobar is presented.
780- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
781// Returns a vertical infobar offset relative to the tab content.
782- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
783// Provides a view that encompasses the voice search bar if it's displayed or
784// nil if the voice search bar isn't displayed.
785- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
786// Returns a vertical voice search bar offset relative to the tab content.
787- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
788// Lazily instantiates |_voiceSearchController|.
789- (void)ensureVoiceSearchControllerCreated;
790// Lazily instantiates |_voiceSearchBar| and adds it to the view.
791- (void)ensureVoiceSearchBarCreated;
792// Shows/hides the voice search bar.
793- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
794// The LogoAnimationControllerOwner to be used for the next logo transition
795// animation.
796- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13797// Returns the footer view if one exists (e.g. the voice search bar).
798- (UIView*)footerView;
799// Returns the height of the header view for the tab model's current tab.
800- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13801// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45802- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13803 atOffset:(CGFloat)headerOffset;
804// Returns the y coordinate for the footer's frame when animating the footer
805// in/out of fullscreen.
806- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
807// Called when the animation for setting the header view's offset is finished.
808// |completed| should indicate if the animation finished completely or was
809// interrupted. |offset| should indicate the header offset after the animation.
810// |dragged| should indicate if the header moved due to the user dragging.
811- (void)fullScreenController:(FullScreenController*)controller
812 headerAnimationCompleted:(BOOL)completed
813 offset:(CGFloat)offset;
814// Performs a search with the image at the given url. The referrer is used to
815// download the image.
816- (void)searchByImageAtURL:(const GURL&)url
817 referrer:(const web::Referrer)referrer;
818// Saves the image at the given URL on the system's album. The referrer is used
819// to download the image.
820- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
821
Mark Cogandfcdea72017-07-18 13:47:38822// Record the last tap point based on the |originPoint| (if any) passed in
823// |command|.
824- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13825// Get return the last stored |_lastTapPoint| if it's been set within the past
826// second.
827- (CGPoint)lastTapPoint;
828// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
829- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
830// Returns the native controller being used by |tab|'s web controller.
831- (id)nativeControllerForTab:(Tab*)tab;
832// Installs the BVC as overscroll actions controller of |nativeContent| if
833// needed. Sets the style of the overscroll actions toolbar.
834- (void)setOverScrollActionControllerToStaticNativeContent:
835 (StaticHtmlNativeContent*)nativeContent;
836// Whether the BVC should declare keyboard commands.
837- (BOOL)shouldRegisterKeyboardCommands;
838// Adds the given url to the reading list.
839- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
840@end
841
842class InfoBarContainerDelegateIOS
843 : public infobars::InfoBarContainer::Delegate {
844 public:
845 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
846 : controller_(controller) {}
847
848 ~InfoBarContainerDelegateIOS() override {}
849
850 private:
851 SkColor GetInfoBarSeparatorColor() const override {
852 NOTIMPLEMENTED();
853 return SK_ColorBLACK;
854 }
855
856 int ArrowTargetHeightForInfoBar(
857 size_t index,
858 const gfx::SlideAnimation& animation) const override {
859 return 0;
860 }
861
862 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
863 int arrow_target_height,
864 int bar_target_height,
865 int* arrow_height,
866 int* arrow_half_width,
867 int* bar_height) const override {
868 DCHECK_NE(-1, bar_target_height)
869 << "Infobars don't have a default height on iOS";
870 *arrow_height = 0;
871 *arrow_half_width = 0;
872 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
873 }
874
875 void InfoBarContainerStateChanged(bool is_animating) override {
876 [controller_ infoBarContainerStateChanged:is_animating];
877 }
878
879 bool DrawInfoBarArrows(int* x) const override { return false; }
880
stkhapuginf58b10d02017-04-10 13:36:17881 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13882};
883
884// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
885@interface BrowserViewController (BookmarkBridgeMethods)
886// If a bookmark matching the currentTab url is added or moved, update the
887// toolbar state so the star highlight is in sync.
888- (void)bookmarkNodeModified:(const BookmarkNode*)node;
889- (void)allBookmarksRemoved;
890@end
891
892// Handle notification that bookmarks has been removed changed so we can update
893// the bookmarked star icon.
894class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
895 public:
896 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
897 : owner_(owner) {}
898
899 ~BrowserBookmarkModelBridge() override {}
900
901 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
902 const BookmarkNode* parent,
903 int old_index,
904 const BookmarkNode* node,
905 const std::set<GURL>& removed_urls) override {
906 [owner_ bookmarkNodeModified:node];
907 }
908
909 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
910 bool ids_reassigned) override {}
911
912 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
913 const BookmarkNode* old_parent,
914 int old_index,
915 const BookmarkNode* new_parent,
916 int new_index) override {}
917
918 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
919 const BookmarkNode* parent,
920 int index) override {
921 [owner_ bookmarkNodeModified:parent->GetChild(index)];
922 }
923
924 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
925 const BookmarkNode* node) override {}
926
927 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
928 const BookmarkNode* node) override {}
929
930 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
931 const BookmarkNode* node) override {}
932
933 void BookmarkAllUserNodesRemoved(
934 bookmarks::BookmarkModel* model,
935 const std::set<GURL>& removed_urls) override {
936 [owner_ allBookmarksRemoved];
937 }
938
939 private:
stkhapuginf58b10d02017-04-10 13:36:17940 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13941};
942
943@implementation BrowserViewController
944
945@synthesize contentArea = _contentArea;
946@synthesize typingShield = _typingShield;
947@synthesize active = _active;
948@synthesize visible = _visible;
949@synthesize viewVisible = _viewVisible;
950@synthesize dismissingModal = _dismissingModal;
951@synthesize hideStatusBar = _hideStatusBar;
952@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
953@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39954@synthesize foregroundTabWasAddedCompletionBlock =
955 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:40956@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
957@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:42958@synthesize recentTabsCoordinator = _recentTabsCoordinator;
sdefresnee65fd872016-12-19 13:38:13959
960#pragma mark - Object lifecycle
961
Mark Cogan5e3da152017-07-11 15:57:30962- (instancetype)
963 initWithTabModel:(TabModel*)model
964 browserState:(ios::ChromeBrowserState*)browserState
965 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
966applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:13967 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
968 if (self) {
969 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17970
stkhapuginc9eee7b2017-04-10 15:49:27971 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:27972 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
973 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39974 _dispatcher = [[CommandDispatcher alloc] init];
975 [_dispatcher startDispatchingToTarget:self
976 forProtocol:@protocol(UrlLoader)];
977 [_dispatcher startDispatchingToTarget:self
978 forProtocol:@protocol(WebToolbarDelegate)];
979 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:24980 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:30981 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
982 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:32983 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
984 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
985 // dispatched to the endpoint as well. Since this is potentially
986 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
987 DCHECK(!applicationCommandEndpoint ||
988 [applicationCommandEndpoint
989 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
990 [_dispatcher
991 startDispatchingToTarget:applicationCommandEndpoint
992 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:39993
sdefresnee65fd872016-12-19 13:38:13994 _javaScriptDialogPresenter.reset(
995 new JavaScriptDialogPresenterImpl(_dialogPresenter));
996 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
997 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:15998 [[UpgradeCenter sharedInstance] registerClient:self
999 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131000 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131001 if (model && browserState)
1002 [self updateWithTabModel:model browserState:browserState];
1003 if ([[NSUserDefaults standardUserDefaults]
1004 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271005 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131006 }
1007 }
1008 return self;
1009}
1010
1011- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1012 bundle:(NSBundle*)nibBundleOrNil {
1013 NOTREACHED();
1014 return nil;
1015}
1016
1017- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1018 NOTREACHED();
1019 return nil;
1020}
1021
1022- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201023 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131024}
1025
1026#pragma mark - Accessibility
1027
1028- (BOOL)accessibilityPerformEscape {
1029 [self dismissPopups];
1030 return YES;
1031}
1032
1033#pragma mark - Properties
1034
edchin3365c7d2017-09-01 22:20:371035- (id<ApplicationCommands,
1036 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371037 OmniboxFocuser,
1038 UrlLoader,
1039 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561040 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
1041 UrlLoader, WebToolbarDelegate>>(_dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241042}
1043
sdefresnee65fd872016-12-19 13:38:131044- (void)setActive:(BOOL)active {
1045 if (_active == active) {
1046 return;
1047 }
1048 _active = active;
1049
1050 // If not active, display an activity indicator overlay over the view to
1051 // prevent interaction with the web page.
1052 // TODO(crbug.com/637093): This coordinator should be managed by the
1053 // coordinator used to present BrowserViewController, when implemented.
1054 if (active) {
1055 [self.activityOverlayCoordinator stop];
1056 self.activityOverlayCoordinator = nil;
1057 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171058 self.activityOverlayCoordinator =
1059 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131060 [self.activityOverlayCoordinator start];
1061 }
1062
1063 if (_browserState) {
1064 web::ActiveStateManager* active_state_manager =
1065 web::BrowserState::GetActiveStateManager(_browserState);
1066 active_state_manager->SetActive(active);
1067 }
1068
1069 [_model setWebUsageEnabled:active];
1070 [self updateDialogPresenterActiveState];
1071
1072 if (active) {
1073 // Make sure the tab (if any; it's possible to get here without a current
1074 // tab if the caller is about to create one) ends up on screen completely.
1075 Tab* currentTab = [_model currentTab];
1076 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441077 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131078 if (_expectingForegroundTab)
1079 [currentTab.webController setOverlayPreviewMode:YES];
1080 if (currentTab)
1081 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341082 } else {
1083 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131084 }
sdefresnee65fd872016-12-19 13:38:131085 [_paymentRequestManager enablePaymentRequest:active];
1086
1087 [self setNeedsStatusBarAppearanceUpdate];
1088}
1089
1090- (void)setPrimary:(BOOL)primary {
1091 [_model setPrimary:primary];
1092 if (primary) {
1093 [self updateDialogPresenterActiveState];
1094 } else {
1095 self.dialogPresenter.active = false;
1096 }
1097}
1098
1099- (BOOL)isPlayingTTS {
1100 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1101}
1102
sdefresne6165c8742017-01-16 15:42:021103- (ios::ChromeBrowserState*)browserState {
1104 return _browserState;
1105}
1106
1107- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271108 return _model;
sdefresne6165c8742017-01-16 15:42:021109}
1110
sdefresnee65fd872016-12-19 13:38:131111- (SideSwipeController*)sideSwipeController {
1112 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271113 _sideSwipeController =
1114 [[SideSwipeController alloc] initWithTabModel:_model
1115 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131116 [_sideSwipeController setSnapshotDelegate:self];
1117 [_sideSwipeController setSwipeDelegate:self];
1118 }
1119 return _sideSwipeController;
1120}
1121
sdefresnee65fd872016-12-19 13:38:131122- (DialogPresenter*)dialogPresenter {
1123 return _dialogPresenter;
1124}
1125
sdefresnee65fd872016-12-19 13:38:131126- (BOOL)canUseDesktopUserAgent {
1127 Tab* tab = [_model currentTab];
1128 if ([self isTabNativePage:tab])
1129 return NO;
1130
1131 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441132 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131133}
1134
1135// Whether the sharing menu should be shown.
1136- (BOOL)canShowShareMenu {
kkhorimotob110b262017-06-01 18:38:251137 const GURL& URL = [_model currentTab].lastCommittedURL;
1138 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131139}
1140
1141- (BOOL)canShowFindBar {
1142 // Make sure web controller can handle find in page.
1143 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421144 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131145 return NO;
rohitrao005a6432017-03-16 20:52:421146 }
sdefresnee65fd872016-12-19 13:38:131147
rohitrao005a6432017-03-16 20:52:421148 auto* helper = FindTabHelper::FromWebState(tab.webState);
1149 return (helper && helper->CurrentPageSupportsFindInPage() &&
1150 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131151}
1152
liaoyukeea9f3ee62017-03-07 22:05:391153- (web::UserAgentType)userAgentType {
1154 web::WebState* webState = [_model currentTab].webState;
1155 if (!webState)
1156 return web::UserAgentType::NONE;
1157 web::NavigationItem* visibleItem =
1158 webState->GetNavigationManager()->GetVisibleItem();
1159 if (!visibleItem)
1160 return web::UserAgentType::NONE;
1161
1162 return visibleItem->GetUserAgentType();
1163}
1164
sdefresnee65fd872016-12-19 13:38:131165- (void)setVisible:(BOOL)visible {
1166 if (_visible == visible)
1167 return;
1168 _visible = visible;
1169}
1170
1171- (void)setViewVisible:(BOOL)viewVisible {
1172 if (_viewVisible == viewVisible)
1173 return;
1174 _viewVisible = viewVisible;
1175 self.visible = viewVisible;
1176 [self updateDialogPresenterActiveState];
1177}
1178
1179- (BOOL)isToolbarOnScreen {
1180 return [self headerHeight] - [self currentHeaderOffset] > 0;
1181}
1182
kkhorimotoa44349c12017-04-12 23:02:121183- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1184 if (_inNewTabAnimation == inNewTabAnimation)
1185 return;
1186 _inNewTabAnimation = inNewTabAnimation;
1187 [self updateDialogPresenterActiveState];
1188}
1189
sdefresnee65fd872016-12-19 13:38:131190- (BOOL)isInNewTabAnimation {
1191 return _inNewTabAnimation;
1192}
1193
1194- (BOOL)shouldShowVoiceSearchBar {
1195 // On iPads, the voice search bar should only be shown for regular horizontal
1196 // size class configurations. It should always be shown for voice search
1197 // results Tabs on iPhones, including configurations with regular horizontal
1198 // size classes (i.e. landscape iPhone 6 Plus).
1199 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1200 UIUserInterfaceSizeClassCompact;
1201 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1202 (!IsIPadIdiom() || compactWidth);
1203}
1204
1205- (void)setHideStatusBar:(BOOL)hideStatusBar {
1206 if (_hideStatusBar == hideStatusBar)
1207 return;
1208 _hideStatusBar = hideStatusBar;
1209 [self setNeedsStatusBarAppearanceUpdate];
1210}
1211
1212#pragma mark - IBActions
1213
1214- (void)shieldWasTapped:(id)sender {
1215 [_toolbarController cancelOmniboxEdit];
1216}
1217
Cooper Knaakd0a974cd2017-08-10 18:05:471218- (void)userEnteredTabSwitcher {
1219 if ([self.tabTipBubblePresenter isUserEngaged]) {
1220 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1221 }
1222}
1223
Cooper Knaake963d6702017-08-11 21:03:111224- (void)presentBubblesIfEligible {
1225 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401226 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111227}
1228
sdefresnee65fd872016-12-19 13:38:131229#pragma mark - UIViewController methods
1230
1231// Perform additional set up after loading the view, typically from a nib.
1232- (void)viewDidLoad {
jif50d5ba252016-12-20 14:00:281233 CGRect initialViewsRect = self.view.frame;
1234 initialViewsRect.origin.y += StatusBarHeight();
1235 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131236 UIViewAutoresizing initialViewAutoresizing =
1237 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1238
stkhapuginf58b10d02017-04-10 13:36:171239 self.contentArea =
1240 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131241 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171242 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131243 self.typingShield.autoresizingMask = initialViewAutoresizing;
1244 [self.typingShield addTarget:self
1245 action:@selector(shieldWasTapped:)
1246 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131247 self.view.autoresizingMask = initialViewAutoresizing;
1248 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1249 [self.view addSubview:self.contentArea];
1250 [self.view addSubview:self.typingShield];
1251 [super viewDidLoad];
1252
1253 // Install fake status bar for iPad iOS7
1254 [self installFakeStatusBar];
1255 [self buildToolbarAndTabStrip];
1256 [self setUpViewLayout];
1257 // If the tab model and browser state are valid, finish initialization.
1258 if (_model && _browserState)
1259 [self addUIFunctionalityForModelAndBrowserState];
1260
1261 // Add a tap gesture recognizer to save the last tap location for the source
1262 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271263 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1264 initWithTarget:self
1265 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131266 [tapRecognizer setDelegate:self];
1267 [tapRecognizer setCancelsTouchesInView:NO];
1268 [_contentArea addGestureRecognizer:tapRecognizer];
1269}
1270
1271- (void)viewDidAppear:(BOOL)animated {
1272 [super viewDidAppear:animated];
1273 self.viewVisible = YES;
1274 [self updateDialogPresenterActiveState];
Cooper Knaake963d6702017-08-11 21:03:111275 [self presentBubblesIfEligible];
sdefresnee65fd872016-12-19 13:38:131276}
1277
1278- (void)viewWillAppear:(BOOL)animated {
1279 [super viewWillAppear:animated];
1280
1281 // Reparent the toolbar if it's been relinquished.
1282 if (_isToolbarControllerRelinquished)
1283 [self reparentToolbarController];
1284
1285 self.visible = YES;
1286
1287 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251288 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131289 _infoBarContainer->RestoreInfobars();
1290 }
1291
1292 // If the controller is suspended, or has been paged out due to low memory,
1293 // updating the view will be handled when it's displayed again.
1294 if (![_model webUsageEnabled] || !self.contentArea)
1295 return;
1296 // Update the displayed tab (if any; the switcher may not have created one
1297 // yet) in case it changed while showing the switcher.
1298 Tab* currentTab = [_model currentTab];
1299 if (currentTab)
1300 [self displayTab:currentTab isNewSelection:YES];
1301}
1302
1303- (void)viewWillDisappear:(BOOL)animated {
1304 self.viewVisible = NO;
1305 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131306 [[_model currentTab] wasHidden];
1307 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251308 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131309 _infoBarContainer->SuspendInfobars();
1310 }
1311 [super viewWillDisappear:animated];
1312}
1313
1314- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1315 duration:(NSTimeInterval)duration {
1316 [super willRotateToInterfaceOrientation:orient duration:duration];
1317 [self dismissPopups];
1318 [self reshowFindBarIfNeededWithCoordinator:nil];
1319}
1320
1321- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1322 [super didRotateFromInterfaceOrientation:orient];
1323
1324 // This reinitializes the toolbar, including updating the Overlay View,
1325 // if there is one.
1326 [self updateToolbar];
1327 [self infoBarContainerStateChanged:false];
1328}
1329
1330- (BOOL)prefersStatusBarHidden {
1331 return self.hideStatusBar;
1332}
1333
1334// Called when in the foreground and the OS needs more memory. Release as much
1335// as possible.
1336- (void)didReceiveMemoryWarning {
1337 // Releases the view if it doesn't have a superview.
1338 [super didReceiveMemoryWarning];
1339
1340 // Release any cached data, images, etc that aren't in use.
1341 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1342 // but since the FaviconCache uses obj-c in the header, it can't be included
1343 // there.
1344 if (_browserState) {
1345 FaviconLoader* loader =
1346 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1347 _browserState);
1348 if (loader)
1349 loader->PurgeCache();
1350 }
1351
1352 if (![self isViewLoaded]) {
1353 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1354 // as the BrowserViewController.
1355 self.contentArea = nil;
1356 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271357 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131358 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271359 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421360 self.recentTabsCoordinator = nil;
stkhapuginc9eee7b2017-04-10 15:49:271361 _toolbarController = nil;
1362 _toolbarModelDelegate = nil;
1363 _toolbarModelIOS = nil;
1364 _tabStripController = nil;
1365 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131366 }
1367}
1368
1369- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1370 [super traitCollectionDidChange:previousTraitCollection];
1371 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1372 // because in some cases the presented view controller isn't a child of the
1373 // BVC in the view controller hierarchy (some intervening object isn't a
1374 // view controller).
1375 [self.presentedViewController
1376 traitCollectionDidChange:previousTraitCollection];
1377 [_toolbarController traitCollectionDidChange:previousTraitCollection];
1378 // Update voice search bar visibility.
1379 [self updateVoiceSearchBarVisibilityAnimated:NO];
1380}
1381
1382- (void)viewWillTransitionToSize:(CGSize)size
1383 withTransitionCoordinator:
1384 (id<UIViewControllerTransitionCoordinator>)coordinator {
1385 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1386 [self dismissPopups];
1387 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1388}
1389
1390- (void)reshowFindBarIfNeededWithCoordinator:
1391 (id<UIViewControllerTransitionCoordinator>)coordinator {
1392 if (![_findBarController isFindInPageShown])
1393 return;
1394
1395 // Record focused state.
1396 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1397
1398 [self hideFindBarWithAnimation:NO];
1399
stkhapuginc9eee7b2017-04-10 15:49:271400 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131401 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1402 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271403 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131404 if (strongSelf)
1405 [strongSelf showFindBarWithAnimation:NO
1406 selectText:NO
1407 shouldFocus:isFocusedBeforeReshow];
1408 };
1409
1410 BOOL enqueued =
1411 [coordinator animateAlongsideTransition:nil completion:completion];
1412 if (!enqueued) {
1413 completion(nil);
1414 }
1415}
1416
1417- (void)dismissViewControllerAnimated:(BOOL)flag
1418 completion:(void (^)())completion {
1419 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271420 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131421 [super dismissViewControllerAnimated:flag
1422 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271423 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131424 [strongSelf setDismissingModal:NO];
1425 [strongSelf setPresenting:NO];
1426 if (completion)
1427 completion();
1428 [[strongSelf dialogPresenter] tryToPresent];
1429 }];
1430}
1431
1432- (void)presentViewController:(UIViewController*)viewControllerToPresent
1433 animated:(BOOL)flag
1434 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271435 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131436 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1437 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1438 // presented, to show a temporary view of the launch screen and then remove it
1439 // when the controller for the FRE has been presented. This fix should be
1440 // removed when the FRE startup code is rewritten.
1441 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1442 experimental_flags::AlwaysDisplayFirstRun()) &&
1443 !tests_hook::DisableFirstRun();
1444 // These if statements check that |presentViewController| is being called for
1445 // the FRE case.
1446 if (firstRunLaunch &&
1447 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1448 UINavigationController* navController =
1449 base::mac::ObjCCastStrict<UINavigationController>(
1450 viewControllerToPresent);
1451 if ([navController.topViewController
1452 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1453 self.hideStatusBar = YES;
1454
1455 // Load view from Launch Screen and add it to window.
1456 NSBundle* mainBundle = base::mac::FrameworkBundle();
1457 NSArray* topObjects =
1458 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1459 UIViewController* launchScreenController =
1460 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1461 // |launchScreenView| is loaded as an autoreleased object, and is retained
1462 // by the |completion| block below.
1463 UIView* launchScreenView = launchScreenController.view;
1464 launchScreenView.userInteractionEnabled = NO;
1465 launchScreenView.frame = self.view.window.bounds;
1466 [self.view.window addSubview:launchScreenView];
1467
1468 // Replace the completion handler sent to the superclass with one which
1469 // removes |launchScreenView| and resets the status bar. If |completion|
1470 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271471 __weak BrowserViewController* weakSelf = self;
1472 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131473 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271474 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131475 if (completion)
1476 completion();
stkhapuginc9eee7b2017-04-10 15:49:271477 };
sdefresnee65fd872016-12-19 13:38:131478 }
1479 }
1480
1481 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171482 if ([_sideSwipeController inSwipe]) {
1483 [_sideSwipeController resetContentView];
1484 }
sdefresnee65fd872016-12-19 13:38:131485
1486 [super presentViewController:viewControllerToPresent
1487 animated:flag
1488 completion:finalCompletionHandler];
1489}
1490
1491#pragma mark - Notification handling
1492
1493- (void)registerForNotifications {
1494 DCHECK(_model);
1495 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1496 [defaultCenter addObserver:self
1497 selector:@selector(pageLoadStarting:)
1498 name:kTabModelTabWillStartLoadingNotification
1499 object:_model];
1500 [defaultCenter addObserver:self
1501 selector:@selector(pageLoadStarted:)
1502 name:kTabModelTabDidStartLoadingNotification
1503 object:_model];
1504 [defaultCenter addObserver:self
1505 selector:@selector(pageLoadComplete:)
1506 name:kTabModelTabDidFinishLoadingNotification
1507 object:_model];
1508 [defaultCenter addObserver:self
1509 selector:@selector(tabDeselected:)
1510 name:kTabModelTabDeselectedNotification
1511 object:_model];
1512 [defaultCenter addObserver:self
1513 selector:@selector(tabWasAdded:)
1514 name:kTabModelNewTabWillOpenNotification
1515 object:_model];
1516}
1517
1518- (void)pageLoadStarting:(NSNotification*)notify {
1519 Tab* tab = notify.userInfo[kTabModelTabKey];
1520 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511521
1522 // Stop any Find in Page searches and close the find bar when navigating to a
1523 // new page.
1524 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361525
sdefresnee65fd872016-12-19 13:38:131526 if (tab == [_model currentTab]) {
1527 // TODO(pinkerton): Fill in here about hiding the forward button on
1528 // navigation.
1529 }
1530}
1531
1532- (void)pageLoadStarted:(NSNotification*)notify {
1533 Tab* tab = notify.userInfo[kTabModelTabKey];
1534 DCHECK(tab);
1535 if (tab == [_model currentTab]) {
1536 if (![self isTabNativePage:tab]) {
1537 [_toolbarController currentPageLoadStarted];
1538 }
1539 [self updateVoiceSearchBarVisibilityAnimated:NO];
1540 }
1541}
1542
1543- (void)pageLoadComplete:(NSNotification*)notify {
1544 // Update the UI, but only if the current tab.
1545 Tab* tab = notify.userInfo[kTabModelTabKey];
1546 if (tab == [_model currentTab]) {
1547 // There isn't any need to update the toolbar here. When the page finishes,
1548 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1549 }
1550
1551 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1552
1553 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1554}
1555
1556- (void)tabDeselected:(NSNotification*)notify {
1557 DCHECK(notify);
1558 Tab* tab = notify.userInfo[kTabModelTabKey];
1559 DCHECK(tab);
1560 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221561 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131562}
1563
1564- (void)tabWasAdded:(NSNotification*)notify {
1565 Tab* tab = notify.userInfo[kTabModelTabKey];
1566 DCHECK(tab);
1567
Eugene But56efc322017-08-11 14:03:441568 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131569
1570 // When adding new tabs, check what kind of reminder infobar should
1571 // be added to the new tab. Try to add only one of them.
1572 // This check is done when a new tab is added either through the Tools Menu
1573 // "New Tab" or through "New Tab" in Stack View Controller. This method
1574 // is called after a new tab has added and finished initial navigation.
1575 // If this is added earlier, the initial navigation may end up clearing
1576 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301577 web::WebState* webState = tab.webState;
1578 DCHECK(webState);
1579
1580 infobars::InfoBarManager* infoBarManager =
1581 InfoBarManagerImpl::FromWebState(webState);
1582 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131583 forTabId:[tab tabId]];
1584 if (!ReSignInInfoBarDelegate::Create(_browserState, tab)) {
Sylvain Defresneed8c0db2017-08-31 16:29:521585 DisplaySyncErrors(_browserState, tab);
sdefresnee65fd872016-12-19 13:38:131586 }
1587
1588 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501589 // phone-specific. Call the foreground tab added completion block; for
1590 // iPhones, this will get executed after the animation has finished.
1591 if (IsIPadIdiom()) {
1592 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431593 // This callback is called before webState is activated (on
1594 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1595 // callback asynchronously to be sure the activation is complete.
1596 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011597 // Test existence again as the block may have been deleted.
1598 if (self.foregroundTabWasAddedCompletionBlock) {
1599 self.foregroundTabWasAddedCompletionBlock();
1600 self.foregroundTabWasAddedCompletionBlock = nil;
1601 }
Olivier Robinc7e46242017-09-06 07:55:431602 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501603 }
sdefresnee65fd872016-12-19 13:38:131604 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501605 }
sdefresnee65fd872016-12-19 13:38:131606
1607 // Do nothing if browsing is currently suspended. The BVC will set everything
1608 // up correctly when browsing resumes.
1609 if (!self.visible || ![_model webUsageEnabled])
1610 return;
1611
1612 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1613
1614 // Block that starts voice search at the end of new Tab animation if
1615 // necessary.
1616 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1617 if (_startVoiceSearchAfterNewTabAnimation) {
1618 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001619 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131620 }
1621 };
1622
kkhorimotoa44349c12017-04-12 23:02:121623 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131624 if (!inBackground) {
1625 UIView* animationParentView = _contentArea;
1626 // Create the new page image, and load with the new tab page snapshot.
1627 CGFloat newPageOffset = 0;
1628 UIImageView* newPage;
kkhorimotob110b262017-06-01 18:38:251629 if (tab.lastCommittedURL == GURL(kChromeUINewTabURL) && !_isOffTheRecord &&
sdefresnee65fd872016-12-19 13:38:131630 !IsIPadIdiom()) {
1631 animationParentView = self.view;
1632 newPage = [self pageFullScreenOpenCloseAnimationView];
1633 } else {
1634 newPage = [self pageOpenCloseAnimationView];
1635 }
1636 newPageOffset = newPage.frame.origin.y;
1637
1638 [tab view].frame = _contentArea.bounds;
1639 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1640 [animationParentView addSubview:newPage];
1641 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521642 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
sdefresnee65fd872016-12-19 13:38:131643 newPage, -newPageOffset,
1644 newPage.frame.size.height - newPage.image.size.height, origin,
1645 _isOffTheRecord, NULL, ^{
1646 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121647 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421648 // Use the model's currentTab here because it is possible that it can
1649 // be reset to a new value before the new Tab animation finished (e.g.
1650 // if another Tab shows a dialog via |dialogPresenter|). However, that
1651 // tab's view hasn't been displayed yet because it was in a new tab
1652 // animation.
1653 Tab* currentTab = [_model currentTab];
1654 if (currentTab) {
1655 [self tabSelected:currentTab];
1656 }
sdefresnee65fd872016-12-19 13:38:131657 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391658
1659 if (self.foregroundTabWasAddedCompletionBlock) {
1660 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451661 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391662 }
sdefresnee65fd872016-12-19 13:38:131663 });
1664 } else {
1665 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1666 // snapshot before adding the views needed for the background animation.
1667 Tab* topTab = [_model currentTab];
1668 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1669 visibleFrameOnly:self.isToolbarOnScreen];
1670 // Add three layers in order on top of the contentArea for the animation:
1671 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271672 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131673 InstallBackgroundInView(background);
1674 [_contentArea addSubview:background];
1675
1676 // 2. A CardView displaying the data from the current tab.
1677 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1678 NSString* title = [topTab title];
1679 if (![title length])
1680 title = [topTab urlDisplayString];
1681 [topCard setTitle:title];
1682 [topCard setFavicon:[topTab favicon]];
1683 [topCard setImage:image];
1684
1685 // 3. A new, blank CardView to represent the new tab being added.
1686 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521687 page_animation_util::AnimateNewBackgroundPageWithCompletion(
sdefresnee65fd872016-12-19 13:38:131688 topCard, [_contentArea frame], IsPortrait(), ^{
1689 [background removeFromSuperview];
1690 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121691 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131692 // Resnapshot the top card if it has its own toolbar, as the toolbar
1693 // will be captured in the new tab animation, but isn't desired for
1694 // the stack view snapshots.
1695 id nativeController = [self nativeControllerForTab:topTab];
1696 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1697 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1698 startVoiceSearchIfNecessaryBlock();
1699 });
peterlaurens9f1b6e02017-06-22 17:46:451700 // Reset the foreground tab completion block so that it can never be
1701 // called more than once regardless of foreground/background tab
1702 // appearances.
1703 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131704 }
1705}
1706
1707#pragma mark - UI Configuration and Layout
1708
1709- (void)updateWithTabModel:(TabModel*)model
1710 browserState:(ios::ChromeBrowserState*)browserState {
1711 DCHECK(model);
1712 DCHECK(browserState);
1713 DCHECK(!_model);
1714 DCHECK(!_browserState);
1715 _browserState = browserState;
1716 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271717 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381718
sdefresnee65fd872016-12-19 13:38:131719 [_model addObserver:self];
1720
1721 if (!_isOffTheRecord) {
1722 [DefaultIOSWebViewFactory
1723 registerWebViewFactory:[ChromeWebViewFactory class]];
1724 }
1725 NSUInteger count = [_model count];
1726 for (NSUInteger index = 0; index < count; ++index)
1727 [self installDelegatesForTab:[_model tabAtIndex:index]];
1728
1729 [self registerForNotifications];
1730
gambardbdc07cc2017-02-03 16:43:111731 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121732 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271733 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131734
sdefresnedc432f42017-01-17 14:36:591735 // Register for bookmark changed notification (BookmarkModel may be null
1736 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131737 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591738 if (_bookmarkModel) {
1739 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1740 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1741 }
sdefresnee65fd872016-12-19 13:38:131742}
1743
sdefresnee65fd872016-12-19 13:38:131744- (void)browserStateDestroyed {
1745 [self setActive:NO];
Gauthier Ambard8160349c2017-09-06 14:43:241746 [self setToolbarBackgroundAlpha:1.0];
sdefresnee65fd872016-12-19 13:38:131747 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271748 _paymentRequestManager = nil;
sdefresnee65fd872016-12-19 13:38:131749 [_toolbarController browserStateDestroyed];
1750 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381751
1752 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431753 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111754 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381755 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271756 [_pageInfoCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381757
sdefresnee65fd872016-12-19 13:38:131758 // The file remover needs the browser state, so needs to be destroyed now.
stkhapuginc9eee7b2017-04-10 15:49:271759 _externalFileRemover = nil;
sdefresnee65fd872016-12-19 13:38:131760 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391761 [_dispatcher stopDispatchingToTarget:self];
1762 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131763}
1764
1765- (void)installFakeStatusBar {
1766 if (IsIPadIdiom()) {
1767 CGFloat statusBarHeight = StatusBarHeight();
1768 CGRect statusBarFrame =
1769 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271770 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131771 [statusBarView setBackgroundColor:TabStrip::BackgroundColor()];
1772 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1773 [statusBarView layer].zPosition = 99;
1774 [[self view] addSubview:statusBarView];
1775 }
1776
1777 // Add a white bar on phone so that the status bar on the NTP is white.
1778 if (!IsIPadIdiom()) {
1779 CGFloat statusBarHeight = StatusBarHeight();
1780 CGRect statusBarFrame =
1781 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271782 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131783 [statusBarView setBackgroundColor:[UIColor whiteColor]];
1784 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1785 [self.view insertSubview:statusBarView atIndex:0];
1786 }
1787}
1788
1789// Create the UI elements. May or may not have valid browser state & tab model.
1790- (void)buildToolbarAndTabStrip {
1791 DCHECK([self isViewLoaded]);
1792 DCHECK(!_toolbarModelDelegate);
1793
Rohit Rao44f204302017-08-10 14:49:541794 // Initialize the prerender service before creating the toolbar controller.
1795 PrerenderService* prerenderService =
1796 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1797 if (prerenderService) {
1798 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131799 }
1800
1801 // Create the toolbar model and controller.
Rohit Rao44f204302017-08-10 14:49:541802 id<PreloadProvider> preloadProvider =
1803 prerenderService ? prerenderService->GetPreloadProvider() : nil;
rohitrao8c4c7fd2017-04-03 15:31:201804 _toolbarModelDelegate.reset(
1805 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131806 _toolbarModelIOS.reset([_dependencyFactory
1807 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
Mark Cogan6ebbde02017-07-07 12:50:131808 _toolbarController =
1809 [_dependencyFactory newWebToolbarControllerWithDelegate:self
1810 urlLoader:self
Rohit Rao44f204302017-08-10 14:49:541811 preloadProvider:preloadProvider
Mark Cogan6ebbde02017-07-07 12:50:131812 dispatcher:self.dispatcher];
justincohen75011c32017-04-28 16:31:391813 [_dispatcher startDispatchingToTarget:_toolbarController
1814 forProtocol:@protocol(OmniboxFocuser)];
sdefresnee65fd872016-12-19 13:38:131815 [_toolbarController setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271816 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131817 _voiceSearchController->SetDelegate(_toolbarController);
1818
1819 // If needed, create the tabstrip.
1820 if (IsIPadIdiom()) {
stkhapuginc9eee7b2017-04-10 15:49:271821 _tabStripController =
Mark Cogandfcdea72017-07-18 13:47:381822 [_dependencyFactory newTabStripControllerWithTabModel:_model
1823 dispatcher:self.dispatcher];
stkhapuginc9eee7b2017-04-10 15:49:271824 _tabStripController.fullscreenDelegate = self;
sdefresnee65fd872016-12-19 13:38:131825 }
1826
1827 // Create infobar container.
1828 if (!_infoBarContainerDelegate) {
1829 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1830 _infoBarContainer.reset(
1831 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1832 }
1833}
1834
1835// Enable functionality that only makes sense if the views are loaded and
1836// both browser state and tab model are valid.
1837- (void)addUIFunctionalityForModelAndBrowserState {
1838 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051839 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131840 DCHECK(_model);
1841 DCHECK([self isViewLoaded]);
1842
1843 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1844
Rohit Raoaf46af92017-08-10 12:52:301845 infobars::InfoBarManager* infoBarManager = nullptr;
1846 if (_model.currentTab) {
1847 DCHECK(_model.currentTab.webState);
1848 infoBarManager =
1849 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1850 }
sdefresnee65fd872016-12-19 13:38:131851 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1852
sczsdd860eba2017-08-10 01:55:381853 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431854 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1855 initWithBaseViewController:self];
1856 _activityServiceCoordinator.dispatcher = _dispatcher;
1857 _activityServiceCoordinator.tabModel = _model;
1858 _activityServiceCoordinator.browserState = _browserState;
1859 _activityServiceCoordinator.positionProvider = _toolbarController;
1860 _activityServiceCoordinator.presentationProvider = self;
1861 _activityServiceCoordinator.snackbarProvider = self;
1862
Rohit Raocda0a992017-08-16 15:37:111863 _qrScannerCoordinator =
1864 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
1865 _qrScannerCoordinator.dispatcher = _dispatcher;
1866 _qrScannerCoordinator.loadProvider = _toolbarController;
1867 _qrScannerCoordinator.presentationProvider = self;
1868
sczsdd860eba2017-08-10 01:55:381869 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:131870 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:381871 _tabHistoryCoordinator.dispatcher = _dispatcher;
1872 _tabHistoryCoordinator.positionProvider = _toolbarController;
1873 _tabHistoryCoordinator.tabModel = _model;
1874 _tabHistoryCoordinator.presentationProvider = self;
1875 _tabHistoryCoordinator.tabHistoryUIUpdater = _toolbarController;
1876
sczs6ae47ad2017-09-06 17:26:531877 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
1878 _sadTabCoordinator.dispatcher = _dispatcher;
1879
Gregory Chatzinoffdf93d692017-09-09 01:32:271880 _pageInfoCoordinator =
1881 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
1882 _pageInfoCoordinator.browserState = _browserState;
1883 _pageInfoCoordinator.dispatcher = _dispatcher;
1884 _pageInfoCoordinator.loader = self;
1885 _pageInfoCoordinator.presentationProvider = self;
1886 _pageInfoCoordinator.tabModel = _model;
1887
mathp9b4c11d2017-07-06 20:24:131888 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:271889 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:131890 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:201891 browserState:_browserState
1892 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:051893 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:191894 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:131895 }
1896}
1897
1898// Set the frame for the various views. View must be loaded.
1899- (void)setUpViewLayout {
1900 DCHECK([self isViewLoaded]);
1901
1902 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
1903
1904 CGFloat minY = [self headerOffset];
1905
1906 // If needed, position the tabstrip.
1907 if (IsIPadIdiom()) {
1908 [self layoutTabStripForWidth:widthOfView];
1909 [[self view] addSubview:[_tabStripController view]];
1910 minY += CGRectGetHeight([[_tabStripController view] frame]);
1911 }
1912
1913 // Position the toolbar next, either at the top of the browser view or
1914 // directly under the tabstrip.
1915 CGRect toolbarFrame = [[_toolbarController view] frame];
1916 toolbarFrame.origin = CGPointMake(0, minY);
1917 toolbarFrame.size.width = widthOfView;
1918 [[_toolbarController view] setFrame:toolbarFrame];
1919
1920 // Place the infobar container above the content area.
1921 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
1922 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
1923
1924 // Place the toolbar controller above the infobar container.
1925 [[self view] insertSubview:[_toolbarController view]
1926 aboveSubview:infoBarContainerView];
1927 minY += CGRectGetHeight(toolbarFrame);
1928
1929 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
1930 // content slightly.
1931 minY -= [ToolbarController toolbarDropShadowHeight];
1932
1933 // Adjust the content area to be under the toolbar, for fullscreen or below
1934 // the toolbar is not fullscreen.
1935 CGRect contentFrame = [_contentArea frame];
1936 CGFloat marginWithHeader = StatusBarHeight();
1937 CGFloat overlap = [self headerHeight] != 0 ? marginWithHeader : minY;
1938 contentFrame.size.height = CGRectGetMaxY(contentFrame) - overlap;
1939 contentFrame.origin.y = overlap;
1940 [_contentArea setFrame:contentFrame];
1941
1942 // Adjust the infobar container to be either at the bottom of the screen
1943 // (iPhone) or on the lower toolbar edge (iPad).
1944 CGRect infoBarFrame = contentFrame;
1945 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
1946 infoBarFrame.size.height = 0;
1947 [infoBarContainerView setFrame:infoBarFrame];
1948
1949 // Attach the typing shield to the content area but have it hidden.
1950 [_typingShield setFrame:[_contentArea frame]];
1951 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
1952 [_typingShield setHidden:YES];
1953 _typingShield.accessibilityIdentifier = @"Typing Shield";
1954 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1955}
1956
1957- (void)layoutTabStripForWidth:(CGFloat)maxWidth {
1958 UIView* tabStripView = [_tabStripController view];
1959 CGRect tabStripFrame = [tabStripView frame];
1960 tabStripFrame.origin = CGPointZero;
1961 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
1962 // because the CGPointZero above will break reset the offset, but it's not
1963 // clear what removing that will do.
1964 tabStripFrame.origin.y = [self headerOffset];
1965 tabStripFrame.size.width = maxWidth;
1966 [tabStripView setFrame:tabStripFrame];
1967}
1968
1969- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
1970 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:441971 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131972
kkhorimotoa44349c12017-04-12 23:02:121973 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:131974 // Hide findbar. |updateToolbar| will restore the findbar later.
1975 [self hideFindBarWithAnimation:NO];
1976
1977 // Make new content visible, resizing it first as the orientation may
1978 // have changed from the last time it was displayed.
1979 [[tab view] setFrame:_contentArea.bounds];
1980 [_contentArea displayContentView:[tab view]];
1981 }
1982 [self updateToolbar];
1983
1984 if (newSelection)
1985 [_toolbarController selectedTabChanged];
1986
1987 // Notify the Tab that it was displayed.
1988 [tab wasShown];
1989}
1990
1991- (void)initializeBookmarkInteractionController {
1992 if (_bookmarkInteractionController)
1993 return;
stkhapuginc9eee7b2017-04-10 15:49:271994 _bookmarkInteractionController =
1995 [[BookmarkInteractionController alloc] initWithBrowserState:_browserState
1996 loader:self
1997 parentController:self];
sdefresnee65fd872016-12-19 13:38:131998}
1999
2000// Update the state of back and forward buttons, hiding the forward button if
2001// there is nowhere to go. Assumes the model's current tab is up to date.
2002- (void)updateToolbar {
2003 // If the BVC has been partially torn down for low memory, wait for the
2004 // view rebuild to handle toolbar updates.
2005 if (!(_toolbarModelIOS && _browserState))
2006 return;
2007
2008 Tab* tab = [_model currentTab];
2009 if (![tab navigationManager])
2010 return;
2011 [_toolbarController updateToolbarState];
2012 [_toolbarController setShareButtonEnabled:self.canShowShareMenu];
2013
Rohit Rao44f204302017-08-10 14:49:542014 PrerenderService* prerenderService =
2015 PrerenderServiceFactory::GetForBrowserState(self.browserState);
2016 BOOL isPrerenderTab =
2017 prerenderService && prerenderService->IsWebStatePrerendered(tab.webState);
2018 if (isPrerenderTab && !_toolbarModelIOS->IsLoading())
sdefresnee65fd872016-12-19 13:38:132019 [_toolbarController showPrerenderingAnimation];
2020
2021 // Also update the loading state for the tools menu (that is really an
2022 // extension of the toolbar on the iPhone).
2023 if (!IsIPadIdiom())
2024 [[_toolbarController toolsPopupController]
2025 setIsTabLoading:_toolbarModelIOS->IsLoading()];
2026
rohitrao005a6432017-03-16 20:52:422027 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2028 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132029 [self showFindBarWithAnimation:NO
2030 selectText:YES
2031 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362032 }
sdefresnee65fd872016-12-19 13:38:132033
2034 // Hide the toolbar if displaying phone NTP.
2035 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552036 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132037 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552038 if (item) {
2039 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132040 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2041 hideToolbar = isNTP && !_isOffTheRecord &&
2042 ![_toolbarController isOmniboxFirstResponder] &&
2043 ![_toolbarController showingOmniboxPopup];
2044 }
2045 [[_toolbarController view] setHidden:hideToolbar];
2046 }
2047}
2048
2049- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122050 self.dialogPresenter.active =
2051 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132052}
2053
2054- (void)dismissPopups {
jif7fed8122017-02-08 13:15:252055 [_toolbarController dismissToolsMenuPopup];
Gregory Chatzinoffdf93d692017-09-09 01:32:272056 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:472057 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382058}
2059
Cooper Knaakd0a974cd2017-08-10 18:05:472060- (BubbleViewControllerPresenter*)
2061bubblePresenterForFeature:(const base::Feature&)feature
2062 direction:(BubbleArrowDirection)direction
2063 alignment:(BubbleAlignment)alignment
2064 text:(NSString*)text {
Cooper Knaak33f9f402017-08-09 18:04:382065 if (!feature_engagement::TrackerFactory::GetForBrowserState(_browserState)
2066 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472067 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382068 }
2069 // Capture |weakSelf| instead of the feature engagement tracker object
2070 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2071 // the feature engagement tracker will remain pointing to invalid memory if
2072 // its owner (the ChromeBrowserState) is deallocated.
2073 __weak BrowserViewController* weakSelf = self;
2074 void (^dismissalCallback)(void) = ^() {
2075 BrowserViewController* strongSelf = weakSelf;
2076 if (strongSelf) {
2077 feature_engagement::TrackerFactory::GetForBrowserState(
2078 strongSelf.browserState)
2079 ->Dismissed(feature);
2080 }
2081 };
2082
Cooper Knaakd0a974cd2017-08-10 18:05:472083 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382084 [[BubbleViewControllerPresenter alloc] initWithText:text
2085 arrowDirection:direction
2086 alignment:alignment
2087 dismissalCallback:dismissalCallback];
2088
Cooper Knaakd0a974cd2017-08-10 18:05:472089 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132090}
2091
Cooper Knaak120cee5e2017-08-10 20:57:002092- (void)presentNewTabTipBubbleOnInitialized {
2093 // If the tab tip bubble has already been presented and the user is still
2094 // considered engaged, it can't be overwritten or set to |nil| or else it will
2095 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2096 // can be safely overwritten or set to |nil|.
2097 if (!self.tabTipBubblePresenter.isUserEngaged) {
2098 __weak BrowserViewController* weakSelf = self;
2099 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2100 [weakSelf presentNewTabTipBubble];
2101 };
2102
2103 // Because the new tab tip occurs on startup, the feature engagement
2104 // tracker's database is not guaranteed to be loaded by this time. For the
2105 // bubble to appear properly, a callback is used to guarantee the event data
2106 // is loaded before the check to see if the promotion should be displayed.
2107 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2108 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2109 }
2110}
2111
2112- (void)presentNewTabTipBubble {
2113 NSString* text =
2114 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2115 CGPoint tabSwitcherAnchor;
2116 if (IsIPadIdiom()) {
2117 DCHECK([self.tabStripController
2118 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
2119 tabSwitcherAnchor = [self.tabStripController
2120 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2121 } else {
2122 DCHECK([self.toolbarController
2123 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
2124 tabSwitcherAnchor = [self.toolbarController
2125 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2126 }
Cooper Knaake963d6702017-08-11 21:03:112127 // If the feature engagement tracker does not consider it valid to display
2128 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2129 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002130 self.tabTipBubblePresenter =
2131 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2132 direction:BubbleArrowDirectionUp
2133 alignment:BubbleAlignmentTrailing
2134 text:text];
2135 [self.tabTipBubblePresenter presentInViewController:self
2136 view:self.view
2137 anchorPoint:tabSwitcherAnchor];
2138}
2139
Helen Yang9175bd52017-08-12 00:28:402140- (void)presentNewIncognitoTabTipBubbleOnInitialized {
2141 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2142 // user is still considered engaged.
2143 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2144 __weak BrowserViewController* weakSelf = self;
2145 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2146 [weakSelf presentNewIncognitoTabTipBubble];
2147 };
2148
2149 // Use a callback in case the new incognito tab tip should be shown on
2150 // startup. This ensures that the tracker's database will be fully loaded
2151 // before checking if the promotion should be displayed.
2152 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2153 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2154 }
2155}
2156
2157- (void)presentNewIncognitoTabTipBubble {
2158 DCHECK([self.toolbarController
2159 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2160 NSString* text = l10n_util::GetNSStringWithFixup(
2161 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
2162 CGPoint toolsButtonAnchor = [self.toolbarController
2163 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2164 self.incognitoTabTipBubblePresenter =
2165 [self bubblePresenterForFeature:feature_engagement::
2166 kIPHNewIncognitoTabTipFeature
2167 direction:BubbleArrowDirectionUp
2168 alignment:BubbleAlignmentTrailing
2169 text:text];
2170 [self.incognitoTabTipBubblePresenter
2171 presentInViewController:self
2172 view:self.view
2173 anchorPoint:toolsButtonAnchor];
2174 // Only trigger the tools menu button animation if the bubble is shown.
2175 if (self.incognitoTabTipBubblePresenter) {
2176 [self.toolbarController triggerToolsMenuButtonAnimation];
2177 }
2178}
2179
sdefresnee65fd872016-12-19 13:38:132180#pragma mark - Tap handling
2181
Mark Cogandfcdea72017-07-18 13:47:382182- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032183 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2184 _lastTapPoint = CGPointZero;
2185 } else {
2186 _lastTapPoint =
2187 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132188 }
Mark Cogane01ebce2017-07-12 19:31:032189 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132190}
2191
2192- (CGPoint)lastTapPoint {
2193 if (CACurrentMediaTime() - _lastTapTime < 1) {
2194 return _lastTapPoint;
2195 }
2196 return CGPointZero;
2197}
2198
2199- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2200 UIView* view = gestureRecognizer.view;
2201 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2202 _lastTapPoint =
2203 [[view superview] convertPoint:viewCoordinate toView:self.view];
2204 _lastTapTime = CACurrentMediaTime();
2205}
2206
2207- (BOOL)addTabIfNoTabWithNormalBrowserState {
2208 if (![_model count]) {
2209 if (!_isOffTheRecord) {
2210 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2211 transition:ui::PAGE_TRANSITION_TYPED];
2212 return YES;
2213 }
2214 }
2215 return NO;
2216}
2217
2218#pragma mark - Tab creation and selection
2219
2220// Called when either a tab finishes loading or when a tab with finished content
2221// is added directly to the model via pre-rendering.
2222- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2223 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2224
2225 // Persist the session on a delay.
2226 [_model saveSessionImmediately:NO];
2227}
2228
2229- (Tab*)addSelectedTabWithURL:(const GURL&)url
2230 postData:(TemplateURLRef::PostContent*)postData
2231 transition:(ui::PageTransition)transition {
2232 return [self addSelectedTabWithURL:url
2233 postData:postData
2234 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492235 transition:transition
2236 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132237}
2238
2239- (Tab*)addSelectedTabWithURL:(const GURL&)url
2240 transition:(ui::PageTransition)transition {
2241 return [self addSelectedTabWithURL:url
2242 atIndex:[_model count]
2243 transition:transition];
2244}
2245
2246- (Tab*)addSelectedTabWithURL:(const GURL&)url
2247 atIndex:(NSUInteger)position
2248 transition:(ui::PageTransition)transition {
2249 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492250 atIndex:position
2251 transition:transition
2252 tabAddedCompletion:nil];
2253}
2254
2255- (Tab*)addSelectedTabWithURL:(const GURL&)url
2256 atIndex:(NSUInteger)position
2257 transition:(ui::PageTransition)transition
2258 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2259 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132260 postData:NULL
2261 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492262 transition:transition
2263 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132264}
2265
2266- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2267 postData:(TemplateURLRef::PostContent*)postData
2268 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492269 transition:(ui::PageTransition)transition
2270 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132271 if (position == NSNotFound)
2272 position = [_model count];
2273 DCHECK(position <= [_model count]);
2274
2275 web::NavigationManager::WebLoadParams params(URL);
2276 params.transition_type = transition;
2277 if (postData) {
2278 // Extract the content type and post params from |postData| and add them
2279 // to the load params.
2280 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2281 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2282 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172283 params.post_data.reset(data);
2284 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132285 }
Olivier Robind508a5632017-07-19 16:29:492286
2287 if (tabAddedCompletion) {
2288 if (self.foregroundTabWasAddedCompletionBlock) {
2289 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2290 self.foregroundTabWasAddedCompletionBlock;
2291 self.foregroundTabWasAddedCompletionBlock = ^{
2292 oldForegroundTabWasAddedCompletionBlock();
2293 tabAddedCompletion();
2294 };
2295 } else {
2296 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2297 }
2298 }
2299
sdefresnea6395912017-03-01 01:14:352300 Tab* tab = [_model insertTabWithLoadParams:params
2301 opener:nil
2302 openedByDOM:NO
2303 atIndex:position
2304 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132305 return tab;
2306}
2307
olivierrobin889af53f2017-03-01 14:56:322308// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132309- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322310 web::WebState* webState = tab.webState;
2311 if (!webState)
2312 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392313 web::NavigationItem* visibleItem =
2314 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322315 if (!visibleItem)
2316 return NO;
2317 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132318}
2319
2320- (void)expectNewForegroundTab {
2321 _expectingForegroundTab = YES;
2322}
2323
2324- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2325 CGRect viewBounds, remainder;
2326 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2327 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172328 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132329}
2330
2331- (UIImageView*)pageOpenCloseAnimationView {
2332 CGRect frame = [_contentArea bounds];
2333
2334 frame.size.height = frame.size.height - [self headerHeight];
2335 frame.origin.y = [self headerHeight];
2336
stkhapuginf58b10d02017-04-10 13:36:172337 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132338 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2339 pageView.center = center;
2340
2341 pageView.backgroundColor = [UIColor whiteColor];
2342 return pageView;
2343}
2344
2345- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142346 // Unregistration happens when the Tab is removed from the TabModel.
Mike Doughertya1ec26402017-08-23 19:46:312347 tab.iOSCaptivePortalBlockingPageDelegate = self;
Mark Cogandfcdea72017-07-18 13:47:382348 tab.dispatcher = self.dispatcher;
sdefresnee65fd872016-12-19 13:38:132349 tab.dialogDelegate = self;
2350 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132351 tab.passKitDialogProvider = self;
2352 tab.fullScreenControllerDelegate = self;
2353 if (!IsIPadIdiom()) {
2354 tab.overscrollActionsControllerDelegate = self;
2355 }
olivierrobin9ce77b82017-01-12 17:29:192356 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132357 tab.tabSnapshottingDelegate = self;
2358 // Install the proper CRWWebController delegates.
2359 tab.webController.nativeProvider = self;
2360 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512361 // BrowserViewController presents SKStoreKitViewController on behalf of a
2362 // tab.
2363 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2364 if (tabHelper)
2365 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132366 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532367 // BrowserViewController owns the coordinator that displays the Sad Tab.
2368 if (!SadTabTabHelper::FromWebState(tab.webState))
2369 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sdefresnee65fd872016-12-19 13:38:132370}
2371
sdefresne49cf2862017-03-15 13:46:142372- (void)uninstallDelegatesForTab:(Tab*)tab {
Mike Doughertya1ec26402017-08-23 19:46:312373 tab.iOSCaptivePortalBlockingPageDelegate = nil;
Mark Cogandfcdea72017-07-18 13:47:382374 tab.dispatcher = nil;
sdefresne49cf2862017-03-15 13:46:142375 tab.dialogDelegate = nil;
2376 tab.snapshotOverlayProvider = nil;
2377 tab.passKitDialogProvider = nil;
2378 tab.fullScreenControllerDelegate = nil;
2379 if (!IsIPadIdiom()) {
2380 tab.overscrollActionsControllerDelegate = nil;
2381 }
2382 tab.tabHeadersDelegate = nil;
2383 tab.tabSnapshottingDelegate = nil;
2384 tab.webController.nativeProvider = nil;
2385 tab.webController.swipeRecognizerProvider = nil;
2386 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2387 if (tabHelper)
2388 tabHelper->SetLauncher(nil);
2389 tab.webState->SetDelegate(nullptr);
2390}
2391
sdefresnee65fd872016-12-19 13:38:132392// Called when a tab is selected in the model. Make any required view changes.
2393// The notification will not be sent when the tab is already the selected tab.
2394- (void)tabSelected:(Tab*)tab {
2395 DCHECK(tab);
2396
2397 // Ignore changes while the tab stack view is visible (or while suspended).
2398 // The display will be refreshed when this view becomes active again.
2399 if (!self.visible || ![_model webUsageEnabled])
2400 return;
2401
2402 [self displayTab:tab isNewSelection:YES];
2403
kkhorimotoa44349c12017-04-12 23:02:122404 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132405 // Now that the new tab has been displayed, return to normal. Rather than
2406 // keep a reference to the previous tab, just turn off preview mode for all
2407 // tabs (since doing so is a no-op for the tabs that don't have it set).
2408 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272409 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132410 [tab.webController setOverlayPreviewMode:NO];
2411 }
2412 }
2413}
2414
2415#pragma mark - External files
2416
2417- (NSSet*)referencedExternalFiles {
2418 NSSet* filesReferencedByTabs = [_model currentlyReferencedExternalFiles];
2419
2420 // TODO(noyau): this is incorrect, the caller should know that the model is
2421 // not loaded yet.
sdefresnedc432f42017-01-17 14:36:592422 if (!_bookmarkModel || !_bookmarkModel->loaded())
sdefresnee65fd872016-12-19 13:38:132423 return filesReferencedByTabs;
2424
2425 std::vector<bookmarks::BookmarkModel::URLAndTitle> bookmarks;
2426 _bookmarkModel->GetBookmarks(&bookmarks);
2427 NSMutableSet* bookmarkedFiles = [NSMutableSet set];
2428 for (const auto& bookmark : bookmarks) {
2429 GURL bookmarkUrl = bookmark.url;
2430 if (UrlIsExternalFileReference(bookmarkUrl)) {
2431 [bookmarkedFiles
2432 addObject:base::SysUTF8ToNSString(bookmarkUrl.ExtractFileName())];
2433 }
2434 }
2435 return [filesReferencedByTabs setByAddingObjectsFromSet:bookmarkedFiles];
2436}
2437
2438- (void)removeExternalFilesImmediately:(BOOL)immediately
2439 completionHandler:(ProceduralBlock)completionHandler {
2440 DCHECK_CURRENTLY_ON(web::WebThread::UI);
2441 DCHECK(!_isOffTheRecord);
2442 _externalFileRemover.reset(new ExternalFileRemover(self));
2443 // Delay the cleanup of the unreferenced files received from other apps
2444 // to not impact startup performance.
2445 int delay = immediately ? 0 : kExternalFilesCleanupDelaySeconds;
2446 _externalFileRemover->RemoveAfterDelay(
2447 base::TimeDelta::FromSeconds(delay),
stkhapuginf58b10d02017-04-10 13:36:172448 base::BindBlockArc(completionHandler ? completionHandler
2449 : ^{
2450 }));
sdefresnee65fd872016-12-19 13:38:132451}
2452
Sylvain Defresne41170aa2017-06-15 10:25:202453- (void)shutdown {
2454 DCHECK(!_isShutdown);
2455 _isShutdown = YES;
2456
2457 _tabStripController = nil;
2458 _infoBarContainer = nil;
2459 _readingListMenuNotifier = nil;
2460 if (_bookmarkModel)
2461 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2462 [_model removeObserver:self];
2463 [[UpgradeCenter sharedInstance] unregisterClient:self];
2464 [[NSNotificationCenter defaultCenter] removeObserver:self];
2465 [_toolbarController setDelegate:nil];
2466 if (_voiceSearchController)
2467 _voiceSearchController->SetDelegate(nil);
2468 [_rateThisAppDialog setDelegate:nil];
2469 [_model closeAllTabs];
Mohamad Ahmadi1ca550a82017-09-08 13:46:392470 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202471}
2472
sdefresnee65fd872016-12-19 13:38:132473#pragma mark - SnapshotOverlayProvider methods
2474
2475- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2476 NSMutableArray* overlays = [NSMutableArray array];
2477 if (![_model webUsageEnabled]) {
2478 return overlays;
2479 }
2480 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2481 if (voiceSearchView) {
2482 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272483 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132484 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272485 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132486 [overlays addObject:voiceSearchOverlay];
2487 }
2488 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2489 if (infoBarView) {
2490 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272491 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132492 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272493 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132494 [overlays addObject:infoBarOverlay];
2495 }
2496 return overlays;
2497}
2498
2499#pragma mark -
2500
2501- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2502 if (IsIPadIdiom()) {
2503 // Not using overlays on iPad because the content is pushed down by
2504 // infobar and the transition between snapshot and fresh page can
2505 // cause both snapshot and real infobars to appear at the same time.
2506 return nil;
2507 }
2508 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302509 if (currentTab && tab == currentTab) {
2510 DCHECK(currentTab.webState);
2511 infobars::InfoBarManager* infoBarManager =
2512 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132513 if (infoBarManager->infobar_count() > 0) {
2514 DCHECK(_infoBarContainer);
2515 return _infoBarContainer->view();
2516 }
2517 }
2518 return nil;
2519}
2520
2521- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272522 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132523 // There is no UI representation for non-current tabs or there is
2524 // no _infoBarContainer instantiated yet.
2525 // Return offset outside of tab.
2526 return CGRectGetMaxY(self.view.frame);
2527 } else if (IsIPadIdiom()) {
2528 // The infobars on iPad are display at the top of a tab.
2529 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2530 } else {
2531 // The infobars on iPhone are displayed at the bottom of a tab.
2532 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2533 return CGRectGetMaxY(visibleFrame) -
2534 CGRectGetHeight(_infoBarContainer->view().frame);
2535 }
2536}
2537
2538- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2539 Tab* currentTab = [_model currentTab];
2540 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2541 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2542 return _voiceSearchBar;
2543 }
2544 return nil;
2545}
2546
2547- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2548 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2549 // There is no UI representation for non-current tabs or there is
2550 // no visible voice search. Return offset outside of tab.
2551 return CGRectGetMaxY(self.view.frame);
2552 } else {
2553 // The voice search bar on iPhone is displayed at the bottom of a tab.
2554 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2555 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2556 }
2557}
2558
2559- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272560 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132561 VoiceSearchProvider* provider =
2562 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2563 if (provider) {
2564 _voiceSearchController =
2565 provider->CreateVoiceSearchController(_browserState);
2566 _voiceSearchController->SetDelegate(_toolbarController);
2567 }
2568 }
2569}
2570
2571- (void)ensureVoiceSearchBarCreated {
2572 if (_voiceSearchBar)
2573 return;
2574
2575 CGFloat width = CGRectGetWidth([[self view] bounds]);
2576 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2577 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272578 _voiceSearchBar = ios::GetChromeBrowserProvider()
2579 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002580 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132581 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2582 [_voiceSearchBar setHidden:YES];
2583 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2584 UIViewAutoresizingFlexibleWidth];
2585 [self.view insertSubview:_voiceSearchBar
2586 belowSubview:_infoBarContainer->view()];
2587}
2588
2589- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2590 // Voice search bar exists and is shown/hidden.
2591 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272592 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132593 return;
2594
2595 // Voice search bar doesn't exist and thus is not visible.
2596 if (!_voiceSearchBar && !show)
2597 return;
2598
2599 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272600 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132601 else
stkhapuginc9eee7b2017-04-10 15:49:272602 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132603}
2604
2605- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2606 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2607 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2608 self.shouldShowVoiceSearchBar) {
2609 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2610 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272611 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132612 }
2613 id currentNativeController =
2614 [self nativeControllerForTab:self.tabModel.currentTab];
2615 Protocol* possibleOwnerProtocol =
2616 @protocol(LogoAnimationControllerOwnerOwner);
2617 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2618 [currentNativeController logoAnimationControllerOwner]) {
2619 // If the current native controller is showing a GLIF view (e.g. the NTP
2620 // when there is no doodle), use that GLIFControllerOwner.
2621 return [currentNativeController logoAnimationControllerOwner];
2622 }
2623 return nil;
2624}
2625
2626#pragma mark - PassKitDialogProvider methods
2627
2628- (void)presentPassKitDialog:(NSData*)data {
2629 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272630 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132631 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272632 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132633 if (error || !data) {
2634 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302635 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132636 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302637 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132638 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2639 // NULL, replace if with DCHECK).
2640 if (infoBarManager)
2641 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2642 }
2643 } else {
2644 PKAddPassesViewController* passKitViewController =
2645 [_dependencyFactory newPassKitViewControllerForPass:pass];
2646 if (passKitViewController) {
2647 [self presentViewController:passKitViewController
2648 animated:YES
2649 completion:^{
2650 }];
2651 }
2652 }
2653}
2654
2655- (UIStatusBarStyle)preferredStatusBarStyle {
2656 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2657 : UIStatusBarStyleDefault;
2658}
2659
2660#pragma mark - CRWWebStateDelegate methods.
2661
eugenebut75a06fa72017-01-09 17:09:552662- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512663 createNewWebStateForURL:(const GURL&)URL
2664 openerURL:(const GURL&)openerURL
2665 initiatedByUser:(BOOL)initiatedByUser {
2666 // Check if requested web state is a popup and block it if necessary.
2667 if (!initiatedByUser) {
2668 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2669 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102670 // It's possible for a page to inject a popup into a window created via
2671 // window.open before its initial load is committed. Rather than relying
2672 // on the last committed or pending NavigationItem's referrer policy, just
2673 // use ReferrerPolicyDefault.
2674 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2675 // policy once referrer policies are correctly recorded in
2676 // NavigationItems.
2677 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512678 helper->HandlePopup(URL, referrer);
2679 return nil;
2680 }
2681 }
2682
2683 // Requested web state should not be blocked from opening.
2684 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2685 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2686
2687 // Tabs open by DOM are always renderer initiated.
2688 web::NavigationManager::WebLoadParams params(GURL{});
2689 params.transition_type = ui::PAGE_TRANSITION_LINK;
2690 params.is_renderer_initiated = true;
2691 Tab* childTab = [[self tabModel]
2692 insertTabWithLoadParams:params
2693 opener:currentTab
2694 openedByDOM:YES
2695 atIndex:TabModelConstants::kTabPositionAutomatically
2696 inBackground:NO];
2697 return childTab.webState;
2698}
2699
eugenebutb46b2122017-03-14 02:43:262700- (void)closeWebState:(web::WebState*)webState {
2701 // Only allow a web page to close itself if it was opened by DOM, or if there
2702 // are no navigation items.
2703 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582704 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262705
2706 if (![self tabModel])
2707 return;
2708
2709 NSUInteger index = [[self tabModel] indexOfTab:tab];
2710 if (index != NSNotFound)
2711 [[self tabModel] closeTabAtIndex:index];
2712}
2713
eugenebut275f5892017-03-09 22:20:512714- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552715 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2716 switch (params.disposition) {
2717 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2718 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2719 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352720 insertTabWithURL:params.url
2721 referrer:params.referrer
2722 transition:params.transition
2723 opener:LegacyTabHelper::GetTabForWebState(webState)
2724 openedByDOM:NO
2725 atIndex:TabModelConstants::kTabPositionAutomatically
2726 inBackground:(params.disposition ==
2727 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552728 return tab.webState;
2729 }
2730 case WindowOpenDisposition::CURRENT_TAB: {
2731 web::NavigationManager::WebLoadParams loadParams(params.url);
2732 loadParams.referrer = params.referrer;
2733 loadParams.transition_type = params.transition;
2734 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2735 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2736 return webState;
2737 }
eugenebutd0984e82017-02-22 23:47:512738 case WindowOpenDisposition::NEW_POPUP: {
2739 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352740 insertTabWithURL:params.url
2741 referrer:params.referrer
2742 transition:params.transition
2743 opener:LegacyTabHelper::GetTabForWebState(webState)
2744 openedByDOM:YES
2745 atIndex:TabModelConstants::kTabPositionAutomatically
2746 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512747 return tab.webState;
2748 }
eugenebut75a06fa72017-01-09 17:09:552749 default:
2750 NOTIMPLEMENTED();
2751 return nullptr;
2752 };
2753}
2754
Mike Dougherty4e6b3a32017-08-23 18:49:212755- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132756 handleContextMenu:(const web::ContextMenuParams&)params {
2757 // Prevent context menu from displaying for a tab which is no longer the
2758 // current one.
2759 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212760 return;
sdefresnee65fd872016-12-19 13:38:132761 }
2762
2763 // No custom context menu if no valid url is available in |params|.
2764 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212765 return;
sdefresnee65fd872016-12-19 13:38:132766 }
2767
2768 DCHECK(_browserState);
2769 DCHECK([_model currentTab]);
2770
stkhapuginc9eee7b2017-04-10 15:49:272771 _contextMenuCoordinator =
2772 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2773 params:params];
sdefresnee65fd872016-12-19 13:38:132774
2775 NSString* title = nil;
2776 ProceduralBlock action = nil;
2777
stkhapuginc9eee7b2017-04-10 15:49:272778 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132779 GURL link = params.link_url;
2780 bool isLink = link.is_valid();
2781 GURL imageUrl = params.src_url;
2782 bool isImage = imageUrl.is_valid();
kkhorimotob110b262017-06-01 18:38:252783 const GURL& committedURL = [_model currentTab].lastCommittedURL;
sdefresnee65fd872016-12-19 13:38:132784
2785 if (isLink) {
2786 if (link.SchemeIs(url::kJavaScriptScheme)) {
2787 // Open
2788 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2789 action = ^{
2790 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2791 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2792 };
2793 [_contextMenuCoordinator addItemWithTitle:title action:action];
2794 }
2795
2796 if (web::UrlHasWebScheme(link)) {
kkhorimotob110b262017-06-01 18:38:252797 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132798
sdefresnee65fd872016-12-19 13:38:132799 // Open in New Tab.
2800 title = l10n_util::GetNSStringWithFixup(
2801 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2802 action = ^{
2803 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002804 // The "New Tab" item in the context menu opens a new tab in the current
2805 // browser state. |isOffTheRecord| indicates whether or not the current
2806 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132807 [weakSelf webPageOrderedOpen:link
2808 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002809 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132810 inBackground:YES
2811 appendTo:kCurrentTab];
2812 };
2813 [_contextMenuCoordinator addItemWithTitle:title action:action];
2814 if (!_isOffTheRecord) {
2815 // Open in Incognito Tab.
2816 title = l10n_util::GetNSStringWithFixup(
2817 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2818 action = ^{
2819 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2820 [weakSelf webPageOrderedOpen:link
2821 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132822 inIncognito:YES
2823 inBackground:NO
2824 appendTo:kCurrentTab];
2825 };
2826 [_contextMenuCoordinator addItemWithTitle:title action:action];
2827 }
olivierrobin51d4cf42017-01-17 13:32:352828 }
gambard65d69152017-03-23 17:44:222829 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352830 NSString* innerText = params.link_text;
2831 if ([innerText length] > 0) {
2832 // Add to reading list.
2833 title = l10n_util::GetNSStringWithFixup(
2834 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2835 action = ^{
2836 Record(ACTION_READ_LATER, isImage, isLink);
2837 [weakSelf addToReadingListURL:link title:innerText];
2838 };
2839 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532840 }
sdefresnee65fd872016-12-19 13:38:132841 }
2842 // Copy Link.
2843 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2844 action = ^{
2845 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282846 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132847 };
2848 [_contextMenuCoordinator addItemWithTitle:title action:action];
2849 }
2850 if (isImage) {
kkhorimotob110b262017-06-01 18:38:252851 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132852 // Save Image.
gambard98b4ddf2017-04-18 07:14:052853 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132854 action = ^{
2855 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2856 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2857 };
2858 [_contextMenuCoordinator addItemWithTitle:title action:action];
2859 // Open Image.
2860 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2861 action = ^{
2862 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2863 [weakSelf loadURL:imageUrl
2864 referrer:referrer
2865 transition:ui::PAGE_TRANSITION_LINK
2866 rendererInitiated:YES];
2867 };
2868 [_contextMenuCoordinator addItemWithTitle:title action:action];
2869 // Open Image In New Tab.
2870 title = l10n_util::GetNSStringWithFixup(
2871 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2872 action = ^{
2873 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2874 [weakSelf webPageOrderedOpen:imageUrl
2875 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132876 inBackground:true
2877 appendTo:kCurrentTab];
2878 };
2879 [_contextMenuCoordinator addItemWithTitle:title action:action];
2880
2881 TemplateURLService* service =
2882 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:102883 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:132884 if (defaultURL && !defaultURL->image_url().empty() &&
2885 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
2886 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
2887 defaultURL->short_name());
2888 action = ^{
2889 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
2890 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
2891 };
2892 [_contextMenuCoordinator addItemWithTitle:title action:action];
2893 }
2894 }
2895
2896 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:132897}
2898
eugenebutb739bdc2017-01-25 06:32:482899- (void)webState:(web::WebState*)webState
2900 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
2901 // Display the action sheet with the arrow pointing at the top center of the
2902 // web contents.
sdefresne0452a9d2017-02-09 15:33:282903 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:482904 UIView* view = webState->GetView();
2905 CGPoint dialogLocation =
2906 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:282907 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:032908 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:172909 helper->PresentDialog(dialogLocation,
2910 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:052911 handler(shouldContinue);
2912 }));
eugenebutb739bdc2017-01-25 06:32:482913}
2914
sdefresnee65fd872016-12-19 13:38:132915- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
2916 (web::WebState*)webState {
2917 return _javaScriptDialogPresenter.get();
2918}
2919
eugenebut63232102017-01-19 16:19:402920- (void)webState:(web::WebState*)webState
2921 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
2922 proposedCredential:(NSURLCredential*)proposedCredential
2923 completionHandler:(void (^)(NSString* username,
2924 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:422925 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
2926 if ([tab isPrerenderTab]) {
2927 [tab discardPrerender];
2928 if (handler) {
2929 handler(nil, nil);
2930 }
2931 return;
2932 }
2933
eugenebut63232102017-01-19 16:19:402934 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
2935 proposedCredential:proposedCredential
2936 webState:webState
2937 completionHandler:handler];
2938}
2939
sdefresnee65fd872016-12-19 13:38:132940#pragma mark - FullScreenControllerDelegate methods
2941
2942- (CGFloat)headerOffset {
2943 if (IsIPadIdiom())
2944 return StatusBarHeight();
2945 return 0.0;
2946}
2947
stkhapugin952ecef2017-04-11 12:11:452948- (NSArray<HeaderDefinition*>*)headerViews {
2949 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:132950 if (![self isViewLoaded])
2951 return results;
2952
2953 if (!IsIPadIdiom()) {
2954 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452955 [results addObject:[HeaderDefinition
2956 definitionWithView:[_toolbarController view]
2957 headerBehaviour:Hideable
2958 heightAdjustment:[ToolbarController
2959 toolbarDropShadowHeight]
2960 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132961 }
2962 } else {
2963 if ([_tabStripController view]) {
stkhapugin952ecef2017-04-11 12:11:452964 [results addObject:[HeaderDefinition
2965 definitionWithView:[_tabStripController view]
2966 headerBehaviour:Hideable
2967 heightAdjustment:0.0
2968 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132969 }
2970 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452971 [results addObject:[HeaderDefinition
2972 definitionWithView:[_toolbarController view]
2973 headerBehaviour:Hideable
2974 heightAdjustment:[ToolbarController
2975 toolbarDropShadowHeight]
2976 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132977 }
2978 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:452979 [results addObject:[HeaderDefinition
2980 definitionWithView:[_findBarController view]
2981 headerBehaviour:Overlap
2982 heightAdjustment:0.0
2983 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:132984 }
2985 }
stkhapugin952ecef2017-04-11 12:11:452986 return [results copy];
sdefresnee65fd872016-12-19 13:38:132987}
2988
2989- (UIView*)footerView {
2990 return _voiceSearchBar;
2991}
2992
2993- (CGFloat)headerHeight {
2994 return [self headerHeightForTab:[_model currentTab]];
2995}
2996
2997- (CGFloat)headerHeightForTab:(Tab*)tab {
2998 id nativeController = [self nativeControllerForTab:tab];
2999 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3000 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3001 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3002 // On iPhone, don't add any header height for ToolbarOwner native
3003 // controllers when they're displaying their own toolbar.
3004 return 0;
3005 }
3006
stkhapugin952ecef2017-04-11 12:11:453007 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133008
3009 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453010 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133011 if (header.view && header.behaviour == Hideable) {
3012 height += CGRectGetHeight([header.view frame]) -
3013 header.heightAdjustement - header.inset;
3014 }
3015 }
3016
3017 return height - StatusBarHeight();
3018}
3019
3020- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193021 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133022}
3023
3024- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453025 NSArray<HeaderDefinition*>* headers = [self headerViews];
3026 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133027 return 0.0;
3028
3029 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3030 // API documentation.
3031 if ([[[self tabModel] currentTab] isPrerenderTab])
3032 return [self headerHeight];
3033
3034 UIView* topHeader = headers[0].view;
3035 return -(topHeader.frame.origin.y - [self headerOffset]);
3036}
3037
3038- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3039 UIView* footer = [self footerView];
3040 CGFloat headerHeight = [self headerHeight];
3041 if (!footer || headerHeight == 0)
3042 return 0.0;
3043
3044 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3045 CGFloat offset = headerOffset * footerHeight / headerHeight;
3046 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3047}
3048
3049- (void)fullScreenController:(FullScreenController*)controller
3050 headerAnimationCompleted:(BOOL)completed
3051 offset:(CGFloat)offset {
3052 if (completed)
justincohen04c27772016-12-21 20:16:593053 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133054}
3055
stkhapugin952ecef2017-04-11 12:11:453056- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133057 atOffset:(CGFloat)headerOffset {
3058 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453059 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:133060 CGRect frame = [header.view frame];
3061 frame.origin.y = height - headerOffset - header.inset;
3062 [header.view setFrame:frame];
3063 if (header.behaviour != Overlap)
3064 height += CGRectGetHeight(frame);
3065 }
3066}
3067
3068- (void)fullScreenController:(FullScreenController*)fullScreenController
3069 drawHeaderViewFromOffset:(CGFloat)headerOffset
3070 animate:(BOOL)animate {
3071 if ([_sideSwipeController inSwipe])
3072 return;
3073
3074 CGRect footerFrame = CGRectZero;
3075 UIView* footer = nil;
3076 // Only animate the voice search bar if the tab is a voice search results tab.
3077 if ([_model currentTab].isVoiceSearchResultsTab) {
3078 footer = [self footerView];
3079 footerFrame = footer.frame;
3080 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3081 }
3082
stkhapugin952ecef2017-04-11 12:11:453083 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133084 void (^block)(void) = ^{
3085 [self setFramesForHeaders:headers atOffset:headerOffset];
3086 footer.frame = footerFrame;
3087 };
3088 void (^completion)(BOOL) = ^(BOOL finished) {
3089 [self fullScreenController:fullScreenController
3090 headerAnimationCompleted:finished
3091 offset:headerOffset];
3092 };
3093 if (animate) {
Sylvain Defresneed8c0db2017-08-31 16:29:523094 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133095 delay:0.0
3096 options:UIViewAnimationOptionBeginFromCurrentState
3097 animations:block
3098 completion:completion];
3099 } else {
3100 block();
3101 completion(YES);
3102 }
3103}
3104
3105- (void)fullScreenController:(FullScreenController*)fullScreenController
3106 drawHeaderViewFromOffset:(CGFloat)headerOffset
3107 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3108 changeTopContentPadding:(BOOL)changeTopContentPadding
3109 scrollingToOffset:(CGFloat)contentOffset {
3110 DCHECK(webViewProxy);
3111 if ([_sideSwipeController inSwipe])
3112 return;
3113
3114 CGRect footerFrame;
3115 UIView* footer = nil;
3116 // Only animate the voice search bar if the tab is a voice search results tab.
3117 if ([_model currentTab].isVoiceSearchResultsTab) {
3118 footer = [self footerView];
3119 footerFrame = footer.frame;
3120 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3121 }
3122
stkhapugin952ecef2017-04-11 12:11:453123 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133124 void (^block)(void) = ^{
3125 [self setFramesForHeaders:headers atOffset:headerOffset];
3126 footer.frame = footerFrame;
3127 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3128 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3129 if (changeTopContentPadding)
3130 webViewProxy.topContentPadding = contentOffset;
3131 };
3132 void (^completion)(BOOL) = ^(BOOL finished) {
3133 [self fullScreenController:fullScreenController
3134 headerAnimationCompleted:finished
3135 offset:headerOffset];
3136 };
3137
Sylvain Defresneed8c0db2017-08-31 16:29:523138 [UIView animateWithDuration:kFullScreenControllerToolbarAnimationDuration
sdefresnee65fd872016-12-19 13:38:133139 delay:0.0
3140 options:UIViewAnimationOptionBeginFromCurrentState
3141 animations:block
3142 completion:completion];
3143}
3144
3145#pragma mark - VoiceSearchBarOwner
3146
3147- (id<VoiceSearchBar>)voiceSearchBar {
3148 return _voiceSearchBar;
3149}
3150
3151#pragma mark - Install OverScrollActionController method.
3152- (void)setOverScrollActionControllerToStaticNativeContent:
3153 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343154 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133155 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173156 [[OverscrollActionsController alloc]
3157 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133158 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053159 OverscrollStyle style = _isOffTheRecord
3160 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3161 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133162 controller.style = style;
3163 nativeContent.overscrollActionsController = controller;
3164 }
3165}
3166
3167#pragma mark - OverscrollActionsControllerDelegate methods.
3168
3169- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053170 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133171 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053172 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383173 [self.dispatcher
3174 openNewTab:[OpenNewTabCommand
3175 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133176 break;
rohitrao922b7111c2017-01-03 14:31:053177 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243178 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133179 break;
liaoyuke563dc4a2017-03-17 18:36:293180 case OverscrollAction::REFRESH: {
sdefresnee65fd872016-12-19 13:38:133181 if ([[[_model currentTab] webController] loadPhase] ==
3182 web::PAGE_LOADING) {
sdefresne7d699dd2017-04-05 13:05:233183 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:133184 }
liaoyuke563dc4a2017-03-17 18:36:293185
3186 web::WebState* webState = [_model currentTab].webState;
3187 if (webState)
3188 // |check_for_repost| is true because the reload is explicitly initiated
3189 // by the user.
3190 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3191 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:133192 break;
liaoyuke563dc4a2017-03-17 18:36:293193 }
rohitrao922b7111c2017-01-03 14:31:053194 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133195 NOTREACHED();
3196 break;
3197 }
3198}
3199
3200- (BOOL)shouldAllowOverscrollActions {
3201 return YES;
3202}
3203
3204- (UIView*)headerView {
3205 return [_toolbarController view];
3206}
3207
3208- (UIView*)toolbarSnapshotView {
3209 return [[_toolbarController view] snapshotViewAfterScreenUpdates:NO];
3210}
3211
3212- (CGFloat)overscrollActionsControllerHeaderInset:
3213 (OverscrollActionsController*)controller {
3214 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3215 return [self headerHeight];
3216 else
3217 return 0;
3218}
3219
3220- (CGFloat)overscrollHeaderHeight {
3221 return [self headerHeight] + StatusBarHeight();
3222}
3223
3224#pragma mark - TabSnapshottingDelegate methods.
3225
3226- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3227 CGRect pageContentArea = _contentArea.bounds;
3228 if ([_model webUsageEnabled])
3229 pageContentArea = tab.view.bounds;
3230 CGFloat headerHeight = [self headerHeightForTab:tab];
3231 id nativeController = [self nativeControllerForTab:tab];
3232 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3233 headerHeight += [nativeController toolbarHeight];
3234 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3235 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3236}
3237
3238#pragma mark - NewTabPageObserver methods.
3239
3240- (void)selectedPanelDidChange {
3241 [self updateToolbar];
3242}
3243
3244#pragma mark - CRWNativeContentProvider methods
3245
3246- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3247 withError:(NSError*)error
3248 isPost:(BOOL)isPost {
3249 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173250 [[ErrorPageContent alloc] initWithLoader:self
3251 browserState:self.browserState
3252 url:url
3253 error:error
3254 isPost:isPost
3255 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133256 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3257 return errorPageContent;
3258}
3259
3260- (BOOL)hasControllerForURL:(const GURL&)url {
3261 std::string host(url.host());
olivierrobin5c861c22017-04-07 15:56:453262 if (host == kChromeUIOfflineHost) {
3263 // Only allow offline URL that are fully specified.
3264 return reading_list::IsOfflineURLValid(
3265 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3266 }
sdefresnee65fd872016-12-19 13:38:133267
Justin Cohen8679e852017-08-14 16:35:253268 if (host == kChromeUIBookmarksHost) {
3269 // Only allow bookmark URL on iPad.
3270 return IsIPadIdiom();
3271 }
3272
3273 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133274}
3275
olivierrobind43eecb2017-01-27 20:35:263276- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3277 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133278 DCHECK(url.SchemeIs(kChromeUIScheme));
3279
3280 id<CRWNativeContent> nativeController = nil;
3281 std::string url_host = url.host();
Justin Cohen49715952017-08-22 14:12:193282 if (url_host == kChromeUINewTabHost ||
3283 (IsIPadIdiom() && url_host == kChromeUIBookmarksHost)) {
sdefresnee65fd872016-12-19 13:38:133284 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173285 [[NewTabPageController alloc] initWithUrl:url
3286 loader:self
3287 focuser:_toolbarController
3288 ntpObserver:self
3289 browserState:_browserState
3290 colorCache:_dominantColorCache
Gauthier Ambard8160349c2017-09-06 14:43:243291 toolbarDelegate:self
justincohenbc913632017-04-18 14:41:453292 tabModel:_model
justincohen75011c32017-04-28 16:31:393293 parentViewController:self
edchin3365c7d2017-09-01 22:20:373294 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:133295 pageController.swipeRecognizerProvider = self.sideSwipeController;
3296
3297 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233298 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133299
3300 if (IsIPadIdiom()) {
3301 // New Tab Page can have multiple panels. Each panel is addressable
3302 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3303 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3304 // the Bookmark Manager, etc.
3305 // The utility functions NewTabPage::IdentifierFromFragment() and
3306 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3307 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3308 // without changing the URL since the URL may be chrome://bookmarks/#123.
3309 // If the URL is chrome://newtab/, pre-select the panel based on the
3310 // #fragment.
3311 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233312 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133313 : NewTabPage::IdentifierFromFragment(url.ref());
3314 }
3315 [pageController selectPanel:panelType];
3316 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453317 } else if (url_host == kChromeUIOfflineHost &&
3318 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133319 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173320 [[OfflinePageNativeContent alloc] initWithLoader:self
3321 browserState:_browserState
3322 webState:webState
3323 URL:url];
sdefresnee65fd872016-12-19 13:38:133324 [self setOverScrollActionControllerToStaticNativeContent:
3325 staticNativeController];
3326 nativeController = staticNativeController;
3327 } else if (url_host == kChromeUIExternalFileHost) {
3328 // Return an instance of the |ExternalFileController| only if the file is
3329 // still in the sandbox.
3330 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3331 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173332 nativeController =
3333 [[ExternalFileController alloc] initWithURL:url
3334 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133335 }
peterlaurens44615d02017-05-23 20:23:093336 } else if (url_host == kChromeUICrashHost) {
3337 // There is no native controller for kChromeUICrashHost, it is instead
3338 // handled as any other renderer crash by the SadTabTabHelper.
3339 // nativeController must be set to nil to prevent defaulting to a
3340 // PageNotAvailableController.
3341 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133342 } else {
3343 DCHECK(![self hasControllerForURL:url]);
3344 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173345 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133346 }
3347 // If a native controller is vended before its tab is added to the tab model,
3348 // use the temporary key and add it under the new tab's tabId in the
3349 // TabModelObserver callback. This happens:
3350 // - when there is no current tab (occurs when vending the NTP controller for
3351 // the first tab that is opened),
3352 // - when the current tab's url doesn't match |url| (occurs when a native
3353 // controller is opened in a new tab)
3354 // - when the current tab's url matches |url| and there is already a native
3355 // controller of the appropriate type vended to it (occurs when a native
3356 // controller is opened in a new tab from a tab with a matching URL, e.g.
3357 // opening an NTP when an NTP is already displayed in the current tab).
3358 // For normal page loads, history navigations, tab restorations, and crash
3359 // recoveries, the tab will already exist in the tab model and the tabId can
3360 // be used as the native controller key.
3361 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3362 // that native controllers vended here always correspond to the current tab.
3363 Tab* currentTab = [_model currentTab];
kkhorimotob110b262017-06-01 18:38:253364 if (!currentTab || currentTab.lastCommittedURL != url ||
Eugene But56efc322017-08-11 14:03:443365 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133366 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443367 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133368 }
sdefresnee65fd872016-12-19 13:38:133369 return nativeController;
3370}
3371
3372- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443373 id nativeController = tab.webController.nativeController;
3374 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133375}
3376
3377#pragma mark - DialogPresenterDelegate methods
3378
3379- (void)dialogPresenter:(DialogPresenter*)presenter
3380 willShowDialogForWebState:(web::WebState*)webState {
3381 for (Tab* iteratedTab in self.tabModel) {
3382 if ([iteratedTab webState] == webState) {
3383 self.tabModel.currentTab = iteratedTab;
3384 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3385 break;
3386 }
3387 }
3388}
3389
3390#pragma mark - Context menu methods
3391
3392- (void)searchByImageAtURL:(const GURL&)url
3393 referrer:(const web::Referrer)referrer {
3394 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273395 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113396 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173397 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3398 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113399 DCHECK(data);
3400 dispatch_async(dispatch_get_main_queue(), ^{
3401 [weakSelf searchByImageData:data atURL:image_source_url];
3402 });
3403 };
3404 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133405 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3406 web::PolicyForNavigation(url, referrer));
3407}
3408
3409- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3410 NSData* imageData = data;
3411 UIImage* image = [UIImage imageWithData:imageData];
3412 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3413 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3414 // kSearchByImageMaxImageHeight).
3415 if (image &&
3416 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3417 (image.size.width > kSearchByImageMaxImageWidth ||
3418 image.size.height > kSearchByImageMaxImageHeight)) {
3419 CGSize newImageSize =
3420 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3421 image = [image gtm_imageByResizingToSize:newImageSize
3422 preserveAspectRatio:YES
3423 trimToFit:NO];
3424 imageData = UIImageJPEGRepresentation(image, 1.0);
3425 }
3426
3427 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3428 std::string byteString(bytes, [imageData length]);
3429
3430 TemplateURLService* templateUrlService =
3431 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103432 const TemplateURL* defaultURL =
3433 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133434 DCHECK(!defaultURL->image_url().empty());
3435 DCHECK(defaultURL->image_url_ref().IsValid(
3436 templateUrlService->search_terms_data()));
3437 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3438 search_args.image_url = imageURL;
3439 search_args.image_thumbnail_content = byteString;
3440
3441 // Generate the URL and populate |post_content| with the content type and
3442 // HTTP body for the request.
3443 TemplateURLRef::PostContent post_content;
3444 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3445 search_args, templateUrlService->search_terms_data(), &post_content));
3446 [self addSelectedTabWithURL:result
3447 postData:&post_content
3448 transition:ui::PAGE_TRANSITION_TYPED];
3449}
3450
3451- (void)saveImageAtURL:(const GURL&)url
3452 referrer:(const web::Referrer&)referrer {
3453 DCHECK(url.is_valid());
3454
gambard9efce7a2017-02-09 18:53:173455 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3456 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113457 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133458
gambardbbf85c42017-06-29 11:15:343459 if ([data length] == 0) {
3460 [self displayPrivacyErrorAlertOnMainQueue:
3461 l10n_util::GetNSString(
3462 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3463 return;
3464 }
3465
gambard9efce7a2017-02-09 18:53:173466 base::FilePath::StringType extension;
3467
3468 bool extensionSuccess =
3469 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3470 if (!extensionSuccess || extension.length() == 0) {
3471 extension = "png";
3472 }
3473
3474 NSString* fileExtension =
3475 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3476 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113477 };
3478 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133479 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3480 web::PolicyForNavigation(url, referrer));
3481}
3482
gambard9efce7a2017-02-09 18:53:173483- (void)managePermissionAndSaveImage:(NSData*)data
3484 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133485 switch ([PHPhotoLibrary authorizationStatus]) {
3486 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173487 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133488 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3489 // Call -saveImage again to check if chrome needs to display an error or
3490 // saves the image.
3491 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173492 [self managePermissionAndSaveImage:data
3493 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133494 }];
3495 break;
stkhapuginf58b10d02017-04-10 13:36:173496 }
sdefresnee65fd872016-12-19 13:38:133497
3498 // The application doesn't have permission to access photo and the user
3499 // cannot grant it.
3500 case PHAuthorizationStatusRestricted:
3501 [self displayPrivacyErrorAlertOnMainQueue:
3502 l10n_util::GetNSString(
3503 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3504 break;
3505
3506 // The application doesn't have permission to access photo and the user
3507 // can grant it.
3508 case PHAuthorizationStatusDenied:
3509 [self displayImageErrorAlertWithSettingsOnMainQueue];
3510 break;
3511
3512 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243513 default:
3514 __weak BrowserViewController* weakSelf = self;
3515 [self saveImage:data
3516 withFileExtension:fileExtension
3517 completion:^(BOOL success, NSError* error) {
3518 [weakSelf finishSavingImageWithError:error];
3519 }];
sdefresnee65fd872016-12-19 13:38:133520 break;
sdefresnee65fd872016-12-19 13:38:133521 }
3522}
3523
Sylvain Defresnefd3ecf22017-07-12 18:47:243524- (void)saveImage:(NSData*)data
3525 withFileExtension:(NSString*)fileExtension
3526 completion:(void (^)(BOOL, NSError*))completion {
3527 base::PostTaskWithTraits(
3528 FROM_HERE,
3529 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3530 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3531 base::BindBlockArc(^{
3532 base::ThreadRestrictions::AssertIOAllowed();
sdefresnee65fd872016-12-19 13:38:133533
Sylvain Defresnefd3ecf22017-07-12 18:47:243534 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3535 stringByAppendingString:fileExtension];
3536 NSURL* fileURL = [NSURL
3537 fileURLWithPath:[NSTemporaryDirectory()
3538 stringByAppendingPathComponent:fileName]];
3539 NSError* error = nil;
3540 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3541 if (error) {
3542 if (completion)
3543 completion(NO, error);
3544 return;
3545 }
sdefresnee65fd872016-12-19 13:38:133546
Sylvain Defresnefd3ecf22017-07-12 18:47:243547 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3548 [PHAssetChangeRequest
3549 creationRequestForAssetFromImageAtFileURL:fileURL];
3550 }
3551 completionHandler:^(BOOL success, NSError* error) {
3552 base::PostTaskWithTraits(
3553 FROM_HERE,
3554 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3555 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3556 base::BindBlockArc(^{
3557 base::ThreadRestrictions::AssertIOAllowed();
3558 if (completion)
3559 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133560
Sylvain Defresnefd3ecf22017-07-12 18:47:243561 // Cleanup the temporary file.
3562 NSError* deleteFileError = nil;
3563 [[NSFileManager defaultManager]
3564 removeItemAtURL:fileURL
3565 error:&deleteFileError];
3566 }));
3567 }];
3568 }));
sdefresnee65fd872016-12-19 13:38:133569}
3570
3571- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3572 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3573 BOOL canGoToSetting =
3574 [[UIApplication sharedApplication] canOpenURL:settingURL];
3575 if (canGoToSetting) {
3576 dispatch_async(dispatch_get_main_queue(), ^{
3577 [self displayImageErrorAlertWithSettings:settingURL];
3578 });
3579 } else {
3580 [self displayPrivacyErrorAlertOnMainQueue:
3581 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3582 }
3583}
3584
3585- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3586 // Dismiss current alert.
3587 [_alertCoordinator stop];
3588
3589 NSString* title =
3590 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3591 NSString* message = l10n_util::GetNSString(
3592 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3593
stkhapuginc9eee7b2017-04-10 15:49:273594 _alertCoordinator =
3595 [[AlertCoordinator alloc] initWithBaseViewController:self
3596 title:title
3597 message:message];
sdefresnee65fd872016-12-19 13:38:133598
3599 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3600 action:nil
3601 style:UIAlertActionStyleCancel];
3602
3603 [_alertCoordinator
3604 addItemWithTitle:l10n_util::GetNSString(
3605 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3606 action:^{
3607 OpenUrlWithCompletionHandler(settingURL, nil);
3608 }
3609 style:UIAlertActionStyleDefault];
3610
3611 [_alertCoordinator start];
3612}
3613
3614- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3615 dispatch_async(dispatch_get_main_queue(), ^{
3616 NSString* title =
3617 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3618 [self showErrorAlertWithStringTitle:title message:errorContent];
3619 });
3620}
3621
3622// This callback is triggered when the image is effectively saved onto the photo
3623// album, or if the save failed for some reason.
3624- (void)finishSavingImageWithError:(NSError*)error {
3625 // Was there an error?
3626 if (error) {
3627 // Saving photo failed even though user has granted access to Photos.
3628 // Display the error information from the NSError object for user.
3629 NSString* errorMessage = [NSString
3630 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3631 [error domain], [error code]];
3632 // This code may be execute outside of the main thread. Make sure to display
3633 // the error on the main thread.
3634 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3635 } else {
3636 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3637 // the photo application. The current behaviour is to create the photo there
3638 // but not providing any link to it is suboptimal. That's what Safari is
3639 // doing, and what the PM want, but it doesn't make it right.
3640 }
3641}
3642
sdefresnee65fd872016-12-19 13:38:133643#pragma mark - Showing popups
3644
sdefresnee65fd872016-12-19 13:38:133645- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133646 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3647
3648 ReadingListModel* readingModel =
3649 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013650 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3651 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133652
pinkerton07e27842017-03-02 15:29:023653 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
gambarde31ad3ba2017-01-19 14:40:033654 [self showSnackbar:l10n_util::GetNSString(
3655 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133656}
3657
3658#pragma mark - Keyboard commands management
3659
3660- (BOOL)shouldRegisterKeyboardCommands {
3661 if ([self presentedViewController])
3662 return NO;
3663
3664 if (_voiceSearchController && _voiceSearchController->IsVisible())
3665 return NO;
3666
3667 // If there is no first responder, try to make the webview the first
3668 // responder.
3669 if (!GetFirstResponder()) {
stkhapuginc9eee7b2017-04-10 15:49:273670 [_model.currentTab.webController.webViewProxy becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133671 }
3672
3673 return YES;
3674}
3675
3676- (KeyCommandsProvider*)keyCommandsProvider {
3677 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273678 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133679 }
stkhapuginc9eee7b2017-04-10 15:49:273680 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133681}
3682
3683#pragma mark - KeyCommandsPlumbing
3684
3685- (BOOL)isOffTheRecord {
3686 return _isOffTheRecord;
3687}
3688
3689- (NSUInteger)tabsCount {
3690 return [_model count];
3691}
3692
lpromero47ea8862017-01-13 17:51:063693- (BOOL)canGoBack {
3694 return [_model currentTab].canGoBack;
3695}
3696
3697- (BOOL)canGoForward {
3698 return [_model currentTab].canGoForward;
3699}
3700
sdefresnee65fd872016-12-19 13:38:133701- (void)focusTabAtIndex:(NSUInteger)index {
3702 if ([_model count] > index) {
3703 [_model setCurrentTab:[_model tabAtIndex:index]];
3704 }
3705}
3706
3707- (void)focusNextTab {
3708 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3709 NSInteger modelCount = [_model count];
3710 if (currentTabIndex < modelCount - 1) {
3711 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3712 [_model setCurrentTab:nextTab];
3713 } else {
3714 [_model setCurrentTab:[_model tabAtIndex:0]];
3715 }
3716}
3717
3718- (void)focusPreviousTab {
3719 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3720 if (currentTabIndex > 0) {
3721 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3722 [_model setCurrentTab:previousTab];
3723 } else {
3724 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3725 [_model setCurrentTab:lastTab];
3726 }
3727}
3728
3729- (void)reopenClosedTab {
3730 sessions::TabRestoreService* const tabRestoreService =
3731 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3732 if (!tabRestoreService || tabRestoreService->entries().empty())
3733 return;
3734
3735 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3736 tabRestoreService->entries().front();
3737 // Only handle the TAB type.
3738 if (entry->type != sessions::TabRestoreService::TAB)
3739 return;
3740
Mark Cogandfcdea72017-07-18 13:47:383741 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133742 TabRestoreServiceDelegateImplIOS* const delegate =
3743 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3744 _browserState);
3745 tabRestoreService->RestoreEntryById(delegate, entry->id,
3746 WindowOpenDisposition::CURRENT_TAB);
3747}
3748
3749- (void)focusOmnibox {
3750 [_toolbarController focusOmnibox];
3751}
3752
3753#pragma mark - UIResponder
3754
3755- (NSArray*)keyCommands {
3756 if (![self shouldRegisterKeyboardCommands]) {
3757 return nil;
3758 }
3759 return [self.keyCommandsProvider
3760 keyCommandsForConsumer:self
Mark Cogan6c58ea92017-07-06 13:08:243761 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133762 editingText:![self isFirstResponder]];
3763}
3764
3765#pragma mark -
3766
3767// Induce an intentional crash in the browser process.
3768- (void)induceBrowserCrash {
3769 CHECK(false);
3770 // Call another function, so that the above CHECK can't be tail-call
3771 // optimized. This ensures that this method's name will show up in the stack
3772 // for easier identification.
3773 CHECK(true);
3774}
3775
3776- (void)loadURL:(const GURL&)url
3777 referrer:(const web::Referrer&)referrer
3778 transition:(ui::PageTransition)transition
3779 rendererInitiated:(BOOL)rendererInitiated {
3780 [[OmniboxGeolocationController sharedInstance]
3781 locationBarDidSubmitURL:url
3782 transition:transition
3783 browserState:_browserState];
3784
3785 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3786 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3787 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3788 }
3789
3790 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243791 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133792 // restart.
3793 if (url.host() == kChromeUIBrowserCrashHost) {
3794 [self induceBrowserCrash];
3795 // In debug the app can continue working even after the CHECK. Adding a
3796 // return avoids the crash url to be added to the history.
3797 return;
3798 }
3799
Rohit Rao44f204302017-08-10 14:49:543800 PrerenderService* prerenderService =
3801 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3802 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593803 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543804 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593805 DCHECK(newWebState);
3806
sdefresnee65fd872016-12-19 13:38:133807 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593808 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133809 DCHECK(oldTab);
3810 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593811
kkhorimotod804c5732017-03-15 23:44:523812 bool canPruneItems =
3813 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593814
kkhorimotod804c5732017-03-15 23:44:523815 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523816 [newTab navigationManager]->CopyStateFromAndPrune(
3817 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593818
3819 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3820 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133821
3822 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3823 // BrowserViewController to detect that a pre-rendered tab is switched in,
3824 // and show the prerendering animation.
3825 newTab.isPrerenderTab = NO;
3826
sdefresne2f7781c2017-03-02 19:12:463827 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133828 return;
3829 }
3830 }
3831
3832 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:543833 if (prerenderService) {
3834 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:133835 }
3836
sdefresnee65fd872016-12-19 13:38:133837 // Some URLs are not allowed while in incognito. If we are in incognito and
3838 // load a disallowed URL, instead create a new tab not in the incognito state.
3839 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3840 [self webPageOrderedOpen:url
3841 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133842 inIncognito:NO
3843 inBackground:NO
3844 appendTo:kCurrentTab];
3845 return;
3846 }
3847
mrefaata84d5a02017-06-08 17:13:293848 // If this is a reload initiated from the omnibox.
3849 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
3850 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
3851 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
3852 [[_model currentTab] navigationManager]->Reload(
3853 web::ReloadType::NORMAL, true /* check_for_repost */);
3854 return;
3855 }
3856
sdefresnee65fd872016-12-19 13:38:133857 web::NavigationManager::WebLoadParams params(urlToLoad);
3858 params.referrer = referrer;
3859 params.transition_type = transition;
3860 params.is_renderer_initiated = rendererInitiated;
3861 DCHECK([_model currentTab]);
sdefresne7d699dd2017-04-05 13:05:233862 [[_model currentTab] navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:133863}
3864
3865- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:543866 PrerenderService* prerenderService =
3867 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3868 if (prerenderService) {
3869 prerenderService->CancelPrerender();
3870 }
sdefresnee65fd872016-12-19 13:38:133871 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:183872 if ([self currentWebState])
3873 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:133874}
3875
3876- (web::WebState*)currentWebState {
3877 return [[_model currentTab] webState];
3878}
3879
sdefresnee65fd872016-12-19 13:38:133880// Load a new URL on a new page/tab.
3881- (void)webPageOrderedOpen:(const GURL&)URL
3882 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133883 inBackground:(BOOL)inBackground
3884 appendTo:(OpenPosition)appendTo {
3885 Tab* adjacentTab = nil;
3886 if (appendTo == kCurrentTab)
3887 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:353888 [_model insertTabWithURL:URL
3889 referrer:referrer
3890 transition:ui::PAGE_TRANSITION_LINK
3891 opener:adjacentTab
3892 openedByDOM:NO
3893 atIndex:TabModelConstants::kTabPositionAutomatically
3894 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:133895}
3896
3897- (void)webPageOrderedOpen:(const GURL&)url
3898 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133899 inIncognito:(BOOL)inIncognito
3900 inBackground:(BOOL)inBackground
3901 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003902 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:233903 // feature_engagement::Tracker based on |inIncognito|.
3904 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003905
sdefresnee65fd872016-12-19 13:38:133906 if (inIncognito == _isOffTheRecord) {
3907 [self webPageOrderedOpen:url
3908 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133909 inBackground:inBackground
3910 appendTo:appendTo];
3911 return;
3912 }
3913 // When sending an open command that switches modes, ensure the tab
3914 // ends up appended to the end of the model, not just next to what is
3915 // currently selected in the other mode. This is done with the |append|
3916 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:273917 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:133918 initWithURL:url
3919 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:133920 inIncognito:inIncognito
3921 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:273922 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:153923 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:133924}
3925
3926- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:053927 WebStateList* webStateList = [_model webStateList];
3928 webStateList->ReplaceWebStateAt(
3929 webStateList->active_index(),
3930 session_util::CreateWebStateWithNavigationEntries(
3931 [_model browserState], sessionTab->current_navigation_index,
3932 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:133933}
3934
3935- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:433936 DCHECK(javascript);
3937 javascript = [javascript stringByRemovingPercentEncoding];
3938 web::WebState* webState = [[_model currentTab] webState];
3939 if (webState) {
3940 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3941 }
sdefresnee65fd872016-12-19 13:38:133942}
3943
3944#pragma mark - WebToolbarDelegate methods
3945
3946- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
3947 if (_locationBarHasFocus)
3948 return; // TODO(crbug.com/244366): This should not be necessary.
3949 _locationBarHasFocus = YES;
3950 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:523951 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:133952 object:nil];
3953 [_sideSwipeController setEnabled:NO];
3954 if ([[_model currentTab].webController wantsKeyboardShield]) {
3955 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
3956 [_typingShield setAlpha:0.0];
3957 [_typingShield setHidden:NO];
3958 [UIView animateWithDuration:0.3
3959 animations:^{
3960 [_typingShield setAlpha:1.0];
3961 }];
3962 }
3963 [[OmniboxGeolocationController sharedInstance]
3964 locationBarDidBecomeFirstResponder:_browserState];
3965}
3966
3967- (IBAction)locationBarDidResignFirstResponder:(id)sender {
3968 if (!_locationBarHasFocus)
3969 return; // TODO(crbug.com/244366): This should not be necessary.
3970 _locationBarHasFocus = NO;
3971 [_sideSwipeController setEnabled:YES];
3972 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:523973 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:133974 object:nil];
3975 [UIView animateWithDuration:0.3
3976 animations:^{
3977 [_typingShield setAlpha:0.0];
3978 }
3979 completion:^(BOOL finished) {
3980 // This can happen if one quickly resigns the omnibox and then taps
3981 // on the omnibox again during this animation. If the animation is
3982 // interrupted and the toolbar controller is first responder, it's safe
3983 // to assume the |_typingShield| shouldn't be hidden here.
3984 if (!finished && [_toolbarController isOmniboxFirstResponder])
3985 return;
3986 [_typingShield setHidden:YES];
3987 }];
3988 [[OmniboxGeolocationController sharedInstance]
3989 locationBarDidResignFirstResponder:_browserState];
3990
3991 // If a load was cancelled by an omnibox edit, but nothing is loading when
3992 // editing ends (i.e., editing was cancelled), restart the cancelled load.
3993 if (_locationBarEditCancelledLoad) {
3994 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:293995
3996 web::WebState* webState = [_model currentTab].webState;
3997 if (!_toolbarModelIOS->IsLoading() && webState)
3998 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3999 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134000 }
4001}
4002
4003- (IBAction)locationBarBeganEdit:(id)sender {
4004 // On handsets, if a page is currently loading it should be stopped.
4005 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354006 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134007 _locationBarEditCancelledLoad = YES;
4008 }
4009}
4010
sdefresnee65fd872016-12-19 13:38:134011- (ToolbarModelIOS*)toolbarModelIOS {
4012 return _toolbarModelIOS.get();
4013}
4014
sdefresnee65fd872016-12-19 13:38:134015- (void)willUpdateToolbarSnapshot {
4016 [[_model currentTab].overscrollActionsController clear];
4017}
4018
4019- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4020 CGRect frame = [_contentArea frame];
4021 if (!fullScreen) {
4022 // Changing the origin here is unnecessary, it's set in page_animation_util.
4023 frame.size.height -= [self headerHeight];
4024 }
4025
4026 CGFloat shortAxis = frame.size.width;
4027 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524028 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134029 CGFloat aspectRatio = frame.size.height / frame.size.width;
4030 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4031 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4032 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4033 CGRect cardFrame = {frame.origin, cardSize};
4034
4035 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174036 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134037 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4038 : CardCloseButtonSide::LEADING;
4039 [_contentArea addSubview:card];
4040 return card;
4041}
4042
Mark Cogan6ebbde02017-07-07 12:50:134043#pragma mark - BrowserCommands
4044
4045- (void)goBack {
4046 [[_model currentTab] goBack];
4047}
4048
4049- (void)goForward {
4050 [[_model currentTab] goForward];
4051}
4052
Mark Coganb9aac6432017-07-07 13:26:354053- (void)stopLoading {
4054 [_model currentTab].webState->Stop();
4055}
4056
4057- (void)reload {
4058 web::WebState* webState = [_model currentTab].webState;
4059 if (webState) {
4060 // |check_for_repost| is true because the reload is explicitly initiated
4061 // by the user.
4062 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4063 true /* check_for_repost */);
4064 }
4065}
4066
Mark Cogan8e791022017-07-10 09:55:354067- (void)bookmarkPage {
4068 [self initializeBookmarkInteractionController];
4069 [_bookmarkInteractionController
4070 presentBookmarkForTab:[_model currentTab]
4071 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()
4072 inView:[_toolbarController bookmarkButtonView]
4073 originRect:[_toolbarController bookmarkButtonAnchorRect]];
4074}
4075
Mark Cogan6acee7f2017-07-11 09:01:404076- (void)showToolsMenu {
4077 DCHECK(_browserState);
4078 DCHECK(self.visible || self.dismissingModal);
4079
4080 // Record the time this menu was requested; to be stored in the configuration
4081 // object.
4082 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
4083
4084 // Dismiss the omnibox (if open).
4085 [_toolbarController cancelOmniboxEdit];
4086 // Dismiss the soft keyboard (if open).
4087 [[_model currentTab].webController dismissKeyboard];
4088 // Dismiss Find in Page focus.
4089 [self updateFindBar:NO shouldFocus:NO];
4090
4091 ToolsMenuConfiguration* configuration =
4092 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]];
4093 configuration.requestStartTime =
4094 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4095 if ([_model count] == 0)
4096 [configuration setNoOpenedTabs:YES];
4097
4098 if (_isOffTheRecord)
4099 [configuration setInIncognito:YES];
4100
4101 if (!_readingListMenuNotifier) {
4102 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4103 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4104 _browserState)];
4105 }
Cooper Knaake4f495cf2017-07-27 23:30:034106
4107 feature_engagement::Tracker* engagementTracker =
4108 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4109 if (engagementTracker->ShouldTriggerHelpUI(
4110 feature_engagement::kIPHBadgedReadingListFeature)) {
4111 [configuration setShowReadingListNewBadge:YES];
4112 [configuration setEngagementTracker:engagementTracker];
4113 }
Mark Cogan6acee7f2017-07-11 09:01:404114 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4115
4116 [configuration setUserAgentType:self.userAgentType];
4117
Helen Yang9175bd52017-08-12 00:28:404118 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4119 [configuration setHighlightNewIncognitoTabCell:YES];
4120 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4121 }
4122
4123 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4124 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4125 }
4126
Mark Cogan6acee7f2017-07-11 09:01:404127 [_toolbarController showToolsMenuPopupWithConfiguration:configuration];
4128
4129 ToolsPopupController* toolsPopupController =
4130 [_toolbarController toolsPopupController];
4131 if ([_model currentTab]) {
4132 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4133 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4134 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
Mark Cogan6acee7f2017-07-11 09:01:404135 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4136
4137 if (!IsIPadIdiom())
4138 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4139 }
4140}
4141
Mark Cogandfcdea72017-07-18 13:47:384142- (void)openNewTab:(OpenNewTabCommand*)command {
4143 if (self.isOffTheRecord != command.incognito) {
4144 // Not for this browser state, send it on its way.
4145 [self.dispatcher switchModesAndOpenNewTab:command];
4146 return;
4147 }
4148
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004149 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234150 // events to the feature_engagement::Tracker based on |command.userInitiated|
4151 // and |command.incognito|.
4152 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004153
Mark Cogandfcdea72017-07-18 13:47:384154 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4155 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494156 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4157 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384158 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494159 if (oldForegroundTabWasAddedCompletionBlock) {
4160 oldForegroundTabWasAddedCompletionBlock();
4161 }
Mark Cogandfcdea72017-07-18 13:47:384162 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4163 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4164 if (offTheRecord) {
4165 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4166 timeDelta);
4167 } else {
4168 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4169 }
4170 };
4171
4172 [self setLastTapPoint:command];
4173 DCHECK(self.visible || self.dismissingModal);
4174 Tab* currentTab = [_model currentTab];
4175 if (currentTab) {
4176 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4177 }
4178 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4179 transition:ui::PAGE_TRANSITION_TYPED];
4180}
4181
Mark Cogan123895002017-07-20 12:54:064182- (void)printTab {
4183 Tab* currentTab = [_model currentTab];
4184 // The UI should prevent users from printing non-printable pages. However, a
4185 // redirection to an un-printable page can happen before it is reflected in
4186 // the UI.
4187 if (![currentTab viewForPrinting]) {
4188 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
4189 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
4190 return;
4191 }
4192 DCHECK(_browserState);
4193 if (!_printController) {
4194 _printController = [[PrintController alloc]
4195 initWithContextGetter:_browserState->GetRequestContext()];
4196 }
4197 [_printController printView:[currentTab viewForPrinting]
4198 withTitle:[currentTab title]
4199 viewController:self];
4200}
4201
Mark Coganfa25b052017-07-20 17:31:034202- (void)addToReadingList:(ReadingListAddCommand*)command {
4203 [self addToReadingListURL:[command URL] title:[command title]];
4204}
4205
sczs3a8c8602017-08-01 20:14:084206- (void)showReadingList {
4207 _readingListCoordinator = [[ReadingListCoordinator alloc]
4208 initWithBaseViewController:self
4209 browserState:self.browserState
4210 loader:self];
4211
4212 [_readingListCoordinator start];
4213}
4214
Jean-François Geyelinedef9552017-08-07 09:56:564215- (void)preloadVoiceSearch {
4216 // Preload VoiceSearchController and views and view controllers needed
4217 // for voice search.
4218 [self ensureVoiceSearchControllerCreated];
4219 _voiceSearchController->PrepareToAppear();
4220}
4221
edchinc5720722017-08-14 22:06:314222#if !defined(NDEBUG)
4223- (void)viewSource {
4224 Tab* tab = [_model currentTab];
4225 DCHECK(tab);
4226 CRWWebController* webController = tab.webController;
4227 NSString* script = @"document.documentElement.outerHTML;";
4228 __weak Tab* weakTab = tab;
4229 __weak BrowserViewController* weakSelf = self;
4230 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4231 Tab* strongTab = weakTab;
4232 if (!strongTab)
4233 return;
4234 if (![result isKindOfClass:[NSString class]])
4235 result = @"Not an HTML page";
4236 std::string base64HTML;
4237 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4238 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
4239 web::Referrer referrer([strongTab lastCommittedURL],
4240 web::ReferrerPolicyDefault);
4241
4242 [[weakSelf tabModel]
4243 insertTabWithURL:URL
4244 referrer:referrer
4245 transition:ui::PAGE_TRANSITION_LINK
4246 opener:strongTab
4247 openedByDOM:YES
4248 atIndex:TabModelConstants::kTabPositionAutomatically
4249 inBackground:NO];
4250 };
4251 [webController executeJavaScript:script
4252 completionHandler:completionHandlerBlock];
4253}
4254#endif // !defined(NDEBUG)
4255
edchin2134c042017-08-18 13:57:354256// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4257- (void)showRateThisAppDialog {
4258 DCHECK(!_rateThisAppDialog);
4259
4260 // Store the current timestamp whenever this dialog is shown.
4261 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4262 base::Time::Now().ToInternalValue());
4263
4264 // Some versions of iOS7 do not support linking directly to the "Ratings and
4265 // Reviews" appstore page. For iOS7 fall back to an alternative URL that
4266 // links to the main appstore page for the Chrome app.
4267 NSURL* storeURL =
4268 [NSURL URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4269 @"MZStore.woa/wa/"
4270 @"viewContentsUserReviews?type=Purple+Software&id="
4271 @"535886823&pt=9008&ct=rating")];
4272
4273 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
4274 [self clearPresentedStateWithCompletion:nil];
4275
4276 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4277 [_rateThisAppDialog setAppStoreURL:storeURL];
4278 [_rateThisAppDialog setDelegate:self];
4279 [_rateThisAppDialog show];
4280}
4281
Gregory Chatzinoff3f40c1542017-08-30 07:50:044282- (void)showFindInPage {
4283 if (!self.canShowFindBar)
4284 return;
4285
4286 if (!_findBarController) {
4287 _findBarController =
4288 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4289 _findBarController.dispatcher = self.dispatcher;
4290 }
4291
4292 Tab* tab = [_model currentTab];
4293 DCHECK(tab);
4294 auto* helper = FindTabHelper::FromWebState(tab.webState);
4295 DCHECK(!helper->IsFindUIActive());
4296 helper->SetFindUIActive(true);
4297 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4298}
4299
4300- (void)closeFindInPage {
4301 __weak BrowserViewController* weakSelf = self;
4302 Tab* currentTab = [_model currentTab];
4303 if (currentTab) {
4304 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4305 [weakSelf updateFindBar:NO shouldFocus:NO];
4306 });
4307 }
4308}
4309
4310- (void)searchFindInPage {
4311 DCHECK([_model currentTab]);
4312 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4313 __weak BrowserViewController* weakSelf = self;
4314 helper->StartFinding(
4315 [_findBarController searchTerm], ^(FindInPageModel* model) {
4316 BrowserViewController* strongSelf = weakSelf;
4317 if (!strongSelf) {
4318 return;
4319 }
4320 [strongSelf->_findBarController updateResultsCount:model];
4321 });
4322
4323 if (!_isOffTheRecord)
4324 helper->PersistSearchTerm();
4325}
4326
4327- (void)findNextStringInPage {
4328 Tab* currentTab = [_model currentTab];
4329 DCHECK(currentTab);
4330 // TODO(crbug.com/603524): Reshow find bar if necessary.
4331 FindTabHelper::FromWebState(currentTab.webState)
4332 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4333 [_findBarController updateResultsCount:model];
4334 });
4335}
4336
4337- (void)findPreviousStringInPage {
4338 Tab* currentTab = [_model currentTab];
4339 DCHECK(currentTab);
4340 // TODO(crbug.com/603524): Reshow find bar if necessary.
4341 FindTabHelper::FromWebState(currentTab.webState)
4342 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4343 [_findBarController updateResultsCount:model];
4344 });
4345}
4346
edchinf84b2502017-08-31 21:30:454347- (void)showHelpPage {
4348 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4349 [self webPageOrderedOpen:helpUrl
4350 referrer:web::Referrer()
4351 inBackground:NO
4352 appendTo:kCurrentTab];
4353}
4354
edchinb59b5602017-09-01 15:00:204355- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104356 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204357 [self showAllBookmarks];
4358 } else {
4359 [self initializeBookmarkInteractionController];
4360 [_bookmarkInteractionController presentBookmarks];
4361 }
4362}
4363
edchin8ee0807d2017-09-01 23:52:474364- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104365 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474366 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4367 } else {
4368 if (!self.recentTabsCoordinator) {
4369 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4370 initWithBaseViewController:self];
4371 self.recentTabsCoordinator.loader = self;
4372 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4373 self.recentTabsCoordinator.browserState = _browserState;
4374 }
4375 [self.recentTabsCoordinator start];
4376 }
4377}
4378
Mark Cogan6de7e9a2017-09-06 12:57:214379- (void)requestDesktopSite {
4380 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4381}
4382
4383- (void)requestMobileSite {
4384 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4385}
4386
sdefresnee65fd872016-12-19 13:38:134387#pragma mark - Command Handling
4388
4389- (IBAction)chromeExecuteCommand:(id)sender {
4390 NSInteger command = [sender tag];
4391
4392 if (!_model || !_browserState)
4393 return;
4394
4395 switch (command) {
sdefresnee65fd872016-12-19 13:38:134396 case IDC_SHOW_MAIL_COMPOSER:
4397 [self showMailComposer:sender];
4398 break;
sdefresnee65fd872016-12-19 13:38:134399 default:
4400 // Unknown commands get sent up the responder chain.
4401 [super chromeExecuteCommand:sender];
4402 break;
4403 }
4404}
4405
4406- (void)closeCurrentTab {
4407 Tab* currentTab = [_model currentTab];
4408 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4409 if (tabIndex == NSNotFound)
4410 return;
4411
jif7fed8122017-02-08 13:15:254412 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4413 // iPad.
sdefresnee65fd872016-12-19 13:38:134414 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4415 exitingPage.image =
4416 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4417
4418 // Close the actual tab, and add its image as a subview.
4419 [_model closeTabAtIndex:tabIndex];
4420
4421 // Do not animate close in iPad.
4422 if (!IsIPadIdiom()) {
4423 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524424 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134425 exitingPage, 0, YES, IsPortrait(), ^{
4426 [exitingPage removeFromSuperview];
4427 });
4428 }
4429}
4430
sdefresnee65fd872016-12-19 13:38:134431- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion {
Rohit Rao01e0e002017-08-14 20:49:434432 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134433 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4434 [_bookmarkInteractionController dismissSnackbar];
4435 [_toolbarController cancelOmniboxEdit];
4436 [_dialogPresenter cancelAllDialogs];
Gregory Chatzinoffdf93d692017-09-09 01:32:274437 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:474438 [self.tabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134439 if (_voiceSearchController)
4440 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364441
4442 Tab* currentTab = [_model currentTab];
4443 [currentTab dismissModals];
4444
rohitrao005a6432017-03-16 20:52:424445 if (currentTab) {
4446 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4447 if (findHelper) {
4448 findHelper->StopFinding(^{
4449 [self updateFindBar:NO shouldFocus:NO];
4450 });
4451 }
4452 }
rohitraob2bf3cb2017-02-10 14:10:364453
sdefresnee65fd872016-12-19 13:38:134454 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134455 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274456 _printController = nil;
jif7fed8122017-02-08 13:15:254457 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134458 [_contextMenuCoordinator stop];
4459 [self dismissRateThisAppDialog];
4460
4461 if (self.presentedViewController) {
4462 // Dismisses any other modal controllers that may be present, e.g. Recent
4463 // Tabs.
4464 // Note that currently, some controllers like the bookmark ones were already
4465 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4466 // but are still reported as the presentedViewController. The result is that
4467 // this will call -dismissViewControllerAnimated:completion: a second time
4468 // on it. It is not per se an issue, as it is a no-op. The problem is that
4469 // in such a case, the completion block is not called.
4470 // To ensure the completion is called, nil is passed here, and the
4471 // completion is called below.
4472 [self dismissViewControllerAnimated:NO completion:nil];
4473 // Dismissed controllers will be so after a delay. Queue the completion
4474 // callback after that.
4475 if (completion) {
4476 dispatch_after(
4477 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4478 dispatch_get_main_queue(), ^{
4479 completion();
4480 });
4481 }
4482 } else if (completion) {
4483 // If no view controllers are presented, we should be ok with dispatching
4484 // the completion block directly.
4485 dispatch_async(dispatch_get_main_queue(), completion);
4486 }
4487}
4488
sdefresnee65fd872016-12-19 13:38:134489#pragma mark - Find Bar
4490
4491- (void)hideFindBarWithAnimation:(BOOL)animate {
4492 [_findBarController hideFindBarView:animate];
4493}
4494
4495- (void)showFindBarWithAnimation:(BOOL)animate
4496 selectText:(BOOL)selectText
4497 shouldFocus:(BOOL)shouldFocus {
4498 DCHECK(_findBarController);
4499 Tab* tab = [_model currentTab];
4500 DCHECK(tab);
4501 CRWWebController* webController = tab.webController;
4502
4503 CGRect referenceFrame = CGRectZero;
4504 if (IsIPadIdiom()) {
4505 referenceFrame = webController.visibleFrame;
4506 referenceFrame.origin.y -= kIPadFindBarOverlap;
4507 } else {
4508 referenceFrame = _contentArea.frame;
4509 }
4510
4511 CGRect omniboxFrame = [_toolbarController visibleOmniboxFrame];
4512 [_findBarController addFindBarView:animate
4513 intoView:self.view
4514 withFrame:referenceFrame
4515 alignWithFrame:omniboxFrame
4516 selectText:selectText];
4517 [self updateFindBar:YES shouldFocus:shouldFocus];
4518}
4519
sdefresnee65fd872016-12-19 13:38:134520- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324521 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4522 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4523 // in a crash.
4524 if (![_model currentTab]) {
4525 return;
4526 }
rohitrao005a6432017-03-16 20:52:424527 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4528 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134529 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424530 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134531 }
4532
4533 [self setFramesForHeaders:[self headerViews]
4534 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424535 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134536 initialUpdate:initialUpdate
4537 focusTextfield:shouldFocus];
4538 } else {
4539 [self hideFindBarWithAnimation:YES];
4540 }
4541}
4542
4543- (void)showAllBookmarks {
4544 DCHECK(self.visible || self.dismissingModal);
4545 GURL URL(kChromeUIBookmarksURL);
4546 Tab* tab = [_model currentTab];
4547 web::NavigationManager::WebLoadParams params(URL);
4548 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234549 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134550}
4551
Gauthier Ambardf520c022017-08-29 07:42:234552- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134553 DCHECK(self.visible || self.dismissingModal);
4554 GURL url(kChromeUINewTabURL);
4555 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4556 if (fragment != "") {
4557 GURL::Replacements replacement;
4558 replacement.SetRefStr(fragment);
4559 url = url.ReplaceComponents(replacement);
4560 }
4561 Tab* tab = [_model currentTab];
4562 web::NavigationManager::WebLoadParams params(url);
4563 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234564 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134565}
4566
sdefresnee65fd872016-12-19 13:38:134567- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274568 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134569 base::RecordAction(base::UserMetricsAction(
4570 "IOSRateThisAppDialogDismissedProgramatically"));
4571 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274572 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134573 }
4574}
4575
Jean-François Geyelin5d2e184c2017-07-28 19:48:004576- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4577 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134578 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124579 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134580 _startVoiceSearchAfterNewTabAnimation = YES;
4581 return;
4582 }
4583
4584 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4585 // dismiss the keyboard.
4586 [self closeFindInPage];
4587 [[_model currentTab].webController dismissKeyboard];
4588
4589 // Ensure that voice search objects are created.
4590 [self ensureVoiceSearchControllerCreated];
4591 [self ensureVoiceSearchBarCreated];
4592
4593 // Present voice search.
4594 [_voiceSearchBar prepareToPresentVoiceSearch];
4595 _voiceSearchController->StartRecognition(self, [_model currentTab]);
4596 [_toolbarController cancelOmniboxEdit];
4597}
4598
4599#pragma mark - ToolbarOwner
4600
4601- (ToolbarController*)relinquishedToolbarController {
4602 if (_isToolbarControllerRelinquished)
4603 return nil;
4604
4605 ToolbarController* relinquishedToolbarController = nil;
4606 if ([_toolbarController view].hidden) {
4607 Tab* currentTab = [_model currentTab];
kkhorimotob110b262017-06-01 18:38:254608 if (currentTab && UrlHasChromeScheme(currentTab.lastCommittedURL)) {
sdefresnee65fd872016-12-19 13:38:134609 // Use the native content controller's toolbar when the BVC's is hidden.
4610 id nativeController = [self nativeControllerForTab:currentTab];
4611 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4612 relinquishedToolbarController =
4613 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274614 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134615 }
4616 }
4617 } else {
stkhapuginc9eee7b2017-04-10 15:49:274618 relinquishedToolbarController = _toolbarController;
sdefresnee65fd872016-12-19 13:38:134619 }
4620 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4621 return relinquishedToolbarController;
4622}
4623
4624- (void)reparentToolbarController {
4625 if (_isToolbarControllerRelinquished) {
4626 if ([[_toolbarController view] isDescendantOfView:self.view]) {
4627 // A native content controller's toolbar has been relinquished.
4628 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274629 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134630 } else if ([_findBarController isFindInPageShown]) {
4631 [self.view insertSubview:[_toolbarController view]
4632 belowSubview:[_findBarController view]];
4633 } else {
4634 [self.view addSubview:[_toolbarController view]];
4635 }
sdefresnee65fd872016-12-19 13:38:134636 _isToolbarControllerRelinquished = NO;
4637 }
4638}
4639
4640#pragma mark - TabModelObserver methods
4641
4642// Observer method, tab inserted.
4643- (void)tabModel:(TabModel*)model
4644 didInsertTab:(Tab*)tab
4645 atIndex:(NSUInteger)modelIndex
4646 inForeground:(BOOL)fg {
4647 DCHECK(tab);
4648 [self installDelegatesForTab:tab];
4649
4650 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194651 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134652 }
4653}
4654
4655// Observer method, active tab changed.
4656- (void)tabModel:(TabModel*)model
4657 didChangeActiveTab:(Tab*)newTab
4658 previousTab:(Tab*)previousTab
4659 atIndex:(NSUInteger)index {
4660 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4661 // Currently this observer method is always called with a non-nil |newTab|,
4662 // but that may change in the future. Remove this DCHECK when it does.
4663 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274664 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304665 DCHECK(newTab.webState);
4666 infobars::InfoBarManager* infoBarManager =
4667 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134668 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4669 }
4670 [self updateVoiceSearchBarVisibilityAnimated:NO];
4671
Mohamad Ahmadi7d09ec32017-07-11 22:32:194672 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134673
sczs6ae47ad2017-09-06 17:26:534674 // Update the Sad Tab coordinator webstate so it matches the current tab
4675 // webstate.
4676 _sadTabCoordinator.webState = newTab.webState;
4677
sdefresnee65fd872016-12-19 13:38:134678 [self tabSelected:newTab];
4679 DCHECK_EQ(newTab, [model currentTab]);
4680 [self installDelegatesForTab:newTab];
4681}
4682
4683// Observer method, tab changed.
4684- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4685 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4686 if (tab == [_model currentTab]) {
4687 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134688 }
4689}
4690
sdefresne49cf2862017-03-15 13:46:144691// Observer method, tab replaced.
4692- (void)tabModel:(TabModel*)model
4693 didReplaceTab:(Tab*)oldTab
4694 withTab:(Tab*)newTab
4695 atIndex:(NSUInteger)index {
4696 [self uninstallDelegatesForTab:oldTab];
4697 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264698
michaeldo79909fb2017-05-09 23:42:504699 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304700 infobars::InfoBarManager* infoBarManager = nullptr;
4701 if (newTab) {
4702 DCHECK(newTab.webState);
4703 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4704 }
michaeldo79909fb2017-05-09 23:42:504705 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4706 }
4707
kkhorimotofa0844cc2017-03-20 17:01:264708 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4709 if (self.active && model.currentTab == newTab)
4710 [self displayTab:newTab isNewSelection:NO];
sdefresne49cf2862017-03-15 13:46:144711}
4712
sdefresnee65fd872016-12-19 13:38:134713// A tab has been removed, remove its views from display if necessary.
4714- (void)tabModel:(TabModel*)model
4715 didRemoveTab:(Tab*)tab
4716 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144717 [self uninstallDelegatesForTab:tab];
4718
kkhorimoto496fdd72017-06-12 19:56:314719 // Cancel dialogs for |tab|'s WebState.
4720 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4721
sdefresnee65fd872016-12-19 13:38:134722 // Ignore changes while the tab stack view is visible (or while suspended).
4723 // The display will be refreshed when this view becomes active again.
4724 if (!self.visible || !model.webUsageEnabled)
4725 return;
4726
4727 // Remove the find bar for now.
4728 [self hideFindBarWithAnimation:NO];
4729}
4730
4731- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4732 if (tab == [model currentTab]) {
4733 [_contentArea displayContentView:nil];
4734 [_toolbarController selectedTabChanged];
4735 }
4736
Mohamad Ahmadi7d09ec32017-07-11 22:32:194737 [_paymentRequestManager stopTrackingWebState:tab.webState];
4738
sdefresnee65fd872016-12-19 13:38:134739 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4740 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194741 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134742 }
4743}
4744
4745// Called when the number of tabs changes. Update the toolbar accordingly.
4746- (void)tabModelDidChangeTabCount:(TabModel*)model {
4747 DCHECK(model == _model);
sdefresnee65fd872016-12-19 13:38:134748 [_toolbarController setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134749}
4750
4751#pragma mark - Upgrade Detection
4752
4753- (void)showUpgrade:(UpgradeCenter*)center {
4754 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274755 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134756 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304757 DCHECK(tab.webState);
4758 infobars::InfoBarManager* infoBarManager =
4759 InfoBarManagerImpl::FromWebState(tab.webState);
4760 DCHECK(infoBarManager);
4761 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134762 }
4763}
4764
sdefresnee65fd872016-12-19 13:38:134765
4766#pragma mark - InfoBarControllerDelegate
4767
4768- (void)infoBarContainerStateChanged:(bool)isAnimating {
4769 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4770 DCHECK(infoBarContainerView);
4771 CGRect containerFrame = infoBarContainerView.frame;
4772 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4773 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4774 containerFrame.size.height = height;
4775 BOOL isViewVisible = self.visible;
4776 [UIView animateWithDuration:0.1
4777 animations:^{
4778 [infoBarContainerView setFrame:containerFrame];
4779 }
4780 completion:^(BOOL finished) {
4781 if (!isViewVisible)
4782 return;
4783 UIAccessibilityPostNotification(
4784 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4785 }];
4786}
4787
4788- (BOOL)shouldAutorotate {
4789 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4790 // Don't rotate if a voice search is being presented or dismissed. Once the
4791 // transition animations finish, only the Voice Search UIViewController's
4792 // |-shouldAutorotate| will be called.
4793 return NO;
4794 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4795 // Don't auto rotate if side swipe controller view says not to.
4796 return NO;
4797 } else {
4798 return [super shouldAutorotate];
4799 }
4800}
4801
4802// Always return yes, as this tap should work with various recognizers,
4803// including UITextTapRecognizer, UILongPressGestureRecognizer,
4804// UIScrollViewPanGestureRecognizer and others.
4805- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4806 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4807 (UIGestureRecognizer*)otherGestureRecognizer {
4808 return YES;
4809}
4810
4811// Tap gestures should only be recognized within |_contentArea|.
4812- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4813 CGPoint location = [gesture locationInView:self.view];
4814
4815 // Only allow touches on descendant views of |_contentArea|.
4816 UIView* hitView = [self.view hitTest:location withEvent:nil];
4817 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4818}
4819
4820#pragma mark - SideSwipeController Delegate Methods
4821
4822- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4823 DCHECK(!IsIPadIdiom());
4824 // Update frame incase orientation changed while |_contentArea| was out of
4825 // the view hierarchy.
4826 [_contentArea setFrame:[sideSwipeView frame]];
4827
4828 [self.view insertSubview:_contentArea atIndex:0];
4829 [self updateVoiceSearchBarVisibilityAnimated:NO];
4830 [self updateToolbar];
4831
4832 // Reset horizontal stack view.
4833 [sideSwipeView removeFromSuperview];
4834 [_sideSwipeController setInSwipe:NO];
4835 [_infoBarContainer->view() setHidden:NO];
4836}
4837
4838- (UIView*)contentView {
4839 return _contentArea;
4840}
4841
4842- (TabStripController*)tabStripController {
4843 return _tabStripController;
4844}
4845
4846- (WebToolbarController*)toolbarController {
4847 return _toolbarController;
4848}
4849
4850- (BOOL)preventSideSwipe {
4851 if ([_toolbarController toolsPopupController])
4852 return YES;
4853
4854 if (_voiceSearchController && _voiceSearchController->IsVisible())
4855 return YES;
4856
sdefresnee65fd872016-12-19 13:38:134857 if (!self.active)
4858 return YES;
4859
4860 return NO;
4861}
4862
4863- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
4864 if (visible) {
4865 [self updateVoiceSearchBarVisibilityAnimated:NO];
4866 [self updateToolbar];
4867 [_infoBarContainer->view() setHidden:NO];
4868 } else {
4869 // Hide UI accessories such as find bar and first visit overlays
4870 // for welcome page.
4871 [self hideFindBarWithAnimation:NO];
4872 [_infoBarContainer->view() setHidden:YES];
4873 [_voiceSearchBar setHidden:YES];
4874 }
4875}
4876
4877- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
4878 BOOL seenToolbar = NO;
4879 BOOL seenInfoBarContainer = NO;
4880 BOOL seenContentArea = NO;
4881 for (UIView* view in views.subviews) {
4882 if (view == [_toolbarController view])
4883 seenToolbar = YES;
4884 else if (view == _infoBarContainer->view())
4885 seenInfoBarContainer = YES;
4886 else if (view == _contentArea)
4887 seenContentArea = YES;
4888 if ((seenToolbar && !seenInfoBarContainer) ||
4889 (seenInfoBarContainer && !seenContentArea))
4890 return NO;
4891 }
4892 return YES;
4893}
4894
4895#pragma mark - PreloadControllerDelegate methods
4896
rohitraoeeb5293b2017-06-15 14:40:024897- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:444898 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:134899}
4900
rohitraoeeb5293b2017-06-15 14:40:024901- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
4902 return [self hasControllerForURL:url];
4903}
4904
sdefresnee65fd872016-12-19 13:38:134905#pragma mark - BookmarkBridgeMethods
4906
4907// If an added or removed bookmark is the same as the current url, update the
4908// toolbar so the star highlight is kept in sync.
4909- (void)bookmarkNodeModified:(const BookmarkNode*)node {
kkhorimotob110b262017-06-01 18:38:254910 if ([_model currentTab] &&
4911 node->url() == [_model currentTab].lastCommittedURL) {
sdefresnee65fd872016-12-19 13:38:134912 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:254913 }
sdefresnee65fd872016-12-19 13:38:134914}
4915
4916// If all bookmarks are removed, update the toolbar so the star highlight is
4917// kept in sync.
4918- (void)allBookmarksRemoved {
4919 [self updateToolbar];
4920}
4921
sdefresnee65fd872016-12-19 13:38:134922- (void)showErrorAlertWithStringTitle:(NSString*)title
4923 message:(NSString*)message {
4924 // Dismiss current alert.
4925 [_alertCoordinator stop];
4926
stkhapuginc9eee7b2017-04-10 15:49:274927 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
4928 message:message
4929 viewController:self];
sdefresnee65fd872016-12-19 13:38:134930 [_alertCoordinator start];
4931}
4932
4933- (void)showSnackbar:(NSString*)message {
4934 [_dependencyFactory showSnackbarWithMessage:message];
4935}
4936
4937#pragma mark - Show Mail Composer methods
4938
4939- (void)showMailComposer:(id)sender {
4940 ShowMailComposerCommand* command = (ShowMailComposerCommand*)sender;
4941 if (![MFMailComposeViewController canSendMail]) {
4942 NSString* alertTitle =
4943 l10n_util::GetNSString([command emailNotConfiguredAlertTitleId]);
4944 NSString* alertMessage =
4945 l10n_util::GetNSString([command emailNotConfiguredAlertMessageId]);
4946 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
4947 return;
4948 }
stkhapuginc9eee7b2017-04-10 15:49:274949 MFMailComposeViewController* mailViewController =
4950 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:134951 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
4952 [mailViewController setToRecipients:[command toRecipients]];
4953 [mailViewController setSubject:[command subject]];
4954 [mailViewController setMessageBody:[command body] isHTML:NO];
4955
4956 const base::FilePath& textFile = [command textFileToAttach];
4957 if (!textFile.empty()) {
4958 NSString* filename = base::SysUTF8ToNSString(textFile.value());
4959 NSData* data = [NSData dataWithContentsOfFile:filename];
4960 if (data) {
4961 NSString* displayName =
4962 base::SysUTF8ToNSString(textFile.BaseName().value());
4963 [mailViewController addAttachmentData:data
4964 mimeType:@"text/plain"
4965 fileName:displayName];
4966 }
4967 }
4968
4969 [mailViewController setMailComposeDelegate:self];
4970 [self presentViewController:mailViewController animated:YES completion:nil];
4971}
4972
4973#pragma mark - MFMailComposeViewControllerDelegate methods
4974
4975- (void)mailComposeController:(MFMailComposeViewController*)controller
4976 didFinishWithResult:(MFMailComposeResult)result
4977 error:(NSError*)error {
4978 [self dismissViewControllerAnimated:YES completion:nil];
4979}
4980
4981#pragma mark - StoreKitLauncher methods
4982
4983- (void)productViewControllerDidFinish:
4984 (SKStoreProductViewController*)viewController {
4985 [self dismissViewControllerAnimated:YES completion:nil];
4986}
4987
4988- (void)openAppStore:(NSString*)appId {
4989 if (![appId length])
4990 return;
4991 NSDictionary* product =
4992 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:274993 SKStoreProductViewController* storeViewController =
4994 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:134995 [storeViewController setDelegate:self];
4996 [storeViewController loadProductWithParameters:product completionBlock:nil];
4997 [self presentViewController:storeViewController animated:YES completion:nil];
4998}
4999
5000#pragma mark - TabDialogDelegate methods
5001
sdefresnee65fd872016-12-19 13:38:135002- (void)cancelDialogForTab:(Tab*)tab {
5003 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5004}
5005
5006#pragma mark - FKFeedbackPromptDelegate methods
5007
5008- (void)userTappedRateApp:(UIView*)view {
5009 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275010 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135011}
5012
5013- (void)userTappedSendFeedback:(UIView*)view {
5014 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275015 _rateThisAppDialog = nil;
sczsb8f81c32017-08-29 15:45:495016 [self.dispatcher showReportAnIssue];
sdefresnee65fd872016-12-19 13:38:135017}
5018
5019- (void)userTappedDismiss:(UIView*)view {
5020 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275021 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135022}
5023
Gauthier Ambard8160349c2017-09-06 14:43:245024#pragma mark - IncognitoViewControllerDelegate
5025
5026- (void)setToolbarBackgroundAlpha:(CGFloat)alpha {
5027 [_toolbarController setBackgroundAlpha:alpha];
5028}
5029
sdefresnee65fd872016-12-19 13:38:135030#pragma mark - VoiceSearchBarDelegate
5031
5032- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275033 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135034 [self ensureVoiceSearchControllerCreated];
5035 return _voiceSearchController->IsTextToSpeechEnabled() &&
5036 _voiceSearchController->IsTextToSpeechSupported();
5037}
5038
5039- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275040 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135041 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5042}
5043
5044#pragma mark - VoiceSearchPresenter
5045
5046- (UIView*)voiceSearchButton {
5047 return _voiceSearchButton;
5048}
5049
5050- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5051 return [self currentLogoAnimationControllerOwner];
5052}
5053
Rohit Rao01e0e002017-08-14 20:49:435054#pragma mark - ActivityService Providers
5055
5056- (void)presentActivityServiceViewController:(UIViewController*)controller {
5057 [self presentViewController:controller animated:YES completion:nil];
5058}
5059
5060- (void)activityServiceDidEndPresenting {
5061 self.presenting = NO;
5062 [self.dialogPresenter tryToPresent];
5063}
5064
Rohit Raocda0a992017-08-16 15:37:115065#pragma mark - QRScanner Requirements
5066
5067- (void)presentQRScannerViewController:(UIViewController*)controller {
5068 [self presentViewController:controller animated:YES completion:nil];
5069}
5070
5071- (void)dismissQRScannerViewController:(UIViewController*)controller
5072 completion:(void (^)(void))completion {
5073 DCHECK_EQ(controller, self.presentedViewController);
5074 [self dismissViewControllerAnimated:YES completion:completion];
5075}
5076
sczsdd860eba2017-08-10 01:55:385077#pragma mark - TabHistoryPresenter
5078
sczs0a726d22017-08-21 22:40:135079- (UIView*)viewForTabHistoryPresentation {
5080 return self.view;
5081}
5082
sczsdd860eba2017-08-10 01:55:385083- (void)prepareForTabHistoryPresentation {
5084 DCHECK(self.visible || self.dismissingModal);
5085 [[self.tabModel currentTab].webController dismissKeyboard];
5086 [_toolbarController cancelOmniboxEdit];
5087}
5088
Mike Doughertya1ec26402017-08-23 19:46:315089#pragma mark - CaptivePortalDetectorTabHelperDelegate
5090
5091- (void)captivePortalBlockingPage:(IOSCaptivePortalBlockingPage*)blockingPage
5092 connectWithLandingURL:(GURL)landingURL {
5093 _captivePortalLoginCoordinator = [[CaptivePortalLoginCoordinator alloc]
5094 initWithBaseViewController:self
5095 landingURL:landingURL];
5096 [_captivePortalLoginCoordinator start];
5097}
5098
Gregory Chatzinoffdf93d692017-09-09 01:32:275099#pragma mark - PageInfoPresentation
5100
5101- (UIView*)viewForPageInfoPresentation {
5102 return self.view;
5103}
5104
5105- (void)prepareForPageInfoPresentation {
5106 // Dismiss the omnibox (if open).
5107 [_toolbarController cancelOmniboxEdit];
5108}
5109
sdefresnee65fd872016-12-19 13:38:135110@end