blob: 158c20ff3a9253b3c0c6903f53790de776524132 [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#import "ios/chrome/browser/ui/commands/generic_chrome_command.h"
118#include "ios/chrome/browser/ui/commands/ios_command_ids.h"
Mark Cogandfcdea72017-07-18 13:47:38119#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13120#import "ios/chrome/browser/ui/commands/open_url_command.h"
121#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
122#import "ios/chrome/browser/ui/commands/show_mail_composer_command.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00123#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23124#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13125#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13126#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
127#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
128#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
129#import "ios/chrome/browser/ui/external_file_controller.h"
130#import "ios/chrome/browser/ui/external_file_remover.h"
sdefresnee65fd872016-12-19 13:38:13131#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
132#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
133#import "ios/chrome/browser/ui/fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38134#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13135#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13136#import "ios/chrome/browser/ui/key_commands_provider.h"
sdefresnee65fd872016-12-19 13:38:13137#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42138#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13139#include "ios/chrome/browser/ui/omnibox/page_info_model.h"
140#import "ios/chrome/browser/ui/omnibox/page_info_view_controller.h"
141#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
142#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37143#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13144#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11145#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
146#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13147#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03148#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13149#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13150#include "ios/chrome/browser/ui/rtl_geometry.h"
151#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
152#import "ios/chrome/browser/ui/stack_view/card_view.h"
153#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
154#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
155#import "ios/chrome/browser/ui/sync/sync_util.h"
156#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
157#import "ios/chrome/browser/ui/tabs/tab_strip_controller.h"
158#import "ios/chrome/browser/ui/toolbar/toolbar_controller.h"
159#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
160#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
sczsbbad1632017-07-29 03:48:00161#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13162#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
163#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
164#include "ios/chrome/browser/ui/ui_util.h"
165#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28166#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13167#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
168#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51169#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13170#import "ios/chrome/browser/web/error_page_content.h"
171#import "ios/chrome/browser/web/passkit_dialog_provider.h"
eugenebutcae3d9e62017-01-27 20:01:05172#import "ios/chrome/browser/web/repost_form_tab_helper.h"
sdefresne62a00bb2017-04-10 15:36:05173#import "ios/chrome/browser/web_state_list/web_state_list.h"
174#import "ios/chrome/browser/web_state_list/web_state_opener.h"
sdefresnee65fd872016-12-19 13:38:13175#include "ios/chrome/grit/ios_chromium_strings.h"
176#include "ios/chrome/grit/ios_strings.h"
177#import "ios/net/request_tracker.h"
178#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
179#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
180#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
181#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
182#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
183#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
184#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
185#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
sdefresnee65fd872016-12-19 13:38:13186#include "ios/web/public/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:13187#include "ios/web/public/navigation_item.h"
188#import "ios/web/public/navigation_manager.h"
189#include "ios/web/public/referrer_util.h"
190#include "ios/web/public/ssl_status.h"
191#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39192#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13193#include "ios/web/public/web_client.h"
194#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13195#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29196#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
sdefresnee65fd872016-12-19 13:38:13197#include "ios/web/public/web_state/web_state.h"
198#import "ios/web/public/web_state/web_state_delegate_bridge.h"
199#include "ios/web/public/web_thread.h"
200#import "ios/web/web_state/ui/crw_web_controller.h"
201#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17202#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13203#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
204#include "net/ssl/ssl_info.h"
205#include "net/url_request/url_request_context_getter.h"
206#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
207#include "ui/base/l10n/l10n_util.h"
208#include "ui/base/l10n/l10n_util_mac.h"
209#include "ui/base/page_transition_types.h"
210#include "url/gurl.h"
211
stkhapuginf58b10d02017-04-10 13:36:17212#if !defined(__has_feature) || !__has_feature(objc_arc)
213#error "This file requires ARC support."
214#endif
215
sdefresnee65fd872016-12-19 13:38:13216using base::UserMetricsAction;
217using bookmarks::BookmarkNode;
218
219class BrowserBookmarkModelBridge;
220class InfoBarContainerDelegateIOS;
221
222namespace ios_internal {
223NSString* const kPageInfoWillShowNotification =
224 @"kPageInfoWillShowNotification";
225NSString* const kPageInfoWillHideNotification =
226 @"kPageInfoWillHideNotification";
227NSString* const kLocationBarBecomesFirstResponderNotification =
228 @"kLocationBarBecomesFirstResponderNotification";
229NSString* const kLocationBarResignsFirstResponderNotification =
230 @"kLocationBarResignsFirstResponderNotification";
231} // namespace ios_internal
232
233namespace {
234
235typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
236 // Note: these values must match the ContextMenuOption enum in histograms.xml.
237 ACTION_OPEN_IN_NEW_TAB = 0,
238 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
239 ACTION_COPY_LINK_ADDRESS = 2,
240 ACTION_SAVE_IMAGE = 6,
241 ACTION_OPEN_IMAGE = 7,
242 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
243 ACTION_SEARCH_BY_IMAGE = 11,
244 ACTION_OPEN_JAVASCRIPT = 21,
245 ACTION_READ_LATER = 22,
246 NUM_ACTIONS = 23,
247};
248
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28249void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13250 if (is_image) {
251 if (is_link) {
252 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
253 NUM_ACTIONS);
254 } else {
255 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
256 NUM_ACTIONS);
257 }
258 } else {
259 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
260 NUM_ACTIONS);
261 }
262}
263
sdefresnee65fd872016-12-19 13:38:13264const CGFloat kVoiceSearchBarHeight = 59.0;
265
266// Dimensions to use when downsizing an image for search-by-image.
267const CGFloat kSearchByImageMaxImageArea = 90000.0;
268const CGFloat kSearchByImageMaxImageWidth = 600.0;
269const CGFloat kSearchByImageMaxImageHeight = 400.0;
270
271// The delay, in seconds, after startup before cleaning up the files received
272// from other applications that are not bookmarked nor referenced by an open or
273// recently closed tab.
274const int kExternalFilesCleanupDelaySeconds = 60;
275
276enum HeaderBehaviour {
277 // The header moves completely out of the screen.
278 Hideable = 0,
279 // This header stays on screen and doesn't overlap with the content.
280 Visible,
281 // This header stay on screen and covers part of the content.
282 Overlap
283};
284
sdefresnee65fd872016-12-19 13:38:13285const CGFloat kIPadFindBarOverlap = 11;
286
287bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24288 // Most URLs are allowed in incognito; the following is an exception.
289 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13290}
291
rohitrao005a6432017-03-16 20:52:42292} // namespace
sdefresnee65fd872016-12-19 13:38:13293
stkhapugin952ecef2017-04-11 12:11:45294#pragma mark - HeaderDefinition helper
295
296@interface HeaderDefinition : NSObject
297
298// The header view.
299@property(nonatomic, strong) UIView* view;
300// How to place the view, and its behaviour when the headers move.
301@property(nonatomic, assign) HeaderBehaviour behaviour;
302// Reduces the height of a header to adjust for shadows.
303@property(nonatomic, assign) CGFloat heightAdjustement;
304// Nudges that particular header up by this number of points.
305@property(nonatomic, assign) CGFloat inset;
306
307- (instancetype)initWithView:(UIView*)view
308 headerBehaviour:(HeaderBehaviour)behaviour
309 heightAdjustment:(CGFloat)heightAdjustment
310 inset:(CGFloat)inset;
311
312+ (instancetype)definitionWithView:(UIView*)view
313 headerBehaviour:(HeaderBehaviour)behaviour
314 heightAdjustment:(CGFloat)heightAdjustment
315 inset:(CGFloat)inset;
316
317@end
318
319@implementation HeaderDefinition
320@synthesize view = _view;
321@synthesize behaviour = _behaviour;
322@synthesize heightAdjustement = _heightAdjustement;
323@synthesize inset = _inset;
324
325+ (instancetype)definitionWithView:(UIView*)view
326 headerBehaviour:(HeaderBehaviour)behaviour
327 heightAdjustment:(CGFloat)heightAdjustment
328 inset:(CGFloat)inset {
329 return [[self alloc] initWithView:view
330 headerBehaviour:behaviour
331 heightAdjustment:heightAdjustment
332 inset:inset];
333}
334
335- (instancetype)initWithView:(UIView*)view
336 headerBehaviour:(HeaderBehaviour)behaviour
337 heightAdjustment:(CGFloat)heightAdjustment
338 inset:(CGFloat)inset {
339 self = [super init];
340 if (self) {
341 _view = view;
342 _behaviour = behaviour;
343 _heightAdjustement = heightAdjustment;
344 _inset = inset;
345 }
346 return self;
347}
348
349@end
350
351#pragma mark - BVC
352
Rohit Rao01e0e002017-08-14 20:49:43353@interface BrowserViewController ()<ActivityServicePresentation,
354 ActivityServiceSnackbar,
355 AppRatingPromptDelegate,
sdefresnee65fd872016-12-19 13:38:13356 CRWNativeContentProvider,
357 CRWWebStateDelegate,
358 DialogPresenterDelegate,
359 FullScreenControllerDelegate,
Mike Doughertya1ec26402017-08-23 19:46:31360 IOSCaptivePortalBlockingPageDelegate,
sdefresnee65fd872016-12-19 13:38:13361 KeyCommandsPlumbing,
362 MFMailComposeViewControllerDelegate,
363 NewTabPageControllerObserver,
364 OverscrollActionsControllerDelegate,
365 PassKitDialogProvider,
366 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11367 QRScannerPresenting,
sdefresnee65fd872016-12-19 13:38:13368 SKStoreProductViewControllerDelegate,
369 SnapshotOverlayProvider,
370 StoreKitLauncher,
371 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19372 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38373 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13374 TabModelObserver,
375 TabSnapshottingDelegate,
376 UIGestureRecognizerDelegate,
377 UpgradeCenterClientProtocol,
378 VoiceSearchBarDelegate,
379 VoiceSearchBarOwner> {
380 // The dependency factory passed on initialization. Used to vend objects used
381 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27382 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13383
384 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27385 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13386
387 // Facade objects used by |_toolbarController|.
388 // Must outlive |_toolbarController|.
389 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
390 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
391
sdefresnee65fd872016-12-19 13:38:13392 // The WebToolbarController used to display the omnibox.
stkhapuginc9eee7b2017-04-10 15:49:27393 WebToolbarController* _toolbarController;
sdefresnee65fd872016-12-19 13:38:13394
395 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27396 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13397
Mike Doughertya1ec26402017-08-23 19:46:31398 // Handles displaying the captive portal login page.
399 CaptivePortalLoginCoordinator* _captivePortalLoginCoordinator;
400
sdefresnee65fd872016-12-19 13:38:13401 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27402 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13403
404 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27405 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13406
407 // Handles presentation of JavaScript dialogs.
408 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
409
justincohen75011c32017-04-28 16:31:39410 // Handles command dispatching.
411 CommandDispatcher* _dispatcher;
412
sdefresnee65fd872016-12-19 13:38:13413 // Keyboard commands provider. It offloads most of the keyboard commands
414 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27415 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13416
417 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
418 // to |-reparentToolbarController| will reset it to NO.
419 BOOL _isToolbarControllerRelinquished;
420
421 // The controller that owns the currently relinquished toolbar controller.
422 // The reference is weak because it's possible for the toolbar owner to be
423 // deallocated mid-animation due to memory pressure or a tab being closed
424 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27425 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13426
427 // Always present on tablet; always nil on phone.
stkhapuginc9eee7b2017-04-10 15:49:27428 TabStripController* _tabStripController;
sdefresnee65fd872016-12-19 13:38:13429
sdefresnee65fd872016-12-19 13:38:13430 // Used to inject Javascript implementing the PaymentRequest API and to
431 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27432 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13433
434 // Used to display the Page Info UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27435 PageInfoViewController* _pageInfoController;
sdefresnee65fd872016-12-19 13:38:13436
437 // Used to display the Voice Search UI. Nil if not visible.
438 scoped_refptr<VoiceSearchController> _voiceSearchController;
439
gambard6299cc1d2017-02-21 13:06:03440 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27441 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03442
sdefresnee65fd872016-12-19 13:38:13443 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27444 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13445
sdefresnee65fd872016-12-19 13:38:13446 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27447 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13448
449 // Records the set of domains for which full screen alert has already been
450 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27451 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13452
453 // Adapter to let BVC be the delegate for WebState.
454 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
455
456 // YES if new tab is animating in.
457 BOOL _inNewTabAnimation;
458
459 // YES if Voice Search should be started when the new tab animation is
460 // finished.
461 BOOL _startVoiceSearchAfterNewTabAnimation;
462
463 // YES if the user interacts with the location bar.
464 BOOL _locationBarHasFocus;
465 // YES if a load was cancelled due to typing in the location bar.
466 BOOL _locationBarEditCancelledLoad;
467 // YES if waiting for a foreground tab due to expectNewForegroundTab.
468 BOOL _expectingForegroundTab;
469
Sylvain Defresne41170aa2017-06-15 10:25:20470 // Whether or not -shutdown has been called.
471 BOOL _isShutdown;
472
sdefresnee65fd872016-12-19 13:38:13473 // The ChromeBrowserState associated with this BVC.
474 ios::ChromeBrowserState* _browserState; // weak
475
476 // Whether or not Incognito* is enabled.
477 BOOL _isOffTheRecord;
478
479 // The last point within |_contentArea| that's received a touch.
480 CGPoint _lastTapPoint;
481
482 // The time at which |_lastTapPoint| was most recently set.
483 CFTimeInterval _lastTapTime;
484
485 // A single infobar container handles all infobars in all tabs. It keeps
486 // track of infobars for current tab (accessed via infobar helper of
487 // the current tab).
488 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
489
490 // Bridge class to deliver container change notifications to BVC.
491 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
492
493 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37494 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27495 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13496
497 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11498 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13499
500 // Card side swipe view.
stkhapuginc9eee7b2017-04-10 15:49:27501 CardSideSwipeView* _sideSwipeView;
sdefresnee65fd872016-12-19 13:38:13502
sdefresnee65fd872016-12-19 13:38:13503 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27504 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13505
506 // Bridge to register for bookmark changes.
507 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
508
509 // Cached pointer to the bookmarks model.
510 bookmarks::BookmarkModel* _bookmarkModel; // weak
511
512 // The controller that shows the bookmarking UI after the user taps the star
513 // button.
stkhapuginc9eee7b2017-04-10 15:49:27514 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13515
516 // Used to remove unreferenced external files.
517 std::unique_ptr<ExternalFileRemover> _externalFileRemover;
518
519 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27520 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13521
Eugene But56efc322017-08-11 14:03:44522 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28523 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13524
525 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27526 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13527
Jean-François Geyelin3d47c212017-08-03 09:24:09528 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27529 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13530
Rohit Rao01e0e002017-08-14 20:49:43531 // Coordinator for the share menu (Activity Services).
532 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
533
sdefresnee65fd872016-12-19 13:38:13534 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27535 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38536
Rohit Raocda0a992017-08-16 15:37:11537 // Coordinator for the QR scanner.
538 QRScannerLegacyCoordinator* _qrScannerCoordinator;
539
sczsdd860eba2017-08-10 01:55:38540 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13541 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sdefresnee65fd872016-12-19 13:38:13542}
543
544// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17545@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13546// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17547@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13548// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17549@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13550// Whether the current tab can enable the request desktop menu item.
551@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
552// Whether the sharing menu should be enabled.
553@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
554// Helper method to check web controller canShowFindBar method.
555@property(nonatomic, assign, readonly) BOOL canShowFindBar;
556// Whether the controller's view is currently available.
557// YES from viewWillAppear to viewWillDisappear.
558@property(nonatomic, assign, getter=isVisible) BOOL visible;
559// Whether the controller's view is currently visible.
560// YES from viewDidAppear to viewWillDisappear.
561@property(nonatomic, assign) BOOL viewVisible;
562// Whether the controller is currently dismissing a presented view controller.
563@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
564// Returns YES if the toolbar has not been scrolled out by fullscreen.
565@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
566 BOOL toolbarOnScreen;
567// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12568@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13569// Whether BVC prefers to hide the status bar. This value is used to determine
570// the response from the |prefersStatusBarHidden| method.
571@property(nonatomic, assign) BOOL hideStatusBar;
572// Whether the VoiceSearchBar should be displayed.
573@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
574// Coordinator for displaying a modal overlay with activity indicator to prevent
575// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17576@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13577 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39578// A block to be run when the |tabWasAdded:| method completes the animation
579// for the presentation of a new tab. Can be used to record performance metrics.
580@property(nonatomic, strong, nullable)
581 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42582// Coordinator for Recent Tabs.
583@property(nonatomic, strong)
584 RecentTabsHandsetCoordinator* recentTabsCoordinator;
sdefresnee65fd872016-12-19 13:38:13585
liaoyukeea9f3ee62017-03-07 22:05:39586// The user agent type used to load the currently visible page. User agent type
587// is NONE if there is no visible page or visible page is a native page.
588@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
589
stkhapugin952ecef2017-04-11 12:11:45590// Returns the header views, all the chrome on top of the page, including the
591// ones that cannot be scrolled off screen by full screen.
592@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
593
Cooper Knaakd0a974cd2017-08-10 18:05:47594// Used to display the new tab tip in-product help promotion bubble. |nil| if
595// the new tab tip bubble has not yet been presented. Once the bubble is
596// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38597@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47598 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38599
Helen Yang9175bd52017-08-12 00:28:40600// Used to display the new incognito tab tip in-product help promotion bubble.
601@property(nonatomic, strong)
602 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
603
sdefresnee65fd872016-12-19 13:38:13604// BVC initialization:
605// If the BVC is initialized with a valid browser state & tab model immediately,
606// the path is straightforward: functionality is enabled, and the UI is built
607// when -viewDidLoad is called.
608// If the BVC is initialized without a browser state or tab model, the tab model
609// and browser state may or may not be provided before -viewDidLoad is called.
610// In most cases, they will not, to improve startup performance.
611// In order to handle this, initialization of various aspects of BVC have been
612// broken out into the following functions, which have expectations (enforced
613// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
614
615// Registers for notifications.
616- (void)registerForNotifications;
617// Called when a tab is starting to load. If it's a link click or form
618// submission, the user is navigating away from any entries in the forward
619// history. Tell the toolbar so it can update the UI appropriately.
620// See the warning on [Tab webWillStartLoadingURL] about invocation of this
621// method sequence by malicious pages.
622- (void)pageLoadStarting:(NSNotification*)notify;
623// Called when a tab actually starts loading.
624- (void)pageLoadStarted:(NSNotification*)notify;
625// Called when a tab finishes loading. Update the Omnibox with the url and
626// stop any page load progess display.
627- (void)pageLoadComplete:(NSNotification*)notify;
628// Called when a tab is deselected in the model.
629// This notification also occurs when a tab is closed.
630- (void)tabDeselected:(NSNotification*)notify;
631// Animates sliding current tab and rotate-entering new tab while new tab loads
632// in background on the iPhone only.
633- (void)tabWasAdded:(NSNotification*)notify;
634
635// Updates non-view-related functionality with the given browser state and tab
636// model.
637// Does not matter whether or not the view has been loaded.
638- (void)updateWithTabModel:(TabModel*)model
639 browserState:(ios::ChromeBrowserState*)browserState;
640// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
641// the status bar to mimic this layout.
642- (void)installFakeStatusBar;
643// Builds the UI parts of tab strip and the toolbar. Does not matter whether
644// or not browser state and tab model are valid.
645- (void)buildToolbarAndTabStrip;
646// Updates view-related functionality with the given tab model and browser
647// state. The view must have been loaded. Uses |_browserState| and |_model|.
648- (void)addUIFunctionalityForModelAndBrowserState;
Julien Brianceaub7e590ac2017-08-01 17:30:22649// Sets the correct frame and hierarchy for subviews and helper views.
sdefresnee65fd872016-12-19 13:38:13650- (void)setUpViewLayout;
651// Sets the correct frame for the tab strip based on the given maximum width.
652- (void)layoutTabStripForWidth:(CGFloat)maxWidth;
653// Makes |tab| the currently visible tab, displaying its view. Calls
654// -selectedTabChanged on the toolbar only if |newSelection| is YES.
655- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
656// Initializes the bookmark interaction controller if not already initialized.
657- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13658// Add all delegates to the provided |tab|.
659- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14660// Remove delegates from the provided |tab|.
661- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13662// Closes the current tab, with animation if applicable.
663- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13664// Shows the Online Help Page in a tab.
665- (void)showHelpPage;
666// 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;
735// Show the Page Security Info.
736- (void)showPageInfoPopupForView:(UIView*)sourceView;
737// Hide the Page Security Info.
738- (void)hidePageInfoPopupForView:(UIView*)sourceView;
sdefresnee65fd872016-12-19 13:38:13739// The infobar state (typically height) has changed.
740- (void)infoBarContainerStateChanged:(bool)is_animating;
741// Adds a CardView on top of the contentArea either taking the size of the full
742// screen or just the size of the space under the header.
743// Returns the CardView that was added.
744- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
745// Called when either a tab finishes loading or when a tab with finished content
746// is added directly to the model via pre-rendering. The tab must be non-nil and
747// must be a member of the tab model controlled by this BrowserViewController.
748- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
749// Evaluates Javascript asynchronously using the current page context.
750- (void)openJavascript:(NSString*)javascript;
sdefresnee65fd872016-12-19 13:38:13751// Shows a self-dismissing snackbar displaying |message|.
752- (void)showSnackbar:(NSString*)message;
753// Induces an intentional crash in the browser process.
754- (void)induceBrowserCrash;
755// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17756- (void)managePermissionAndSaveImage:(NSData*)data
757 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13758// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24759// saved as a temporary file on disk then saved in photos. Saving will happen
760// on a background sequence and the completion block will be invoked on that
761// sequence.
762- (void)saveImage:(NSData*)data
763 withFileExtension:(NSString*)fileExtension
764 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13765// Called when Chrome has been denied access to the photos or videos and the
766// user can change it.
767// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
768// settings. Dismiss previous alert if it has not been dismissed yet.
769- (void)displayImageErrorAlertWithSettingsOnMainQueue;
770// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
771// previous alert if it has not been dismissed yet.
772- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
773// Called when Chrome has been denied access to the photos or videos and the
774// user cannot change it.
775// Shows a privacy alert on the main queue, with errorContent as the message.
776// Dismisses previous alert if it has not been dismissed yet.
777- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
778// Called with the results of saving a picture in the photo album. If error is
779// nil the save succeeded.
780- (void)finishSavingImageWithError:(NSError*)error;
781// Provides a view that encompasses currently displayed infobar(s) or nil
782// if no infobar is presented.
783- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
784// Returns a vertical infobar offset relative to the tab content.
785- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
786// Provides a view that encompasses the voice search bar if it's displayed or
787// nil if the voice search bar isn't displayed.
788- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
789// Returns a vertical voice search bar offset relative to the tab content.
790- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
791// Lazily instantiates |_voiceSearchController|.
792- (void)ensureVoiceSearchControllerCreated;
793// Lazily instantiates |_voiceSearchBar| and adds it to the view.
794- (void)ensureVoiceSearchBarCreated;
795// Shows/hides the voice search bar.
796- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
797// The LogoAnimationControllerOwner to be used for the next logo transition
798// animation.
799- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13800// Returns the footer view if one exists (e.g. the voice search bar).
801- (UIView*)footerView;
802// Returns the height of the header view for the tab model's current tab.
803- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13804// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45805- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13806 atOffset:(CGFloat)headerOffset;
807// Returns the y coordinate for the footer's frame when animating the footer
808// in/out of fullscreen.
809- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
810// Called when the animation for setting the header view's offset is finished.
811// |completed| should indicate if the animation finished completely or was
812// interrupted. |offset| should indicate the header offset after the animation.
813// |dragged| should indicate if the header moved due to the user dragging.
814- (void)fullScreenController:(FullScreenController*)controller
815 headerAnimationCompleted:(BOOL)completed
816 offset:(CGFloat)offset;
817// Performs a search with the image at the given url. The referrer is used to
818// download the image.
819- (void)searchByImageAtURL:(const GURL&)url
820 referrer:(const web::Referrer)referrer;
821// Saves the image at the given URL on the system's album. The referrer is used
822// to download the image.
823- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
824
Mark Cogandfcdea72017-07-18 13:47:38825// Record the last tap point based on the |originPoint| (if any) passed in
826// |command|.
827- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13828// Get return the last stored |_lastTapPoint| if it's been set within the past
829// second.
830- (CGPoint)lastTapPoint;
831// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
832- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
833// Returns the native controller being used by |tab|'s web controller.
834- (id)nativeControllerForTab:(Tab*)tab;
835// Installs the BVC as overscroll actions controller of |nativeContent| if
836// needed. Sets the style of the overscroll actions toolbar.
837- (void)setOverScrollActionControllerToStaticNativeContent:
838 (StaticHtmlNativeContent*)nativeContent;
839// Whether the BVC should declare keyboard commands.
840- (BOOL)shouldRegisterKeyboardCommands;
841// Adds the given url to the reading list.
842- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
843@end
844
845class InfoBarContainerDelegateIOS
846 : public infobars::InfoBarContainer::Delegate {
847 public:
848 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
849 : controller_(controller) {}
850
851 ~InfoBarContainerDelegateIOS() override {}
852
853 private:
854 SkColor GetInfoBarSeparatorColor() const override {
855 NOTIMPLEMENTED();
856 return SK_ColorBLACK;
857 }
858
859 int ArrowTargetHeightForInfoBar(
860 size_t index,
861 const gfx::SlideAnimation& animation) const override {
862 return 0;
863 }
864
865 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
866 int arrow_target_height,
867 int bar_target_height,
868 int* arrow_height,
869 int* arrow_half_width,
870 int* bar_height) const override {
871 DCHECK_NE(-1, bar_target_height)
872 << "Infobars don't have a default height on iOS";
873 *arrow_height = 0;
874 *arrow_half_width = 0;
875 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
876 }
877
878 void InfoBarContainerStateChanged(bool is_animating) override {
879 [controller_ infoBarContainerStateChanged:is_animating];
880 }
881
882 bool DrawInfoBarArrows(int* x) const override { return false; }
883
stkhapuginf58b10d02017-04-10 13:36:17884 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13885};
886
887// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
888@interface BrowserViewController (BookmarkBridgeMethods)
889// If a bookmark matching the currentTab url is added or moved, update the
890// toolbar state so the star highlight is in sync.
891- (void)bookmarkNodeModified:(const BookmarkNode*)node;
892- (void)allBookmarksRemoved;
893@end
894
895// Handle notification that bookmarks has been removed changed so we can update
896// the bookmarked star icon.
897class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
898 public:
899 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
900 : owner_(owner) {}
901
902 ~BrowserBookmarkModelBridge() override {}
903
904 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
905 const BookmarkNode* parent,
906 int old_index,
907 const BookmarkNode* node,
908 const std::set<GURL>& removed_urls) override {
909 [owner_ bookmarkNodeModified:node];
910 }
911
912 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
913 bool ids_reassigned) override {}
914
915 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
916 const BookmarkNode* old_parent,
917 int old_index,
918 const BookmarkNode* new_parent,
919 int new_index) override {}
920
921 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
922 const BookmarkNode* parent,
923 int index) override {
924 [owner_ bookmarkNodeModified:parent->GetChild(index)];
925 }
926
927 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
928 const BookmarkNode* node) override {}
929
930 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
931 const BookmarkNode* node) override {}
932
933 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
934 const BookmarkNode* node) override {}
935
936 void BookmarkAllUserNodesRemoved(
937 bookmarks::BookmarkModel* model,
938 const std::set<GURL>& removed_urls) override {
939 [owner_ allBookmarksRemoved];
940 }
941
942 private:
stkhapuginf58b10d02017-04-10 13:36:17943 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13944};
945
946@implementation BrowserViewController
947
948@synthesize contentArea = _contentArea;
949@synthesize typingShield = _typingShield;
950@synthesize active = _active;
951@synthesize visible = _visible;
952@synthesize viewVisible = _viewVisible;
953@synthesize dismissingModal = _dismissingModal;
954@synthesize hideStatusBar = _hideStatusBar;
955@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
956@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39957@synthesize foregroundTabWasAddedCompletionBlock =
958 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:40959@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
960@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:42961@synthesize recentTabsCoordinator = _recentTabsCoordinator;
sdefresnee65fd872016-12-19 13:38:13962
963#pragma mark - Object lifecycle
964
Mark Cogan5e3da152017-07-11 15:57:30965- (instancetype)
966 initWithTabModel:(TabModel*)model
967 browserState:(ios::ChromeBrowserState*)browserState
968 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
969applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:13970 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
971 if (self) {
972 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17973
stkhapuginc9eee7b2017-04-10 15:49:27974 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:27975 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
976 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39977 _dispatcher = [[CommandDispatcher alloc] init];
978 [_dispatcher startDispatchingToTarget:self
979 forProtocol:@protocol(UrlLoader)];
980 [_dispatcher startDispatchingToTarget:self
981 forProtocol:@protocol(WebToolbarDelegate)];
982 [_dispatcher startDispatchingToTarget:self
983 forSelector:@selector(chromeExecuteCommand:)];
Mark Cogan6c58ea92017-07-06 13:08:24984 [_dispatcher startDispatchingToTarget:self
985 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:30986 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
987 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:32988 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
989 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
990 // dispatched to the endpoint as well. Since this is potentially
991 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
992 DCHECK(!applicationCommandEndpoint ||
993 [applicationCommandEndpoint
994 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
995 [_dispatcher
996 startDispatchingToTarget:applicationCommandEndpoint
997 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:39998
sdefresnee65fd872016-12-19 13:38:13999 _javaScriptDialogPresenter.reset(
1000 new JavaScriptDialogPresenterImpl(_dialogPresenter));
1001 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
1002 // TODO(leng): Delay this.
1003 [[UpgradeCenter sharedInstance] registerClient:self];
1004 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131005 if (model && browserState)
1006 [self updateWithTabModel:model browserState:browserState];
1007 if ([[NSUserDefaults standardUserDefaults]
1008 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271009 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131010 }
1011 }
1012 return self;
1013}
1014
1015- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1016 bundle:(NSBundle*)nibBundleOrNil {
1017 NOTREACHED();
1018 return nil;
1019}
1020
1021- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1022 NOTREACHED();
1023 return nil;
1024}
1025
1026- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201027 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131028}
1029
1030#pragma mark - Accessibility
1031
1032- (BOOL)accessibilityPerformEscape {
1033 [self dismissPopups];
1034 return YES;
1035}
1036
1037#pragma mark - Properties
1038
Mark Cogan5e3da152017-07-11 15:57:301039- (id<ApplicationCommands, BrowserCommands>)dispatcher {
1040 return static_cast<id<ApplicationCommands, BrowserCommands>>(_dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241041}
1042
sdefresnee65fd872016-12-19 13:38:131043- (void)setActive:(BOOL)active {
1044 if (_active == active) {
1045 return;
1046 }
1047 _active = active;
1048
1049 // If not active, display an activity indicator overlay over the view to
1050 // prevent interaction with the web page.
1051 // TODO(crbug.com/637093): This coordinator should be managed by the
1052 // coordinator used to present BrowserViewController, when implemented.
1053 if (active) {
1054 [self.activityOverlayCoordinator stop];
1055 self.activityOverlayCoordinator = nil;
1056 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171057 self.activityOverlayCoordinator =
1058 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131059 [self.activityOverlayCoordinator start];
1060 }
1061
1062 if (_browserState) {
1063 web::ActiveStateManager* active_state_manager =
1064 web::BrowserState::GetActiveStateManager(_browserState);
1065 active_state_manager->SetActive(active);
1066 }
1067
1068 [_model setWebUsageEnabled:active];
1069 [self updateDialogPresenterActiveState];
1070
1071 if (active) {
1072 // Make sure the tab (if any; it's possible to get here without a current
1073 // tab if the caller is about to create one) ends up on screen completely.
1074 Tab* currentTab = [_model currentTab];
1075 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441076 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131077 if (_expectingForegroundTab)
1078 [currentTab.webController setOverlayPreviewMode:YES];
1079 if (currentTab)
1080 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341081 } else {
1082 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131083 }
sdefresnee65fd872016-12-19 13:38:131084 [_paymentRequestManager enablePaymentRequest:active];
1085
1086 [self setNeedsStatusBarAppearanceUpdate];
1087}
1088
1089- (void)setPrimary:(BOOL)primary {
1090 [_model setPrimary:primary];
1091 if (primary) {
1092 [self updateDialogPresenterActiveState];
1093 } else {
1094 self.dialogPresenter.active = false;
1095 }
1096}
1097
1098- (BOOL)isPlayingTTS {
1099 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1100}
1101
sdefresne6165c8742017-01-16 15:42:021102- (ios::ChromeBrowserState*)browserState {
1103 return _browserState;
1104}
1105
1106- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271107 return _model;
sdefresne6165c8742017-01-16 15:42:021108}
1109
sdefresnee65fd872016-12-19 13:38:131110- (SideSwipeController*)sideSwipeController {
1111 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271112 _sideSwipeController =
1113 [[SideSwipeController alloc] initWithTabModel:_model
1114 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131115 [_sideSwipeController setSnapshotDelegate:self];
1116 [_sideSwipeController setSwipeDelegate:self];
1117 }
1118 return _sideSwipeController;
1119}
1120
sdefresnee65fd872016-12-19 13:38:131121- (DialogPresenter*)dialogPresenter {
1122 return _dialogPresenter;
1123}
1124
sdefresnee65fd872016-12-19 13:38:131125- (BOOL)canUseDesktopUserAgent {
1126 Tab* tab = [_model currentTab];
1127 if ([self isTabNativePage:tab])
1128 return NO;
1129
1130 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441131 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131132}
1133
1134// Whether the sharing menu should be shown.
1135- (BOOL)canShowShareMenu {
kkhorimotob110b262017-06-01 18:38:251136 const GURL& URL = [_model currentTab].lastCommittedURL;
1137 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131138}
1139
1140- (BOOL)canShowFindBar {
1141 // Make sure web controller can handle find in page.
1142 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421143 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131144 return NO;
rohitrao005a6432017-03-16 20:52:421145 }
sdefresnee65fd872016-12-19 13:38:131146
rohitrao005a6432017-03-16 20:52:421147 auto* helper = FindTabHelper::FromWebState(tab.webState);
1148 return (helper && helper->CurrentPageSupportsFindInPage() &&
1149 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131150}
1151
liaoyukeea9f3ee62017-03-07 22:05:391152- (web::UserAgentType)userAgentType {
1153 web::WebState* webState = [_model currentTab].webState;
1154 if (!webState)
1155 return web::UserAgentType::NONE;
1156 web::NavigationItem* visibleItem =
1157 webState->GetNavigationManager()->GetVisibleItem();
1158 if (!visibleItem)
1159 return web::UserAgentType::NONE;
1160
1161 return visibleItem->GetUserAgentType();
1162}
1163
sdefresnee65fd872016-12-19 13:38:131164- (void)setVisible:(BOOL)visible {
1165 if (_visible == visible)
1166 return;
1167 _visible = visible;
1168}
1169
1170- (void)setViewVisible:(BOOL)viewVisible {
1171 if (_viewVisible == viewVisible)
1172 return;
1173 _viewVisible = viewVisible;
1174 self.visible = viewVisible;
1175 [self updateDialogPresenterActiveState];
1176}
1177
1178- (BOOL)isToolbarOnScreen {
1179 return [self headerHeight] - [self currentHeaderOffset] > 0;
1180}
1181
kkhorimotoa44349c12017-04-12 23:02:121182- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1183 if (_inNewTabAnimation == inNewTabAnimation)
1184 return;
1185 _inNewTabAnimation = inNewTabAnimation;
1186 [self updateDialogPresenterActiveState];
1187}
1188
sdefresnee65fd872016-12-19 13:38:131189- (BOOL)isInNewTabAnimation {
1190 return _inNewTabAnimation;
1191}
1192
1193- (BOOL)shouldShowVoiceSearchBar {
1194 // On iPads, the voice search bar should only be shown for regular horizontal
1195 // size class configurations. It should always be shown for voice search
1196 // results Tabs on iPhones, including configurations with regular horizontal
1197 // size classes (i.e. landscape iPhone 6 Plus).
1198 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1199 UIUserInterfaceSizeClassCompact;
1200 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1201 (!IsIPadIdiom() || compactWidth);
1202}
1203
1204- (void)setHideStatusBar:(BOOL)hideStatusBar {
1205 if (_hideStatusBar == hideStatusBar)
1206 return;
1207 _hideStatusBar = hideStatusBar;
1208 [self setNeedsStatusBarAppearanceUpdate];
1209}
1210
1211#pragma mark - IBActions
1212
1213- (void)shieldWasTapped:(id)sender {
1214 [_toolbarController cancelOmniboxEdit];
1215}
1216
Cooper Knaakd0a974cd2017-08-10 18:05:471217- (void)userEnteredTabSwitcher {
1218 if ([self.tabTipBubblePresenter isUserEngaged]) {
1219 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1220 }
1221}
1222
Cooper Knaake963d6702017-08-11 21:03:111223- (void)presentBubblesIfEligible {
1224 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401225 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111226}
1227
sdefresnee65fd872016-12-19 13:38:131228#pragma mark - UIViewController methods
1229
1230// Perform additional set up after loading the view, typically from a nib.
1231- (void)viewDidLoad {
jif50d5ba252016-12-20 14:00:281232 CGRect initialViewsRect = self.view.frame;
1233 initialViewsRect.origin.y += StatusBarHeight();
1234 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131235 UIViewAutoresizing initialViewAutoresizing =
1236 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1237
stkhapuginf58b10d02017-04-10 13:36:171238 self.contentArea =
1239 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131240 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171241 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131242 self.typingShield.autoresizingMask = initialViewAutoresizing;
1243 [self.typingShield addTarget:self
1244 action:@selector(shieldWasTapped:)
1245 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131246 self.view.autoresizingMask = initialViewAutoresizing;
1247 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1248 [self.view addSubview:self.contentArea];
1249 [self.view addSubview:self.typingShield];
1250 [super viewDidLoad];
1251
1252 // Install fake status bar for iPad iOS7
1253 [self installFakeStatusBar];
1254 [self buildToolbarAndTabStrip];
1255 [self setUpViewLayout];
1256 // If the tab model and browser state are valid, finish initialization.
1257 if (_model && _browserState)
1258 [self addUIFunctionalityForModelAndBrowserState];
1259
1260 // Add a tap gesture recognizer to save the last tap location for the source
1261 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271262 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1263 initWithTarget:self
1264 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131265 [tapRecognizer setDelegate:self];
1266 [tapRecognizer setCancelsTouchesInView:NO];
1267 [_contentArea addGestureRecognizer:tapRecognizer];
1268}
1269
1270- (void)viewDidAppear:(BOOL)animated {
1271 [super viewDidAppear:animated];
1272 self.viewVisible = YES;
1273 [self updateDialogPresenterActiveState];
Cooper Knaake963d6702017-08-11 21:03:111274 [self presentBubblesIfEligible];
sdefresnee65fd872016-12-19 13:38:131275}
1276
1277- (void)viewWillAppear:(BOOL)animated {
1278 [super viewWillAppear:animated];
1279
1280 // Reparent the toolbar if it's been relinquished.
1281 if (_isToolbarControllerRelinquished)
1282 [self reparentToolbarController];
1283
1284 self.visible = YES;
1285
1286 // Restore hidden infobars.
jif7fed8122017-02-08 13:15:251287 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131288 _infoBarContainer->RestoreInfobars();
1289 }
1290
1291 // If the controller is suspended, or has been paged out due to low memory,
1292 // updating the view will be handled when it's displayed again.
1293 if (![_model webUsageEnabled] || !self.contentArea)
1294 return;
1295 // Update the displayed tab (if any; the switcher may not have created one
1296 // yet) in case it changed while showing the switcher.
1297 Tab* currentTab = [_model currentTab];
1298 if (currentTab)
1299 [self displayTab:currentTab isNewSelection:YES];
1300}
1301
1302- (void)viewWillDisappear:(BOOL)animated {
1303 self.viewVisible = NO;
1304 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131305 [[_model currentTab] wasHidden];
1306 [_bookmarkInteractionController dismissSnackbar];
jif7fed8122017-02-08 13:15:251307 if (IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131308 _infoBarContainer->SuspendInfobars();
1309 }
1310 [super viewWillDisappear:animated];
1311}
1312
1313- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1314 duration:(NSTimeInterval)duration {
1315 [super willRotateToInterfaceOrientation:orient duration:duration];
1316 [self dismissPopups];
1317 [self reshowFindBarIfNeededWithCoordinator:nil];
1318}
1319
1320- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1321 [super didRotateFromInterfaceOrientation:orient];
1322
1323 // This reinitializes the toolbar, including updating the Overlay View,
1324 // if there is one.
1325 [self updateToolbar];
1326 [self infoBarContainerStateChanged:false];
1327}
1328
1329- (BOOL)prefersStatusBarHidden {
1330 return self.hideStatusBar;
1331}
1332
1333// Called when in the foreground and the OS needs more memory. Release as much
1334// as possible.
1335- (void)didReceiveMemoryWarning {
1336 // Releases the view if it doesn't have a superview.
1337 [super didReceiveMemoryWarning];
1338
1339 // Release any cached data, images, etc that aren't in use.
1340 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1341 // but since the FaviconCache uses obj-c in the header, it can't be included
1342 // there.
1343 if (_browserState) {
1344 FaviconLoader* loader =
1345 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1346 _browserState);
1347 if (loader)
1348 loader->PurgeCache();
1349 }
1350
1351 if (![self isViewLoaded]) {
1352 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1353 // as the BrowserViewController.
1354 self.contentArea = nil;
1355 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271356 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131357 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271358 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421359 self.recentTabsCoordinator = nil;
stkhapuginc9eee7b2017-04-10 15:49:271360 _toolbarController = nil;
1361 _toolbarModelDelegate = nil;
1362 _toolbarModelIOS = nil;
1363 _tabStripController = nil;
1364 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131365 }
1366}
1367
1368- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1369 [super traitCollectionDidChange:previousTraitCollection];
1370 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1371 // because in some cases the presented view controller isn't a child of the
1372 // BVC in the view controller hierarchy (some intervening object isn't a
1373 // view controller).
1374 [self.presentedViewController
1375 traitCollectionDidChange:previousTraitCollection];
1376 [_toolbarController traitCollectionDidChange:previousTraitCollection];
1377 // Update voice search bar visibility.
1378 [self updateVoiceSearchBarVisibilityAnimated:NO];
1379}
1380
1381- (void)viewWillTransitionToSize:(CGSize)size
1382 withTransitionCoordinator:
1383 (id<UIViewControllerTransitionCoordinator>)coordinator {
1384 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1385 [self dismissPopups];
1386 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1387}
1388
1389- (void)reshowFindBarIfNeededWithCoordinator:
1390 (id<UIViewControllerTransitionCoordinator>)coordinator {
1391 if (![_findBarController isFindInPageShown])
1392 return;
1393
1394 // Record focused state.
1395 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1396
1397 [self hideFindBarWithAnimation:NO];
1398
stkhapuginc9eee7b2017-04-10 15:49:271399 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131400 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1401 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271402 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131403 if (strongSelf)
1404 [strongSelf showFindBarWithAnimation:NO
1405 selectText:NO
1406 shouldFocus:isFocusedBeforeReshow];
1407 };
1408
1409 BOOL enqueued =
1410 [coordinator animateAlongsideTransition:nil completion:completion];
1411 if (!enqueued) {
1412 completion(nil);
1413 }
1414}
1415
1416- (void)dismissViewControllerAnimated:(BOOL)flag
1417 completion:(void (^)())completion {
1418 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271419 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131420 [super dismissViewControllerAnimated:flag
1421 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271422 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131423 [strongSelf setDismissingModal:NO];
1424 [strongSelf setPresenting:NO];
1425 if (completion)
1426 completion();
1427 [[strongSelf dialogPresenter] tryToPresent];
1428 }];
1429}
1430
1431- (void)presentViewController:(UIViewController*)viewControllerToPresent
1432 animated:(BOOL)flag
1433 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271434 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131435 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1436 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1437 // presented, to show a temporary view of the launch screen and then remove it
1438 // when the controller for the FRE has been presented. This fix should be
1439 // removed when the FRE startup code is rewritten.
1440 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1441 experimental_flags::AlwaysDisplayFirstRun()) &&
1442 !tests_hook::DisableFirstRun();
1443 // These if statements check that |presentViewController| is being called for
1444 // the FRE case.
1445 if (firstRunLaunch &&
1446 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1447 UINavigationController* navController =
1448 base::mac::ObjCCastStrict<UINavigationController>(
1449 viewControllerToPresent);
1450 if ([navController.topViewController
1451 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1452 self.hideStatusBar = YES;
1453
1454 // Load view from Launch Screen and add it to window.
1455 NSBundle* mainBundle = base::mac::FrameworkBundle();
1456 NSArray* topObjects =
1457 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1458 UIViewController* launchScreenController =
1459 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1460 // |launchScreenView| is loaded as an autoreleased object, and is retained
1461 // by the |completion| block below.
1462 UIView* launchScreenView = launchScreenController.view;
1463 launchScreenView.userInteractionEnabled = NO;
1464 launchScreenView.frame = self.view.window.bounds;
1465 [self.view.window addSubview:launchScreenView];
1466
1467 // Replace the completion handler sent to the superclass with one which
1468 // removes |launchScreenView| and resets the status bar. If |completion|
1469 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271470 __weak BrowserViewController* weakSelf = self;
1471 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131472 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271473 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131474 if (completion)
1475 completion();
stkhapuginc9eee7b2017-04-10 15:49:271476 };
sdefresnee65fd872016-12-19 13:38:131477 }
1478 }
1479
1480 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171481 if ([_sideSwipeController inSwipe]) {
1482 [_sideSwipeController resetContentView];
1483 }
sdefresnee65fd872016-12-19 13:38:131484
1485 [super presentViewController:viewControllerToPresent
1486 animated:flag
1487 completion:finalCompletionHandler];
1488}
1489
1490#pragma mark - Notification handling
1491
1492- (void)registerForNotifications {
1493 DCHECK(_model);
1494 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1495 [defaultCenter addObserver:self
1496 selector:@selector(pageLoadStarting:)
1497 name:kTabModelTabWillStartLoadingNotification
1498 object:_model];
1499 [defaultCenter addObserver:self
1500 selector:@selector(pageLoadStarted:)
1501 name:kTabModelTabDidStartLoadingNotification
1502 object:_model];
1503 [defaultCenter addObserver:self
1504 selector:@selector(pageLoadComplete:)
1505 name:kTabModelTabDidFinishLoadingNotification
1506 object:_model];
1507 [defaultCenter addObserver:self
1508 selector:@selector(tabDeselected:)
1509 name:kTabModelTabDeselectedNotification
1510 object:_model];
1511 [defaultCenter addObserver:self
1512 selector:@selector(tabWasAdded:)
1513 name:kTabModelNewTabWillOpenNotification
1514 object:_model];
1515}
1516
1517- (void)pageLoadStarting:(NSNotification*)notify {
1518 Tab* tab = notify.userInfo[kTabModelTabKey];
1519 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511520
1521 // Stop any Find in Page searches and close the find bar when navigating to a
1522 // new page.
1523 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361524
sdefresnee65fd872016-12-19 13:38:131525 if (tab == [_model currentTab]) {
1526 // TODO(pinkerton): Fill in here about hiding the forward button on
1527 // navigation.
1528 }
1529}
1530
1531- (void)pageLoadStarted:(NSNotification*)notify {
1532 Tab* tab = notify.userInfo[kTabModelTabKey];
1533 DCHECK(tab);
1534 if (tab == [_model currentTab]) {
1535 if (![self isTabNativePage:tab]) {
1536 [_toolbarController currentPageLoadStarted];
1537 }
1538 [self updateVoiceSearchBarVisibilityAnimated:NO];
1539 }
1540}
1541
1542- (void)pageLoadComplete:(NSNotification*)notify {
1543 // Update the UI, but only if the current tab.
1544 Tab* tab = notify.userInfo[kTabModelTabKey];
1545 if (tab == [_model currentTab]) {
1546 // There isn't any need to update the toolbar here. When the page finishes,
1547 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1548 }
1549
1550 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1551
1552 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1553}
1554
1555- (void)tabDeselected:(NSNotification*)notify {
1556 DCHECK(notify);
1557 Tab* tab = notify.userInfo[kTabModelTabKey];
1558 DCHECK(tab);
1559 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221560 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131561}
1562
1563- (void)tabWasAdded:(NSNotification*)notify {
1564 Tab* tab = notify.userInfo[kTabModelTabKey];
1565 DCHECK(tab);
1566
Eugene But56efc322017-08-11 14:03:441567 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131568
1569 // When adding new tabs, check what kind of reminder infobar should
1570 // be added to the new tab. Try to add only one of them.
1571 // This check is done when a new tab is added either through the Tools Menu
1572 // "New Tab" or through "New Tab" in Stack View Controller. This method
1573 // is called after a new tab has added and finished initial navigation.
1574 // If this is added earlier, the initial navigation may end up clearing
1575 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301576 web::WebState* webState = tab.webState;
1577 DCHECK(webState);
1578
1579 infobars::InfoBarManager* infoBarManager =
1580 InfoBarManagerImpl::FromWebState(webState);
1581 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131582 forTabId:[tab tabId]];
1583 if (!ReSignInInfoBarDelegate::Create(_browserState, tab)) {
1584 ios_internal::sync::displaySyncErrors(_browserState, tab);
1585 }
1586
1587 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501588 // phone-specific. Call the foreground tab added completion block; for
1589 // iPhones, this will get executed after the animation has finished.
1590 if (IsIPadIdiom()) {
1591 if (self.foregroundTabWasAddedCompletionBlock) {
1592 self.foregroundTabWasAddedCompletionBlock();
1593 self.foregroundTabWasAddedCompletionBlock = nil;
1594 }
sdefresnee65fd872016-12-19 13:38:131595 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501596 }
sdefresnee65fd872016-12-19 13:38:131597
1598 // Do nothing if browsing is currently suspended. The BVC will set everything
1599 // up correctly when browsing resumes.
1600 if (!self.visible || ![_model webUsageEnabled])
1601 return;
1602
1603 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1604
1605 // Block that starts voice search at the end of new Tab animation if
1606 // necessary.
1607 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1608 if (_startVoiceSearchAfterNewTabAnimation) {
1609 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001610 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131611 }
1612 };
1613
kkhorimotoa44349c12017-04-12 23:02:121614 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131615 if (!inBackground) {
1616 UIView* animationParentView = _contentArea;
1617 // Create the new page image, and load with the new tab page snapshot.
1618 CGFloat newPageOffset = 0;
1619 UIImageView* newPage;
kkhorimotob110b262017-06-01 18:38:251620 if (tab.lastCommittedURL == GURL(kChromeUINewTabURL) && !_isOffTheRecord &&
sdefresnee65fd872016-12-19 13:38:131621 !IsIPadIdiom()) {
1622 animationParentView = self.view;
1623 newPage = [self pageFullScreenOpenCloseAnimationView];
1624 } else {
1625 newPage = [self pageOpenCloseAnimationView];
1626 }
1627 newPageOffset = newPage.frame.origin.y;
1628
1629 [tab view].frame = _contentArea.bounds;
1630 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1631 [animationParentView addSubview:newPage];
1632 CGPoint origin = [self lastTapPoint];
1633 ios_internal::page_animation_util::AnimateInPaperWithAnimationAndCompletion(
1634 newPage, -newPageOffset,
1635 newPage.frame.size.height - newPage.image.size.height, origin,
1636 _isOffTheRecord, NULL, ^{
1637 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121638 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421639 // Use the model's currentTab here because it is possible that it can
1640 // be reset to a new value before the new Tab animation finished (e.g.
1641 // if another Tab shows a dialog via |dialogPresenter|). However, that
1642 // tab's view hasn't been displayed yet because it was in a new tab
1643 // animation.
1644 Tab* currentTab = [_model currentTab];
1645 if (currentTab) {
1646 [self tabSelected:currentTab];
1647 }
sdefresnee65fd872016-12-19 13:38:131648 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391649
1650 if (self.foregroundTabWasAddedCompletionBlock) {
1651 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451652 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391653 }
sdefresnee65fd872016-12-19 13:38:131654 });
1655 } else {
1656 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1657 // snapshot before adding the views needed for the background animation.
1658 Tab* topTab = [_model currentTab];
1659 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1660 visibleFrameOnly:self.isToolbarOnScreen];
1661 // Add three layers in order on top of the contentArea for the animation:
1662 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271663 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131664 InstallBackgroundInView(background);
1665 [_contentArea addSubview:background];
1666
1667 // 2. A CardView displaying the data from the current tab.
1668 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1669 NSString* title = [topTab title];
1670 if (![title length])
1671 title = [topTab urlDisplayString];
1672 [topCard setTitle:title];
1673 [topCard setFavicon:[topTab favicon]];
1674 [topCard setImage:image];
1675
1676 // 3. A new, blank CardView to represent the new tab being added.
1677 // Launch the new background tab animation.
1678 ios_internal::page_animation_util::AnimateNewBackgroundPageWithCompletion(
1679 topCard, [_contentArea frame], IsPortrait(), ^{
1680 [background removeFromSuperview];
1681 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121682 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131683 // Resnapshot the top card if it has its own toolbar, as the toolbar
1684 // will be captured in the new tab animation, but isn't desired for
1685 // the stack view snapshots.
1686 id nativeController = [self nativeControllerForTab:topTab];
1687 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1688 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1689 startVoiceSearchIfNecessaryBlock();
1690 });
peterlaurens9f1b6e02017-06-22 17:46:451691 // Reset the foreground tab completion block so that it can never be
1692 // called more than once regardless of foreground/background tab
1693 // appearances.
1694 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131695 }
1696}
1697
1698#pragma mark - UI Configuration and Layout
1699
1700- (void)updateWithTabModel:(TabModel*)model
1701 browserState:(ios::ChromeBrowserState*)browserState {
1702 DCHECK(model);
1703 DCHECK(browserState);
1704 DCHECK(!_model);
1705 DCHECK(!_browserState);
1706 _browserState = browserState;
1707 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271708 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381709
sdefresnee65fd872016-12-19 13:38:131710 [_model addObserver:self];
1711
1712 if (!_isOffTheRecord) {
1713 [DefaultIOSWebViewFactory
1714 registerWebViewFactory:[ChromeWebViewFactory class]];
1715 }
1716 NSUInteger count = [_model count];
1717 for (NSUInteger index = 0; index < count; ++index)
1718 [self installDelegatesForTab:[_model tabAtIndex:index]];
1719
1720 [self registerForNotifications];
1721
gambardbdc07cc2017-02-03 16:43:111722 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121723 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271724 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131725
sdefresnedc432f42017-01-17 14:36:591726 // Register for bookmark changed notification (BookmarkModel may be null
1727 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131728 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591729 if (_bookmarkModel) {
1730 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1731 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1732 }
sdefresnee65fd872016-12-19 13:38:131733}
1734
sdefresnee65fd872016-12-19 13:38:131735- (void)browserStateDestroyed {
1736 [self setActive:NO];
1737 // Reset the toolbar opacity in case it was changed for contextual search.
1738 [self updateToolbarControlsAlpha:1.0];
1739 [self updateToolbarBackgroundAlpha:1.0];
sdefresnee65fd872016-12-19 13:38:131740 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271741 _paymentRequestManager = nil;
sdefresnee65fd872016-12-19 13:38:131742 [_toolbarController browserStateDestroyed];
1743 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381744
1745 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431746 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111747 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381748 [_tabHistoryCoordinator disconnect];
1749
sdefresnee65fd872016-12-19 13:38:131750 // The file remover needs the browser state, so needs to be destroyed now.
stkhapuginc9eee7b2017-04-10 15:49:271751 _externalFileRemover = nil;
sdefresnee65fd872016-12-19 13:38:131752 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391753 [_dispatcher stopDispatchingToTarget:self];
1754 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131755}
1756
1757- (void)installFakeStatusBar {
1758 if (IsIPadIdiom()) {
1759 CGFloat statusBarHeight = StatusBarHeight();
1760 CGRect statusBarFrame =
1761 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271762 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131763 [statusBarView setBackgroundColor:TabStrip::BackgroundColor()];
1764 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1765 [statusBarView layer].zPosition = 99;
1766 [[self view] addSubview:statusBarView];
1767 }
1768
1769 // Add a white bar on phone so that the status bar on the NTP is white.
1770 if (!IsIPadIdiom()) {
1771 CGFloat statusBarHeight = StatusBarHeight();
1772 CGRect statusBarFrame =
1773 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:271774 UIView* statusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
sdefresnee65fd872016-12-19 13:38:131775 [statusBarView setBackgroundColor:[UIColor whiteColor]];
1776 [statusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1777 [self.view insertSubview:statusBarView atIndex:0];
1778 }
1779}
1780
1781// Create the UI elements. May or may not have valid browser state & tab model.
1782- (void)buildToolbarAndTabStrip {
1783 DCHECK([self isViewLoaded]);
1784 DCHECK(!_toolbarModelDelegate);
1785
Rohit Rao44f204302017-08-10 14:49:541786 // Initialize the prerender service before creating the toolbar controller.
1787 PrerenderService* prerenderService =
1788 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1789 if (prerenderService) {
1790 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131791 }
1792
1793 // Create the toolbar model and controller.
Rohit Rao44f204302017-08-10 14:49:541794 id<PreloadProvider> preloadProvider =
1795 prerenderService ? prerenderService->GetPreloadProvider() : nil;
rohitrao8c4c7fd2017-04-03 15:31:201796 _toolbarModelDelegate.reset(
1797 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131798 _toolbarModelIOS.reset([_dependencyFactory
1799 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
Mark Cogan6ebbde02017-07-07 12:50:131800 _toolbarController =
1801 [_dependencyFactory newWebToolbarControllerWithDelegate:self
1802 urlLoader:self
Rohit Rao44f204302017-08-10 14:49:541803 preloadProvider:preloadProvider
Mark Cogan6ebbde02017-07-07 12:50:131804 dispatcher:self.dispatcher];
justincohen75011c32017-04-28 16:31:391805 [_dispatcher startDispatchingToTarget:_toolbarController
1806 forProtocol:@protocol(OmniboxFocuser)];
sdefresnee65fd872016-12-19 13:38:131807 [_toolbarController setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271808 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131809 _voiceSearchController->SetDelegate(_toolbarController);
1810
1811 // If needed, create the tabstrip.
1812 if (IsIPadIdiom()) {
stkhapuginc9eee7b2017-04-10 15:49:271813 _tabStripController =
Mark Cogandfcdea72017-07-18 13:47:381814 [_dependencyFactory newTabStripControllerWithTabModel:_model
1815 dispatcher:self.dispatcher];
stkhapuginc9eee7b2017-04-10 15:49:271816 _tabStripController.fullscreenDelegate = self;
sdefresnee65fd872016-12-19 13:38:131817 }
1818
1819 // Create infobar container.
1820 if (!_infoBarContainerDelegate) {
1821 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1822 _infoBarContainer.reset(
1823 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1824 }
1825}
1826
1827// Enable functionality that only makes sense if the views are loaded and
1828// both browser state and tab model are valid.
1829- (void)addUIFunctionalityForModelAndBrowserState {
1830 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051831 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131832 DCHECK(_model);
1833 DCHECK([self isViewLoaded]);
1834
1835 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1836
Rohit Raoaf46af92017-08-10 12:52:301837 infobars::InfoBarManager* infoBarManager = nullptr;
1838 if (_model.currentTab) {
1839 DCHECK(_model.currentTab.webState);
1840 infoBarManager =
1841 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1842 }
sdefresnee65fd872016-12-19 13:38:131843 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1844
sczsdd860eba2017-08-10 01:55:381845 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431846 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1847 initWithBaseViewController:self];
1848 _activityServiceCoordinator.dispatcher = _dispatcher;
1849 _activityServiceCoordinator.tabModel = _model;
1850 _activityServiceCoordinator.browserState = _browserState;
1851 _activityServiceCoordinator.positionProvider = _toolbarController;
1852 _activityServiceCoordinator.presentationProvider = self;
1853 _activityServiceCoordinator.snackbarProvider = self;
1854
Rohit Raocda0a992017-08-16 15:37:111855 _qrScannerCoordinator =
1856 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
1857 _qrScannerCoordinator.dispatcher = _dispatcher;
1858 _qrScannerCoordinator.loadProvider = _toolbarController;
1859 _qrScannerCoordinator.presentationProvider = self;
1860
sczsdd860eba2017-08-10 01:55:381861 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:131862 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:381863 _tabHistoryCoordinator.dispatcher = _dispatcher;
1864 _tabHistoryCoordinator.positionProvider = _toolbarController;
1865 _tabHistoryCoordinator.tabModel = _model;
1866 _tabHistoryCoordinator.presentationProvider = self;
1867 _tabHistoryCoordinator.tabHistoryUIUpdater = _toolbarController;
1868
mathp9b4c11d2017-07-06 20:24:131869 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:271870 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:131871 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:201872 browserState:_browserState
1873 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:051874 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:191875 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:131876 }
1877}
1878
1879// Set the frame for the various views. View must be loaded.
1880- (void)setUpViewLayout {
1881 DCHECK([self isViewLoaded]);
1882
1883 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
1884
1885 CGFloat minY = [self headerOffset];
1886
1887 // If needed, position the tabstrip.
1888 if (IsIPadIdiom()) {
1889 [self layoutTabStripForWidth:widthOfView];
1890 [[self view] addSubview:[_tabStripController view]];
1891 minY += CGRectGetHeight([[_tabStripController view] frame]);
1892 }
1893
1894 // Position the toolbar next, either at the top of the browser view or
1895 // directly under the tabstrip.
1896 CGRect toolbarFrame = [[_toolbarController view] frame];
1897 toolbarFrame.origin = CGPointMake(0, minY);
1898 toolbarFrame.size.width = widthOfView;
1899 [[_toolbarController view] setFrame:toolbarFrame];
1900
1901 // Place the infobar container above the content area.
1902 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
1903 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
1904
1905 // Place the toolbar controller above the infobar container.
1906 [[self view] insertSubview:[_toolbarController view]
1907 aboveSubview:infoBarContainerView];
1908 minY += CGRectGetHeight(toolbarFrame);
1909
1910 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
1911 // content slightly.
1912 minY -= [ToolbarController toolbarDropShadowHeight];
1913
1914 // Adjust the content area to be under the toolbar, for fullscreen or below
1915 // the toolbar is not fullscreen.
1916 CGRect contentFrame = [_contentArea frame];
1917 CGFloat marginWithHeader = StatusBarHeight();
1918 CGFloat overlap = [self headerHeight] != 0 ? marginWithHeader : minY;
1919 contentFrame.size.height = CGRectGetMaxY(contentFrame) - overlap;
1920 contentFrame.origin.y = overlap;
1921 [_contentArea setFrame:contentFrame];
1922
1923 // Adjust the infobar container to be either at the bottom of the screen
1924 // (iPhone) or on the lower toolbar edge (iPad).
1925 CGRect infoBarFrame = contentFrame;
1926 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
1927 infoBarFrame.size.height = 0;
1928 [infoBarContainerView setFrame:infoBarFrame];
1929
1930 // Attach the typing shield to the content area but have it hidden.
1931 [_typingShield setFrame:[_contentArea frame]];
1932 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
1933 [_typingShield setHidden:YES];
1934 _typingShield.accessibilityIdentifier = @"Typing Shield";
1935 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1936}
1937
1938- (void)layoutTabStripForWidth:(CGFloat)maxWidth {
1939 UIView* tabStripView = [_tabStripController view];
1940 CGRect tabStripFrame = [tabStripView frame];
1941 tabStripFrame.origin = CGPointZero;
1942 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
1943 // because the CGPointZero above will break reset the offset, but it's not
1944 // clear what removing that will do.
1945 tabStripFrame.origin.y = [self headerOffset];
1946 tabStripFrame.size.width = maxWidth;
1947 [tabStripView setFrame:tabStripFrame];
1948}
1949
1950- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
1951 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:441952 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131953
kkhorimotoa44349c12017-04-12 23:02:121954 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:131955 // Hide findbar. |updateToolbar| will restore the findbar later.
1956 [self hideFindBarWithAnimation:NO];
1957
1958 // Make new content visible, resizing it first as the orientation may
1959 // have changed from the last time it was displayed.
1960 [[tab view] setFrame:_contentArea.bounds];
1961 [_contentArea displayContentView:[tab view]];
1962 }
1963 [self updateToolbar];
1964
1965 if (newSelection)
1966 [_toolbarController selectedTabChanged];
1967
1968 // Notify the Tab that it was displayed.
1969 [tab wasShown];
1970}
1971
1972- (void)initializeBookmarkInteractionController {
1973 if (_bookmarkInteractionController)
1974 return;
stkhapuginc9eee7b2017-04-10 15:49:271975 _bookmarkInteractionController =
1976 [[BookmarkInteractionController alloc] initWithBrowserState:_browserState
1977 loader:self
1978 parentController:self];
sdefresnee65fd872016-12-19 13:38:131979}
1980
1981// Update the state of back and forward buttons, hiding the forward button if
1982// there is nowhere to go. Assumes the model's current tab is up to date.
1983- (void)updateToolbar {
1984 // If the BVC has been partially torn down for low memory, wait for the
1985 // view rebuild to handle toolbar updates.
1986 if (!(_toolbarModelIOS && _browserState))
1987 return;
1988
1989 Tab* tab = [_model currentTab];
1990 if (![tab navigationManager])
1991 return;
1992 [_toolbarController updateToolbarState];
1993 [_toolbarController setShareButtonEnabled:self.canShowShareMenu];
1994
Rohit Rao44f204302017-08-10 14:49:541995 PrerenderService* prerenderService =
1996 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1997 BOOL isPrerenderTab =
1998 prerenderService && prerenderService->IsWebStatePrerendered(tab.webState);
1999 if (isPrerenderTab && !_toolbarModelIOS->IsLoading())
sdefresnee65fd872016-12-19 13:38:132000 [_toolbarController showPrerenderingAnimation];
2001
2002 // Also update the loading state for the tools menu (that is really an
2003 // extension of the toolbar on the iPhone).
2004 if (!IsIPadIdiom())
2005 [[_toolbarController toolsPopupController]
2006 setIsTabLoading:_toolbarModelIOS->IsLoading()];
2007
rohitrao005a6432017-03-16 20:52:422008 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2009 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132010 [self showFindBarWithAnimation:NO
2011 selectText:YES
2012 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362013 }
sdefresnee65fd872016-12-19 13:38:132014
2015 // Hide the toolbar if displaying phone NTP.
2016 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552017 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132018 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552019 if (item) {
2020 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132021 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2022 hideToolbar = isNTP && !_isOffTheRecord &&
2023 ![_toolbarController isOmniboxFirstResponder] &&
2024 ![_toolbarController showingOmniboxPopup];
2025 }
2026 [[_toolbarController view] setHidden:hideToolbar];
2027 }
2028}
2029
2030- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122031 self.dialogPresenter.active =
2032 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132033}
2034
2035- (void)dismissPopups {
jif7fed8122017-02-08 13:15:252036 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:132037 [self hidePageInfoPopupForView:nil];
Cooper Knaakd0a974cd2017-08-10 18:05:472038 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382039}
2040
Cooper Knaakd0a974cd2017-08-10 18:05:472041- (BubbleViewControllerPresenter*)
2042bubblePresenterForFeature:(const base::Feature&)feature
2043 direction:(BubbleArrowDirection)direction
2044 alignment:(BubbleAlignment)alignment
2045 text:(NSString*)text {
Cooper Knaak33f9f402017-08-09 18:04:382046 if (!feature_engagement::TrackerFactory::GetForBrowserState(_browserState)
2047 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472048 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382049 }
2050 // Capture |weakSelf| instead of the feature engagement tracker object
2051 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2052 // the feature engagement tracker will remain pointing to invalid memory if
2053 // its owner (the ChromeBrowserState) is deallocated.
2054 __weak BrowserViewController* weakSelf = self;
2055 void (^dismissalCallback)(void) = ^() {
2056 BrowserViewController* strongSelf = weakSelf;
2057 if (strongSelf) {
2058 feature_engagement::TrackerFactory::GetForBrowserState(
2059 strongSelf.browserState)
2060 ->Dismissed(feature);
2061 }
2062 };
2063
Cooper Knaakd0a974cd2017-08-10 18:05:472064 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382065 [[BubbleViewControllerPresenter alloc] initWithText:text
2066 arrowDirection:direction
2067 alignment:alignment
2068 dismissalCallback:dismissalCallback];
2069
Cooper Knaakd0a974cd2017-08-10 18:05:472070 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132071}
2072
Cooper Knaak120cee5e2017-08-10 20:57:002073- (void)presentNewTabTipBubbleOnInitialized {
2074 // If the tab tip bubble has already been presented and the user is still
2075 // considered engaged, it can't be overwritten or set to |nil| or else it will
2076 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2077 // can be safely overwritten or set to |nil|.
2078 if (!self.tabTipBubblePresenter.isUserEngaged) {
2079 __weak BrowserViewController* weakSelf = self;
2080 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2081 [weakSelf presentNewTabTipBubble];
2082 };
2083
2084 // Because the new tab tip occurs on startup, the feature engagement
2085 // tracker's database is not guaranteed to be loaded by this time. For the
2086 // bubble to appear properly, a callback is used to guarantee the event data
2087 // is loaded before the check to see if the promotion should be displayed.
2088 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2089 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2090 }
2091}
2092
2093- (void)presentNewTabTipBubble {
2094 NSString* text =
2095 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2096 CGPoint tabSwitcherAnchor;
2097 if (IsIPadIdiom()) {
2098 DCHECK([self.tabStripController
2099 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
2100 tabSwitcherAnchor = [self.tabStripController
2101 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2102 } else {
2103 DCHECK([self.toolbarController
2104 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
2105 tabSwitcherAnchor = [self.toolbarController
2106 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2107 }
Cooper Knaake963d6702017-08-11 21:03:112108 // If the feature engagement tracker does not consider it valid to display
2109 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2110 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002111 self.tabTipBubblePresenter =
2112 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2113 direction:BubbleArrowDirectionUp
2114 alignment:BubbleAlignmentTrailing
2115 text:text];
2116 [self.tabTipBubblePresenter presentInViewController:self
2117 view:self.view
2118 anchorPoint:tabSwitcherAnchor];
2119}
2120
Helen Yang9175bd52017-08-12 00:28:402121- (void)presentNewIncognitoTabTipBubbleOnInitialized {
2122 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2123 // user is still considered engaged.
2124 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2125 __weak BrowserViewController* weakSelf = self;
2126 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2127 [weakSelf presentNewIncognitoTabTipBubble];
2128 };
2129
2130 // Use a callback in case the new incognito tab tip should be shown on
2131 // startup. This ensures that the tracker's database will be fully loaded
2132 // before checking if the promotion should be displayed.
2133 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2134 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2135 }
2136}
2137
2138- (void)presentNewIncognitoTabTipBubble {
2139 DCHECK([self.toolbarController
2140 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2141 NSString* text = l10n_util::GetNSStringWithFixup(
2142 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
2143 CGPoint toolsButtonAnchor = [self.toolbarController
2144 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2145 self.incognitoTabTipBubblePresenter =
2146 [self bubblePresenterForFeature:feature_engagement::
2147 kIPHNewIncognitoTabTipFeature
2148 direction:BubbleArrowDirectionUp
2149 alignment:BubbleAlignmentTrailing
2150 text:text];
2151 [self.incognitoTabTipBubblePresenter
2152 presentInViewController:self
2153 view:self.view
2154 anchorPoint:toolsButtonAnchor];
2155 // Only trigger the tools menu button animation if the bubble is shown.
2156 if (self.incognitoTabTipBubblePresenter) {
2157 [self.toolbarController triggerToolsMenuButtonAnimation];
2158 }
2159}
2160
sdefresnee65fd872016-12-19 13:38:132161#pragma mark - Tap handling
2162
Mark Cogandfcdea72017-07-18 13:47:382163- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032164 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2165 _lastTapPoint = CGPointZero;
2166 } else {
2167 _lastTapPoint =
2168 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132169 }
Mark Cogane01ebce2017-07-12 19:31:032170 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132171}
2172
2173- (CGPoint)lastTapPoint {
2174 if (CACurrentMediaTime() - _lastTapTime < 1) {
2175 return _lastTapPoint;
2176 }
2177 return CGPointZero;
2178}
2179
2180- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2181 UIView* view = gestureRecognizer.view;
2182 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2183 _lastTapPoint =
2184 [[view superview] convertPoint:viewCoordinate toView:self.view];
2185 _lastTapTime = CACurrentMediaTime();
2186}
2187
2188- (BOOL)addTabIfNoTabWithNormalBrowserState {
2189 if (![_model count]) {
2190 if (!_isOffTheRecord) {
2191 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2192 transition:ui::PAGE_TRANSITION_TYPED];
2193 return YES;
2194 }
2195 }
2196 return NO;
2197}
2198
2199#pragma mark - Tab creation and selection
2200
2201// Called when either a tab finishes loading or when a tab with finished content
2202// is added directly to the model via pre-rendering.
2203- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2204 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2205
2206 // Persist the session on a delay.
2207 [_model saveSessionImmediately:NO];
2208}
2209
2210- (Tab*)addSelectedTabWithURL:(const GURL&)url
2211 postData:(TemplateURLRef::PostContent*)postData
2212 transition:(ui::PageTransition)transition {
2213 return [self addSelectedTabWithURL:url
2214 postData:postData
2215 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492216 transition:transition
2217 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132218}
2219
2220- (Tab*)addSelectedTabWithURL:(const GURL&)url
2221 transition:(ui::PageTransition)transition {
2222 return [self addSelectedTabWithURL:url
2223 atIndex:[_model count]
2224 transition:transition];
2225}
2226
2227- (Tab*)addSelectedTabWithURL:(const GURL&)url
2228 atIndex:(NSUInteger)position
2229 transition:(ui::PageTransition)transition {
2230 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492231 atIndex:position
2232 transition:transition
2233 tabAddedCompletion:nil];
2234}
2235
2236- (Tab*)addSelectedTabWithURL:(const GURL&)url
2237 atIndex:(NSUInteger)position
2238 transition:(ui::PageTransition)transition
2239 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2240 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132241 postData:NULL
2242 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492243 transition:transition
2244 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132245}
2246
2247- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2248 postData:(TemplateURLRef::PostContent*)postData
2249 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492250 transition:(ui::PageTransition)transition
2251 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132252 if (position == NSNotFound)
2253 position = [_model count];
2254 DCHECK(position <= [_model count]);
2255
2256 web::NavigationManager::WebLoadParams params(URL);
2257 params.transition_type = transition;
2258 if (postData) {
2259 // Extract the content type and post params from |postData| and add them
2260 // to the load params.
2261 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2262 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2263 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172264 params.post_data.reset(data);
2265 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132266 }
Olivier Robind508a5632017-07-19 16:29:492267
2268 if (tabAddedCompletion) {
2269 if (self.foregroundTabWasAddedCompletionBlock) {
2270 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2271 self.foregroundTabWasAddedCompletionBlock;
2272 self.foregroundTabWasAddedCompletionBlock = ^{
2273 oldForegroundTabWasAddedCompletionBlock();
2274 tabAddedCompletion();
2275 };
2276 } else {
2277 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2278 }
2279 }
2280
sdefresnea6395912017-03-01 01:14:352281 Tab* tab = [_model insertTabWithLoadParams:params
2282 opener:nil
2283 openedByDOM:NO
2284 atIndex:position
2285 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132286 return tab;
2287}
2288
olivierrobin889af53f2017-03-01 14:56:322289// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132290- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322291 web::WebState* webState = tab.webState;
2292 if (!webState)
2293 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392294 web::NavigationItem* visibleItem =
2295 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322296 if (!visibleItem)
2297 return NO;
2298 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132299}
2300
2301- (void)expectNewForegroundTab {
2302 _expectingForegroundTab = YES;
2303}
2304
2305- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2306 CGRect viewBounds, remainder;
2307 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2308 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172309 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132310}
2311
2312- (UIImageView*)pageOpenCloseAnimationView {
2313 CGRect frame = [_contentArea bounds];
2314
2315 frame.size.height = frame.size.height - [self headerHeight];
2316 frame.origin.y = [self headerHeight];
2317
stkhapuginf58b10d02017-04-10 13:36:172318 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132319 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2320 pageView.center = center;
2321
2322 pageView.backgroundColor = [UIColor whiteColor];
2323 return pageView;
2324}
2325
2326- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142327 // Unregistration happens when the Tab is removed from the TabModel.
Mike Doughertya1ec26402017-08-23 19:46:312328 tab.iOSCaptivePortalBlockingPageDelegate = self;
Mark Cogandfcdea72017-07-18 13:47:382329 tab.dispatcher = self.dispatcher;
sdefresnee65fd872016-12-19 13:38:132330 tab.dialogDelegate = self;
2331 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132332 tab.passKitDialogProvider = self;
2333 tab.fullScreenControllerDelegate = self;
2334 if (!IsIPadIdiom()) {
2335 tab.overscrollActionsControllerDelegate = self;
2336 }
olivierrobin9ce77b82017-01-12 17:29:192337 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132338 tab.tabSnapshottingDelegate = self;
2339 // Install the proper CRWWebController delegates.
2340 tab.webController.nativeProvider = self;
2341 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512342 // BrowserViewController presents SKStoreKitViewController on behalf of a
2343 // tab.
2344 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2345 if (tabHelper)
2346 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132347 tab.webState->SetDelegate(_webStateDelegate.get());
2348}
2349
sdefresne49cf2862017-03-15 13:46:142350- (void)uninstallDelegatesForTab:(Tab*)tab {
Mike Doughertya1ec26402017-08-23 19:46:312351 tab.iOSCaptivePortalBlockingPageDelegate = nil;
Mark Cogandfcdea72017-07-18 13:47:382352 tab.dispatcher = nil;
sdefresne49cf2862017-03-15 13:46:142353 tab.dialogDelegate = nil;
2354 tab.snapshotOverlayProvider = nil;
2355 tab.passKitDialogProvider = nil;
2356 tab.fullScreenControllerDelegate = nil;
2357 if (!IsIPadIdiom()) {
2358 tab.overscrollActionsControllerDelegate = nil;
2359 }
2360 tab.tabHeadersDelegate = nil;
2361 tab.tabSnapshottingDelegate = nil;
2362 tab.webController.nativeProvider = nil;
2363 tab.webController.swipeRecognizerProvider = nil;
2364 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2365 if (tabHelper)
2366 tabHelper->SetLauncher(nil);
2367 tab.webState->SetDelegate(nullptr);
2368}
2369
sdefresnee65fd872016-12-19 13:38:132370// Called when a tab is selected in the model. Make any required view changes.
2371// The notification will not be sent when the tab is already the selected tab.
2372- (void)tabSelected:(Tab*)tab {
2373 DCHECK(tab);
2374
2375 // Ignore changes while the tab stack view is visible (or while suspended).
2376 // The display will be refreshed when this view becomes active again.
2377 if (!self.visible || ![_model webUsageEnabled])
2378 return;
2379
2380 [self displayTab:tab isNewSelection:YES];
2381
kkhorimotoa44349c12017-04-12 23:02:122382 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132383 // Now that the new tab has been displayed, return to normal. Rather than
2384 // keep a reference to the previous tab, just turn off preview mode for all
2385 // tabs (since doing so is a no-op for the tabs that don't have it set).
2386 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272387 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132388 [tab.webController setOverlayPreviewMode:NO];
2389 }
2390 }
2391}
2392
2393#pragma mark - External files
2394
2395- (NSSet*)referencedExternalFiles {
2396 NSSet* filesReferencedByTabs = [_model currentlyReferencedExternalFiles];
2397
2398 // TODO(noyau): this is incorrect, the caller should know that the model is
2399 // not loaded yet.
sdefresnedc432f42017-01-17 14:36:592400 if (!_bookmarkModel || !_bookmarkModel->loaded())
sdefresnee65fd872016-12-19 13:38:132401 return filesReferencedByTabs;
2402
2403 std::vector<bookmarks::BookmarkModel::URLAndTitle> bookmarks;
2404 _bookmarkModel->GetBookmarks(&bookmarks);
2405 NSMutableSet* bookmarkedFiles = [NSMutableSet set];
2406 for (const auto& bookmark : bookmarks) {
2407 GURL bookmarkUrl = bookmark.url;
2408 if (UrlIsExternalFileReference(bookmarkUrl)) {
2409 [bookmarkedFiles
2410 addObject:base::SysUTF8ToNSString(bookmarkUrl.ExtractFileName())];
2411 }
2412 }
2413 return [filesReferencedByTabs setByAddingObjectsFromSet:bookmarkedFiles];
2414}
2415
2416- (void)removeExternalFilesImmediately:(BOOL)immediately
2417 completionHandler:(ProceduralBlock)completionHandler {
2418 DCHECK_CURRENTLY_ON(web::WebThread::UI);
2419 DCHECK(!_isOffTheRecord);
2420 _externalFileRemover.reset(new ExternalFileRemover(self));
2421 // Delay the cleanup of the unreferenced files received from other apps
2422 // to not impact startup performance.
2423 int delay = immediately ? 0 : kExternalFilesCleanupDelaySeconds;
2424 _externalFileRemover->RemoveAfterDelay(
2425 base::TimeDelta::FromSeconds(delay),
stkhapuginf58b10d02017-04-10 13:36:172426 base::BindBlockArc(completionHandler ? completionHandler
2427 : ^{
2428 }));
sdefresnee65fd872016-12-19 13:38:132429}
2430
Sylvain Defresne41170aa2017-06-15 10:25:202431- (void)shutdown {
2432 DCHECK(!_isShutdown);
2433 _isShutdown = YES;
2434
2435 _tabStripController = nil;
2436 _infoBarContainer = nil;
2437 _readingListMenuNotifier = nil;
2438 if (_bookmarkModel)
2439 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2440 [_model removeObserver:self];
2441 [[UpgradeCenter sharedInstance] unregisterClient:self];
2442 [[NSNotificationCenter defaultCenter] removeObserver:self];
2443 [_toolbarController setDelegate:nil];
2444 if (_voiceSearchController)
2445 _voiceSearchController->SetDelegate(nil);
2446 [_rateThisAppDialog setDelegate:nil];
2447 [_model closeAllTabs];
2448}
2449
sdefresnee65fd872016-12-19 13:38:132450#pragma mark - SnapshotOverlayProvider methods
2451
2452- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2453 NSMutableArray* overlays = [NSMutableArray array];
2454 if (![_model webUsageEnabled]) {
2455 return overlays;
2456 }
2457 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2458 if (voiceSearchView) {
2459 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272460 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132461 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272462 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132463 [overlays addObject:voiceSearchOverlay];
2464 }
2465 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2466 if (infoBarView) {
2467 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272468 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132469 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272470 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132471 [overlays addObject:infoBarOverlay];
2472 }
2473 return overlays;
2474}
2475
2476#pragma mark -
2477
2478- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2479 if (IsIPadIdiom()) {
2480 // Not using overlays on iPad because the content is pushed down by
2481 // infobar and the transition between snapshot and fresh page can
2482 // cause both snapshot and real infobars to appear at the same time.
2483 return nil;
2484 }
2485 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302486 if (currentTab && tab == currentTab) {
2487 DCHECK(currentTab.webState);
2488 infobars::InfoBarManager* infoBarManager =
2489 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132490 if (infoBarManager->infobar_count() > 0) {
2491 DCHECK(_infoBarContainer);
2492 return _infoBarContainer->view();
2493 }
2494 }
2495 return nil;
2496}
2497
2498- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272499 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132500 // There is no UI representation for non-current tabs or there is
2501 // no _infoBarContainer instantiated yet.
2502 // Return offset outside of tab.
2503 return CGRectGetMaxY(self.view.frame);
2504 } else if (IsIPadIdiom()) {
2505 // The infobars on iPad are display at the top of a tab.
2506 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2507 } else {
2508 // The infobars on iPhone are displayed at the bottom of a tab.
2509 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2510 return CGRectGetMaxY(visibleFrame) -
2511 CGRectGetHeight(_infoBarContainer->view().frame);
2512 }
2513}
2514
2515- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2516 Tab* currentTab = [_model currentTab];
2517 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2518 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2519 return _voiceSearchBar;
2520 }
2521 return nil;
2522}
2523
2524- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2525 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2526 // There is no UI representation for non-current tabs or there is
2527 // no visible voice search. Return offset outside of tab.
2528 return CGRectGetMaxY(self.view.frame);
2529 } else {
2530 // The voice search bar on iPhone is displayed at the bottom of a tab.
2531 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2532 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2533 }
2534}
2535
2536- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272537 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132538 VoiceSearchProvider* provider =
2539 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2540 if (provider) {
2541 _voiceSearchController =
2542 provider->CreateVoiceSearchController(_browserState);
2543 _voiceSearchController->SetDelegate(_toolbarController);
2544 }
2545 }
2546}
2547
2548- (void)ensureVoiceSearchBarCreated {
2549 if (_voiceSearchBar)
2550 return;
2551
2552 CGFloat width = CGRectGetWidth([[self view] bounds]);
2553 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2554 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272555 _voiceSearchBar = ios::GetChromeBrowserProvider()
2556 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002557 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132558 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2559 [_voiceSearchBar setHidden:YES];
2560 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2561 UIViewAutoresizingFlexibleWidth];
2562 [self.view insertSubview:_voiceSearchBar
2563 belowSubview:_infoBarContainer->view()];
2564}
2565
2566- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2567 // Voice search bar exists and is shown/hidden.
2568 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272569 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132570 return;
2571
2572 // Voice search bar doesn't exist and thus is not visible.
2573 if (!_voiceSearchBar && !show)
2574 return;
2575
2576 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272577 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132578 else
stkhapuginc9eee7b2017-04-10 15:49:272579 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132580}
2581
2582- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2583 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2584 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2585 self.shouldShowVoiceSearchBar) {
2586 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2587 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272588 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132589 }
2590 id currentNativeController =
2591 [self nativeControllerForTab:self.tabModel.currentTab];
2592 Protocol* possibleOwnerProtocol =
2593 @protocol(LogoAnimationControllerOwnerOwner);
2594 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2595 [currentNativeController logoAnimationControllerOwner]) {
2596 // If the current native controller is showing a GLIF view (e.g. the NTP
2597 // when there is no doodle), use that GLIFControllerOwner.
2598 return [currentNativeController logoAnimationControllerOwner];
2599 }
2600 return nil;
2601}
2602
2603#pragma mark - PassKitDialogProvider methods
2604
2605- (void)presentPassKitDialog:(NSData*)data {
2606 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272607 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132608 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272609 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132610 if (error || !data) {
2611 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302612 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132613 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302614 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132615 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2616 // NULL, replace if with DCHECK).
2617 if (infoBarManager)
2618 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2619 }
2620 } else {
2621 PKAddPassesViewController* passKitViewController =
2622 [_dependencyFactory newPassKitViewControllerForPass:pass];
2623 if (passKitViewController) {
2624 [self presentViewController:passKitViewController
2625 animated:YES
2626 completion:^{
2627 }];
2628 }
2629 }
2630}
2631
2632- (UIStatusBarStyle)preferredStatusBarStyle {
2633 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2634 : UIStatusBarStyleDefault;
2635}
2636
2637#pragma mark - CRWWebStateDelegate methods.
2638
eugenebut75a06fa72017-01-09 17:09:552639- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512640 createNewWebStateForURL:(const GURL&)URL
2641 openerURL:(const GURL&)openerURL
2642 initiatedByUser:(BOOL)initiatedByUser {
2643 // Check if requested web state is a popup and block it if necessary.
2644 if (!initiatedByUser) {
2645 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2646 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102647 // It's possible for a page to inject a popup into a window created via
2648 // window.open before its initial load is committed. Rather than relying
2649 // on the last committed or pending NavigationItem's referrer policy, just
2650 // use ReferrerPolicyDefault.
2651 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2652 // policy once referrer policies are correctly recorded in
2653 // NavigationItems.
2654 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512655 helper->HandlePopup(URL, referrer);
2656 return nil;
2657 }
2658 }
2659
2660 // Requested web state should not be blocked from opening.
2661 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2662 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2663
2664 // Tabs open by DOM are always renderer initiated.
2665 web::NavigationManager::WebLoadParams params(GURL{});
2666 params.transition_type = ui::PAGE_TRANSITION_LINK;
2667 params.is_renderer_initiated = true;
2668 Tab* childTab = [[self tabModel]
2669 insertTabWithLoadParams:params
2670 opener:currentTab
2671 openedByDOM:YES
2672 atIndex:TabModelConstants::kTabPositionAutomatically
2673 inBackground:NO];
2674 return childTab.webState;
2675}
2676
eugenebutb46b2122017-03-14 02:43:262677- (void)closeWebState:(web::WebState*)webState {
2678 // Only allow a web page to close itself if it was opened by DOM, or if there
2679 // are no navigation items.
2680 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582681 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262682
2683 if (![self tabModel])
2684 return;
2685
2686 NSUInteger index = [[self tabModel] indexOfTab:tab];
2687 if (index != NSNotFound)
2688 [[self tabModel] closeTabAtIndex:index];
2689}
2690
eugenebut275f5892017-03-09 22:20:512691- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552692 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2693 switch (params.disposition) {
2694 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2695 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2696 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352697 insertTabWithURL:params.url
2698 referrer:params.referrer
2699 transition:params.transition
2700 opener:LegacyTabHelper::GetTabForWebState(webState)
2701 openedByDOM:NO
2702 atIndex:TabModelConstants::kTabPositionAutomatically
2703 inBackground:(params.disposition ==
2704 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552705 return tab.webState;
2706 }
2707 case WindowOpenDisposition::CURRENT_TAB: {
2708 web::NavigationManager::WebLoadParams loadParams(params.url);
2709 loadParams.referrer = params.referrer;
2710 loadParams.transition_type = params.transition;
2711 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2712 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2713 return webState;
2714 }
eugenebutd0984e82017-02-22 23:47:512715 case WindowOpenDisposition::NEW_POPUP: {
2716 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352717 insertTabWithURL:params.url
2718 referrer:params.referrer
2719 transition:params.transition
2720 opener:LegacyTabHelper::GetTabForWebState(webState)
2721 openedByDOM:YES
2722 atIndex:TabModelConstants::kTabPositionAutomatically
2723 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512724 return tab.webState;
2725 }
eugenebut75a06fa72017-01-09 17:09:552726 default:
2727 NOTIMPLEMENTED();
2728 return nullptr;
2729 };
2730}
2731
Mike Dougherty4e6b3a32017-08-23 18:49:212732- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132733 handleContextMenu:(const web::ContextMenuParams&)params {
2734 // Prevent context menu from displaying for a tab which is no longer the
2735 // current one.
2736 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212737 return;
sdefresnee65fd872016-12-19 13:38:132738 }
2739
2740 // No custom context menu if no valid url is available in |params|.
2741 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212742 return;
sdefresnee65fd872016-12-19 13:38:132743 }
2744
2745 DCHECK(_browserState);
2746 DCHECK([_model currentTab]);
2747
stkhapuginc9eee7b2017-04-10 15:49:272748 _contextMenuCoordinator =
2749 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2750 params:params];
sdefresnee65fd872016-12-19 13:38:132751
2752 NSString* title = nil;
2753 ProceduralBlock action = nil;
2754
stkhapuginc9eee7b2017-04-10 15:49:272755 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132756 GURL link = params.link_url;
2757 bool isLink = link.is_valid();
2758 GURL imageUrl = params.src_url;
2759 bool isImage = imageUrl.is_valid();
kkhorimotob110b262017-06-01 18:38:252760 const GURL& committedURL = [_model currentTab].lastCommittedURL;
sdefresnee65fd872016-12-19 13:38:132761
2762 if (isLink) {
2763 if (link.SchemeIs(url::kJavaScriptScheme)) {
2764 // Open
2765 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2766 action = ^{
2767 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2768 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2769 };
2770 [_contextMenuCoordinator addItemWithTitle:title action:action];
2771 }
2772
2773 if (web::UrlHasWebScheme(link)) {
kkhorimotob110b262017-06-01 18:38:252774 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132775
sdefresnee65fd872016-12-19 13:38:132776 // Open in New Tab.
2777 title = l10n_util::GetNSStringWithFixup(
2778 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2779 action = ^{
2780 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002781 // The "New Tab" item in the context menu opens a new tab in the current
2782 // browser state. |isOffTheRecord| indicates whether or not the current
2783 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132784 [weakSelf webPageOrderedOpen:link
2785 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002786 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132787 inBackground:YES
2788 appendTo:kCurrentTab];
2789 };
2790 [_contextMenuCoordinator addItemWithTitle:title action:action];
2791 if (!_isOffTheRecord) {
2792 // Open in Incognito Tab.
2793 title = l10n_util::GetNSStringWithFixup(
2794 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2795 action = ^{
2796 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2797 [weakSelf webPageOrderedOpen:link
2798 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132799 inIncognito:YES
2800 inBackground:NO
2801 appendTo:kCurrentTab];
2802 };
2803 [_contextMenuCoordinator addItemWithTitle:title action:action];
2804 }
olivierrobin51d4cf42017-01-17 13:32:352805 }
gambard65d69152017-03-23 17:44:222806 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352807 NSString* innerText = params.link_text;
2808 if ([innerText length] > 0) {
2809 // Add to reading list.
2810 title = l10n_util::GetNSStringWithFixup(
2811 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2812 action = ^{
2813 Record(ACTION_READ_LATER, isImage, isLink);
2814 [weakSelf addToReadingListURL:link title:innerText];
2815 };
2816 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532817 }
sdefresnee65fd872016-12-19 13:38:132818 }
2819 // Copy Link.
2820 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2821 action = ^{
2822 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282823 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132824 };
2825 [_contextMenuCoordinator addItemWithTitle:title action:action];
2826 }
2827 if (isImage) {
kkhorimotob110b262017-06-01 18:38:252828 web::Referrer referrer(committedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132829 // Save Image.
gambard98b4ddf2017-04-18 07:14:052830 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132831 action = ^{
2832 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2833 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2834 };
2835 [_contextMenuCoordinator addItemWithTitle:title action:action];
2836 // Open Image.
2837 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
2838 action = ^{
2839 Record(ACTION_OPEN_IMAGE, isImage, isLink);
2840 [weakSelf loadURL:imageUrl
2841 referrer:referrer
2842 transition:ui::PAGE_TRANSITION_LINK
2843 rendererInitiated:YES];
2844 };
2845 [_contextMenuCoordinator addItemWithTitle:title action:action];
2846 // Open Image In New Tab.
2847 title = l10n_util::GetNSStringWithFixup(
2848 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
2849 action = ^{
2850 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
2851 [weakSelf webPageOrderedOpen:imageUrl
2852 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132853 inBackground:true
2854 appendTo:kCurrentTab];
2855 };
2856 [_contextMenuCoordinator addItemWithTitle:title action:action];
2857
2858 TemplateURLService* service =
2859 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:102860 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:132861 if (defaultURL && !defaultURL->image_url().empty() &&
2862 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
2863 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
2864 defaultURL->short_name());
2865 action = ^{
2866 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
2867 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
2868 };
2869 [_contextMenuCoordinator addItemWithTitle:title action:action];
2870 }
2871 }
2872
2873 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:132874}
2875
eugenebutb739bdc2017-01-25 06:32:482876- (void)webState:(web::WebState*)webState
2877 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
2878 // Display the action sheet with the arrow pointing at the top center of the
2879 // web contents.
sdefresne0452a9d2017-02-09 15:33:282880 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:482881 UIView* view = webState->GetView();
2882 CGPoint dialogLocation =
2883 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:282884 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:032885 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:172886 helper->PresentDialog(dialogLocation,
2887 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:052888 handler(shouldContinue);
2889 }));
eugenebutb739bdc2017-01-25 06:32:482890}
2891
sdefresnee65fd872016-12-19 13:38:132892- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
2893 (web::WebState*)webState {
2894 return _javaScriptDialogPresenter.get();
2895}
2896
eugenebut63232102017-01-19 16:19:402897- (void)webState:(web::WebState*)webState
2898 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
2899 proposedCredential:(NSURLCredential*)proposedCredential
2900 completionHandler:(void (^)(NSString* username,
2901 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:422902 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
2903 if ([tab isPrerenderTab]) {
2904 [tab discardPrerender];
2905 if (handler) {
2906 handler(nil, nil);
2907 }
2908 return;
2909 }
2910
eugenebut63232102017-01-19 16:19:402911 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
2912 proposedCredential:proposedCredential
2913 webState:webState
2914 completionHandler:handler];
2915}
2916
sdefresnee65fd872016-12-19 13:38:132917#pragma mark - FullScreenControllerDelegate methods
2918
2919- (CGFloat)headerOffset {
2920 if (IsIPadIdiom())
2921 return StatusBarHeight();
2922 return 0.0;
2923}
2924
stkhapugin952ecef2017-04-11 12:11:452925- (NSArray<HeaderDefinition*>*)headerViews {
2926 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:132927 if (![self isViewLoaded])
2928 return results;
2929
2930 if (!IsIPadIdiom()) {
2931 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452932 [results addObject:[HeaderDefinition
2933 definitionWithView:[_toolbarController view]
2934 headerBehaviour:Hideable
2935 heightAdjustment:[ToolbarController
2936 toolbarDropShadowHeight]
2937 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132938 }
2939 } else {
2940 if ([_tabStripController view]) {
stkhapugin952ecef2017-04-11 12:11:452941 [results addObject:[HeaderDefinition
2942 definitionWithView:[_tabStripController view]
2943 headerBehaviour:Hideable
2944 heightAdjustment:0.0
2945 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132946 }
2947 if ([_toolbarController view]) {
stkhapugin952ecef2017-04-11 12:11:452948 [results addObject:[HeaderDefinition
2949 definitionWithView:[_toolbarController view]
2950 headerBehaviour:Hideable
2951 heightAdjustment:[ToolbarController
2952 toolbarDropShadowHeight]
2953 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:132954 }
2955 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:452956 [results addObject:[HeaderDefinition
2957 definitionWithView:[_findBarController view]
2958 headerBehaviour:Overlap
2959 heightAdjustment:0.0
2960 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:132961 }
2962 }
stkhapugin952ecef2017-04-11 12:11:452963 return [results copy];
sdefresnee65fd872016-12-19 13:38:132964}
2965
2966- (UIView*)footerView {
2967 return _voiceSearchBar;
2968}
2969
2970- (CGFloat)headerHeight {
2971 return [self headerHeightForTab:[_model currentTab]];
2972}
2973
2974- (CGFloat)headerHeightForTab:(Tab*)tab {
2975 id nativeController = [self nativeControllerForTab:tab];
2976 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2977 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2978 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2979 // On iPhone, don't add any header height for ToolbarOwner native
2980 // controllers when they're displaying their own toolbar.
2981 return 0;
2982 }
2983
stkhapugin952ecef2017-04-11 12:11:452984 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:132985
2986 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:452987 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:132988 if (header.view && header.behaviour == Hideable) {
2989 height += CGRectGetHeight([header.view frame]) -
2990 header.heightAdjustement - header.inset;
2991 }
2992 }
2993
2994 return height - StatusBarHeight();
2995}
2996
2997- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:192998 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:132999}
3000
3001- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453002 NSArray<HeaderDefinition*>* headers = [self headerViews];
3003 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133004 return 0.0;
3005
3006 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3007 // API documentation.
3008 if ([[[self tabModel] currentTab] isPrerenderTab])
3009 return [self headerHeight];
3010
3011 UIView* topHeader = headers[0].view;
3012 return -(topHeader.frame.origin.y - [self headerOffset]);
3013}
3014
3015- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3016 UIView* footer = [self footerView];
3017 CGFloat headerHeight = [self headerHeight];
3018 if (!footer || headerHeight == 0)
3019 return 0.0;
3020
3021 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3022 CGFloat offset = headerOffset * footerHeight / headerHeight;
3023 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3024}
3025
3026- (void)fullScreenController:(FullScreenController*)controller
3027 headerAnimationCompleted:(BOOL)completed
3028 offset:(CGFloat)offset {
3029 if (completed)
justincohen04c27772016-12-21 20:16:593030 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133031}
3032
stkhapugin952ecef2017-04-11 12:11:453033- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133034 atOffset:(CGFloat)headerOffset {
3035 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453036 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:133037 CGRect frame = [header.view frame];
3038 frame.origin.y = height - headerOffset - header.inset;
3039 [header.view setFrame:frame];
3040 if (header.behaviour != Overlap)
3041 height += CGRectGetHeight(frame);
3042 }
3043}
3044
3045- (void)fullScreenController:(FullScreenController*)fullScreenController
3046 drawHeaderViewFromOffset:(CGFloat)headerOffset
3047 animate:(BOOL)animate {
3048 if ([_sideSwipeController inSwipe])
3049 return;
3050
3051 CGRect footerFrame = CGRectZero;
3052 UIView* footer = nil;
3053 // Only animate the voice search bar if the tab is a voice search results tab.
3054 if ([_model currentTab].isVoiceSearchResultsTab) {
3055 footer = [self footerView];
3056 footerFrame = footer.frame;
3057 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3058 }
3059
stkhapugin952ecef2017-04-11 12:11:453060 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133061 void (^block)(void) = ^{
3062 [self setFramesForHeaders:headers atOffset:headerOffset];
3063 footer.frame = footerFrame;
3064 };
3065 void (^completion)(BOOL) = ^(BOOL finished) {
3066 [self fullScreenController:fullScreenController
3067 headerAnimationCompleted:finished
3068 offset:headerOffset];
3069 };
3070 if (animate) {
3071 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
3072 delay:0.0
3073 options:UIViewAnimationOptionBeginFromCurrentState
3074 animations:block
3075 completion:completion];
3076 } else {
3077 block();
3078 completion(YES);
3079 }
3080}
3081
3082- (void)fullScreenController:(FullScreenController*)fullScreenController
3083 drawHeaderViewFromOffset:(CGFloat)headerOffset
3084 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3085 changeTopContentPadding:(BOOL)changeTopContentPadding
3086 scrollingToOffset:(CGFloat)contentOffset {
3087 DCHECK(webViewProxy);
3088 if ([_sideSwipeController inSwipe])
3089 return;
3090
3091 CGRect footerFrame;
3092 UIView* footer = nil;
3093 // Only animate the voice search bar if the tab is a voice search results tab.
3094 if ([_model currentTab].isVoiceSearchResultsTab) {
3095 footer = [self footerView];
3096 footerFrame = footer.frame;
3097 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3098 }
3099
stkhapugin952ecef2017-04-11 12:11:453100 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133101 void (^block)(void) = ^{
3102 [self setFramesForHeaders:headers atOffset:headerOffset];
3103 footer.frame = footerFrame;
3104 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3105 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3106 if (changeTopContentPadding)
3107 webViewProxy.topContentPadding = contentOffset;
3108 };
3109 void (^completion)(BOOL) = ^(BOOL finished) {
3110 [self fullScreenController:fullScreenController
3111 headerAnimationCompleted:finished
3112 offset:headerOffset];
3113 };
3114
3115 [UIView animateWithDuration:ios_internal::kToolbarAnimationDuration
3116 delay:0.0
3117 options:UIViewAnimationOptionBeginFromCurrentState
3118 animations:block
3119 completion:completion];
3120}
3121
3122#pragma mark - VoiceSearchBarOwner
3123
3124- (id<VoiceSearchBar>)voiceSearchBar {
3125 return _voiceSearchBar;
3126}
3127
3128#pragma mark - Install OverScrollActionController method.
3129- (void)setOverScrollActionControllerToStaticNativeContent:
3130 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343131 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133132 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173133 [[OverscrollActionsController alloc]
3134 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133135 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053136 OverscrollStyle style = _isOffTheRecord
3137 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3138 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133139 controller.style = style;
3140 nativeContent.overscrollActionsController = controller;
3141 }
3142}
3143
3144#pragma mark - OverscrollActionsControllerDelegate methods.
3145
3146- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053147 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133148 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053149 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383150 [self.dispatcher
3151 openNewTab:[OpenNewTabCommand
3152 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133153 break;
rohitrao922b7111c2017-01-03 14:31:053154 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243155 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133156 break;
liaoyuke563dc4a2017-03-17 18:36:293157 case OverscrollAction::REFRESH: {
sdefresnee65fd872016-12-19 13:38:133158 if ([[[_model currentTab] webController] loadPhase] ==
3159 web::PAGE_LOADING) {
sdefresne7d699dd2017-04-05 13:05:233160 [_model currentTab].webState->Stop();
sdefresnee65fd872016-12-19 13:38:133161 }
liaoyuke563dc4a2017-03-17 18:36:293162
3163 web::WebState* webState = [_model currentTab].webState;
3164 if (webState)
3165 // |check_for_repost| is true because the reload is explicitly initiated
3166 // by the user.
3167 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3168 true /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:133169 break;
liaoyuke563dc4a2017-03-17 18:36:293170 }
rohitrao922b7111c2017-01-03 14:31:053171 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133172 NOTREACHED();
3173 break;
3174 }
3175}
3176
3177- (BOOL)shouldAllowOverscrollActions {
3178 return YES;
3179}
3180
3181- (UIView*)headerView {
3182 return [_toolbarController view];
3183}
3184
3185- (UIView*)toolbarSnapshotView {
3186 return [[_toolbarController view] snapshotViewAfterScreenUpdates:NO];
3187}
3188
3189- (CGFloat)overscrollActionsControllerHeaderInset:
3190 (OverscrollActionsController*)controller {
3191 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3192 return [self headerHeight];
3193 else
3194 return 0;
3195}
3196
3197- (CGFloat)overscrollHeaderHeight {
3198 return [self headerHeight] + StatusBarHeight();
3199}
3200
3201#pragma mark - TabSnapshottingDelegate methods.
3202
3203- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3204 CGRect pageContentArea = _contentArea.bounds;
3205 if ([_model webUsageEnabled])
3206 pageContentArea = tab.view.bounds;
3207 CGFloat headerHeight = [self headerHeightForTab:tab];
3208 id nativeController = [self nativeControllerForTab:tab];
3209 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3210 headerHeight += [nativeController toolbarHeight];
3211 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3212 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3213}
3214
3215#pragma mark - NewTabPageObserver methods.
3216
3217- (void)selectedPanelDidChange {
3218 [self updateToolbar];
3219}
3220
3221#pragma mark - CRWNativeContentProvider methods
3222
3223- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3224 withError:(NSError*)error
3225 isPost:(BOOL)isPost {
3226 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173227 [[ErrorPageContent alloc] initWithLoader:self
3228 browserState:self.browserState
3229 url:url
3230 error:error
3231 isPost:isPost
3232 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133233 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3234 return errorPageContent;
3235}
3236
3237- (BOOL)hasControllerForURL:(const GURL&)url {
3238 std::string host(url.host());
olivierrobin5c861c22017-04-07 15:56:453239 if (host == kChromeUIOfflineHost) {
3240 // Only allow offline URL that are fully specified.
3241 return reading_list::IsOfflineURLValid(
3242 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3243 }
sdefresnee65fd872016-12-19 13:38:133244
Justin Cohen8679e852017-08-14 16:35:253245 if (host == kChromeUIBookmarksHost) {
3246 // Only allow bookmark URL on iPad.
3247 return IsIPadIdiom();
3248 }
3249
3250 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133251}
3252
olivierrobind43eecb2017-01-27 20:35:263253- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3254 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133255 DCHECK(url.SchemeIs(kChromeUIScheme));
3256
3257 id<CRWNativeContent> nativeController = nil;
3258 std::string url_host = url.host();
Justin Cohen49715952017-08-22 14:12:193259 if (url_host == kChromeUINewTabHost ||
3260 (IsIPadIdiom() && url_host == kChromeUIBookmarksHost)) {
sdefresnee65fd872016-12-19 13:38:133261 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173262 [[NewTabPageController alloc] initWithUrl:url
3263 loader:self
3264 focuser:_toolbarController
3265 ntpObserver:self
3266 browserState:_browserState
3267 colorCache:_dominantColorCache
3268 webToolbarDelegate:self
justincohenbc913632017-04-18 14:41:453269 tabModel:_model
justincohen75011c32017-04-28 16:31:393270 parentViewController:self
3271 dispatcher:_dispatcher];
sdefresnee65fd872016-12-19 13:38:133272 pageController.swipeRecognizerProvider = self.sideSwipeController;
3273
3274 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233275 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133276
3277 if (IsIPadIdiom()) {
3278 // New Tab Page can have multiple panels. Each panel is addressable
3279 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3280 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3281 // the Bookmark Manager, etc.
3282 // The utility functions NewTabPage::IdentifierFromFragment() and
3283 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3284 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3285 // without changing the URL since the URL may be chrome://bookmarks/#123.
3286 // If the URL is chrome://newtab/, pre-select the panel based on the
3287 // #fragment.
3288 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233289 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133290 : NewTabPage::IdentifierFromFragment(url.ref());
3291 }
3292 [pageController selectPanel:panelType];
3293 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453294 } else if (url_host == kChromeUIOfflineHost &&
3295 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133296 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173297 [[OfflinePageNativeContent alloc] initWithLoader:self
3298 browserState:_browserState
3299 webState:webState
3300 URL:url];
sdefresnee65fd872016-12-19 13:38:133301 [self setOverScrollActionControllerToStaticNativeContent:
3302 staticNativeController];
3303 nativeController = staticNativeController;
3304 } else if (url_host == kChromeUIExternalFileHost) {
3305 // Return an instance of the |ExternalFileController| only if the file is
3306 // still in the sandbox.
3307 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3308 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173309 nativeController =
3310 [[ExternalFileController alloc] initWithURL:url
3311 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133312 }
peterlaurens44615d02017-05-23 20:23:093313 } else if (url_host == kChromeUICrashHost) {
3314 // There is no native controller for kChromeUICrashHost, it is instead
3315 // handled as any other renderer crash by the SadTabTabHelper.
3316 // nativeController must be set to nil to prevent defaulting to a
3317 // PageNotAvailableController.
3318 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133319 } else {
3320 DCHECK(![self hasControllerForURL:url]);
3321 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173322 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133323 }
3324 // If a native controller is vended before its tab is added to the tab model,
3325 // use the temporary key and add it under the new tab's tabId in the
3326 // TabModelObserver callback. This happens:
3327 // - when there is no current tab (occurs when vending the NTP controller for
3328 // the first tab that is opened),
3329 // - when the current tab's url doesn't match |url| (occurs when a native
3330 // controller is opened in a new tab)
3331 // - when the current tab's url matches |url| and there is already a native
3332 // controller of the appropriate type vended to it (occurs when a native
3333 // controller is opened in a new tab from a tab with a matching URL, e.g.
3334 // opening an NTP when an NTP is already displayed in the current tab).
3335 // For normal page loads, history navigations, tab restorations, and crash
3336 // recoveries, the tab will already exist in the tab model and the tabId can
3337 // be used as the native controller key.
3338 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3339 // that native controllers vended here always correspond to the current tab.
3340 Tab* currentTab = [_model currentTab];
kkhorimotob110b262017-06-01 18:38:253341 if (!currentTab || currentTab.lastCommittedURL != url ||
Eugene But56efc322017-08-11 14:03:443342 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133343 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443344 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133345 }
sdefresnee65fd872016-12-19 13:38:133346 return nativeController;
3347}
3348
3349- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443350 id nativeController = tab.webController.nativeController;
3351 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133352}
3353
3354#pragma mark - DialogPresenterDelegate methods
3355
3356- (void)dialogPresenter:(DialogPresenter*)presenter
3357 willShowDialogForWebState:(web::WebState*)webState {
3358 for (Tab* iteratedTab in self.tabModel) {
3359 if ([iteratedTab webState] == webState) {
3360 self.tabModel.currentTab = iteratedTab;
3361 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3362 break;
3363 }
3364 }
3365}
3366
3367#pragma mark - Context menu methods
3368
3369- (void)searchByImageAtURL:(const GURL&)url
3370 referrer:(const web::Referrer)referrer {
3371 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273372 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113373 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173374 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3375 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113376 DCHECK(data);
3377 dispatch_async(dispatch_get_main_queue(), ^{
3378 [weakSelf searchByImageData:data atURL:image_source_url];
3379 });
3380 };
3381 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133382 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3383 web::PolicyForNavigation(url, referrer));
3384}
3385
3386- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3387 NSData* imageData = data;
3388 UIImage* image = [UIImage imageWithData:imageData];
3389 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3390 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3391 // kSearchByImageMaxImageHeight).
3392 if (image &&
3393 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3394 (image.size.width > kSearchByImageMaxImageWidth ||
3395 image.size.height > kSearchByImageMaxImageHeight)) {
3396 CGSize newImageSize =
3397 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3398 image = [image gtm_imageByResizingToSize:newImageSize
3399 preserveAspectRatio:YES
3400 trimToFit:NO];
3401 imageData = UIImageJPEGRepresentation(image, 1.0);
3402 }
3403
3404 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3405 std::string byteString(bytes, [imageData length]);
3406
3407 TemplateURLService* templateUrlService =
3408 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103409 const TemplateURL* defaultURL =
3410 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133411 DCHECK(!defaultURL->image_url().empty());
3412 DCHECK(defaultURL->image_url_ref().IsValid(
3413 templateUrlService->search_terms_data()));
3414 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3415 search_args.image_url = imageURL;
3416 search_args.image_thumbnail_content = byteString;
3417
3418 // Generate the URL and populate |post_content| with the content type and
3419 // HTTP body for the request.
3420 TemplateURLRef::PostContent post_content;
3421 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3422 search_args, templateUrlService->search_terms_data(), &post_content));
3423 [self addSelectedTabWithURL:result
3424 postData:&post_content
3425 transition:ui::PAGE_TRANSITION_TYPED];
3426}
3427
3428- (void)saveImageAtURL:(const GURL&)url
3429 referrer:(const web::Referrer&)referrer {
3430 DCHECK(url.is_valid());
3431
gambard9efce7a2017-02-09 18:53:173432 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3433 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113434 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133435
gambardbbf85c42017-06-29 11:15:343436 if ([data length] == 0) {
3437 [self displayPrivacyErrorAlertOnMainQueue:
3438 l10n_util::GetNSString(
3439 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3440 return;
3441 }
3442
gambard9efce7a2017-02-09 18:53:173443 base::FilePath::StringType extension;
3444
3445 bool extensionSuccess =
3446 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3447 if (!extensionSuccess || extension.length() == 0) {
3448 extension = "png";
3449 }
3450
3451 NSString* fileExtension =
3452 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3453 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113454 };
3455 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133456 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3457 web::PolicyForNavigation(url, referrer));
3458}
3459
gambard9efce7a2017-02-09 18:53:173460- (void)managePermissionAndSaveImage:(NSData*)data
3461 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133462 switch ([PHPhotoLibrary authorizationStatus]) {
3463 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173464 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133465 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3466 // Call -saveImage again to check if chrome needs to display an error or
3467 // saves the image.
3468 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173469 [self managePermissionAndSaveImage:data
3470 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133471 }];
3472 break;
stkhapuginf58b10d02017-04-10 13:36:173473 }
sdefresnee65fd872016-12-19 13:38:133474
3475 // The application doesn't have permission to access photo and the user
3476 // cannot grant it.
3477 case PHAuthorizationStatusRestricted:
3478 [self displayPrivacyErrorAlertOnMainQueue:
3479 l10n_util::GetNSString(
3480 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3481 break;
3482
3483 // The application doesn't have permission to access photo and the user
3484 // can grant it.
3485 case PHAuthorizationStatusDenied:
3486 [self displayImageErrorAlertWithSettingsOnMainQueue];
3487 break;
3488
3489 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243490 default:
3491 __weak BrowserViewController* weakSelf = self;
3492 [self saveImage:data
3493 withFileExtension:fileExtension
3494 completion:^(BOOL success, NSError* error) {
3495 [weakSelf finishSavingImageWithError:error];
3496 }];
sdefresnee65fd872016-12-19 13:38:133497 break;
sdefresnee65fd872016-12-19 13:38:133498 }
3499}
3500
Sylvain Defresnefd3ecf22017-07-12 18:47:243501- (void)saveImage:(NSData*)data
3502 withFileExtension:(NSString*)fileExtension
3503 completion:(void (^)(BOOL, NSError*))completion {
3504 base::PostTaskWithTraits(
3505 FROM_HERE,
3506 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3507 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3508 base::BindBlockArc(^{
3509 base::ThreadRestrictions::AssertIOAllowed();
sdefresnee65fd872016-12-19 13:38:133510
Sylvain Defresnefd3ecf22017-07-12 18:47:243511 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3512 stringByAppendingString:fileExtension];
3513 NSURL* fileURL = [NSURL
3514 fileURLWithPath:[NSTemporaryDirectory()
3515 stringByAppendingPathComponent:fileName]];
3516 NSError* error = nil;
3517 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3518 if (error) {
3519 if (completion)
3520 completion(NO, error);
3521 return;
3522 }
sdefresnee65fd872016-12-19 13:38:133523
Sylvain Defresnefd3ecf22017-07-12 18:47:243524 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3525 [PHAssetChangeRequest
3526 creationRequestForAssetFromImageAtFileURL:fileURL];
3527 }
3528 completionHandler:^(BOOL success, NSError* error) {
3529 base::PostTaskWithTraits(
3530 FROM_HERE,
3531 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3532 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3533 base::BindBlockArc(^{
3534 base::ThreadRestrictions::AssertIOAllowed();
3535 if (completion)
3536 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133537
Sylvain Defresnefd3ecf22017-07-12 18:47:243538 // Cleanup the temporary file.
3539 NSError* deleteFileError = nil;
3540 [[NSFileManager defaultManager]
3541 removeItemAtURL:fileURL
3542 error:&deleteFileError];
3543 }));
3544 }];
3545 }));
sdefresnee65fd872016-12-19 13:38:133546}
3547
3548- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3549 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3550 BOOL canGoToSetting =
3551 [[UIApplication sharedApplication] canOpenURL:settingURL];
3552 if (canGoToSetting) {
3553 dispatch_async(dispatch_get_main_queue(), ^{
3554 [self displayImageErrorAlertWithSettings:settingURL];
3555 });
3556 } else {
3557 [self displayPrivacyErrorAlertOnMainQueue:
3558 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3559 }
3560}
3561
3562- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3563 // Dismiss current alert.
3564 [_alertCoordinator stop];
3565
3566 NSString* title =
3567 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3568 NSString* message = l10n_util::GetNSString(
3569 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3570
stkhapuginc9eee7b2017-04-10 15:49:273571 _alertCoordinator =
3572 [[AlertCoordinator alloc] initWithBaseViewController:self
3573 title:title
3574 message:message];
sdefresnee65fd872016-12-19 13:38:133575
3576 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3577 action:nil
3578 style:UIAlertActionStyleCancel];
3579
3580 [_alertCoordinator
3581 addItemWithTitle:l10n_util::GetNSString(
3582 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3583 action:^{
3584 OpenUrlWithCompletionHandler(settingURL, nil);
3585 }
3586 style:UIAlertActionStyleDefault];
3587
3588 [_alertCoordinator start];
3589}
3590
3591- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3592 dispatch_async(dispatch_get_main_queue(), ^{
3593 NSString* title =
3594 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3595 [self showErrorAlertWithStringTitle:title message:errorContent];
3596 });
3597}
3598
3599// This callback is triggered when the image is effectively saved onto the photo
3600// album, or if the save failed for some reason.
3601- (void)finishSavingImageWithError:(NSError*)error {
3602 // Was there an error?
3603 if (error) {
3604 // Saving photo failed even though user has granted access to Photos.
3605 // Display the error information from the NSError object for user.
3606 NSString* errorMessage = [NSString
3607 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3608 [error domain], [error code]];
3609 // This code may be execute outside of the main thread. Make sure to display
3610 // the error on the main thread.
3611 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3612 } else {
3613 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3614 // the photo application. The current behaviour is to create the photo there
3615 // but not providing any link to it is suboptimal. That's what Safari is
3616 // doing, and what the PM want, but it doesn't make it right.
3617 }
3618}
3619
sdefresnee65fd872016-12-19 13:38:133620#pragma mark - Showing popups
3621
sdefresnee65fd872016-12-19 13:38:133622- (void)showPageInfoPopupForView:(UIView*)sourceView {
3623 Tab* tab = [_model currentTab];
3624 DCHECK([tab navigationManager]);
3625 web::NavigationItem* navItem = [tab navigationManager]->GetVisibleItem();
3626
3627 // It is fully expected to have a navItem here, as showPageInfoPopup can only
3628 // be trigerred by a button enabled when a current item matches some
3629 // conditions. However a crash was seen were navItem was NULL hence this
3630 // test after a DCHECK.
3631 DCHECK(navItem);
3632 if (!navItem)
3633 return;
3634
olivierrobin013ba672017-03-01 21:16:243635 // Don't show if the page is native except for offline pages (to show the
3636 // offline page info).
3637 if ([self isTabNativePage:tab] &&
3638 !reading_list::IsOfflineURL(navItem->GetURL())) {
sdefresnee65fd872016-12-19 13:38:133639 return;
olivierrobin013ba672017-03-01 21:16:243640 }
sdefresnee65fd872016-12-19 13:38:133641
justincohenb1a73cf2017-02-06 20:25:433642 // Don't show the bubble twice (this can happen when tapping very quickly in
3643 // accessibility mode).
3644 if (_pageInfoController)
3645 return;
3646
sdefresnee65fd872016-12-19 13:38:133647 base::RecordAction(UserMetricsAction("MobileToolbarPageSecurityInfo"));
3648
3649 // Dismiss the omnibox (if open).
3650 [_toolbarController cancelOmniboxEdit];
3651
3652 [[NSNotificationCenter defaultCenter]
3653 postNotificationName:ios_internal::kPageInfoWillShowNotification
3654 object:nil];
3655
3656 // TODO(rohitrao): Get rid of PageInfoModel completely.
3657 PageInfoModelBubbleBridge* bridge = new PageInfoModelBubbleBridge();
3658 PageInfoModel* pageInfoModel = new PageInfoModel(
3659 _browserState, navItem->GetURL(), navItem->GetSSL(), bridge);
3660
3661 UIView* view = [self view];
stkhapuginc9eee7b2017-04-10 15:49:273662 _pageInfoController = [[PageInfoViewController alloc]
sdefresnee65fd872016-12-19 13:38:133663 initWithModel:pageInfoModel
3664 bridge:bridge
3665 sourceFrame:[sourceView convertRect:[sourceView bounds] toView:view]
stkhapuginc9eee7b2017-04-10 15:49:273666 parentView:view];
Mark Coganb9aac6432017-07-07 13:26:353667 _pageInfoController.dispatcher = self.dispatcher;
stkhapuginc9eee7b2017-04-10 15:49:273668 bridge->set_controller(_pageInfoController);
sdefresnee65fd872016-12-19 13:38:133669}
3670
3671- (void)hidePageInfoPopupForView:(UIView*)sourceView {
3672 [_pageInfoController dismiss];
stkhapuginc9eee7b2017-04-10 15:49:273673 _pageInfoController = nil;
sdefresnee65fd872016-12-19 13:38:133674}
3675
3676- (void)showSecurityHelpPage {
3677 [self webPageOrderedOpen:GURL(kPageInfoHelpCenterURL)
3678 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133679 inBackground:NO
3680 appendTo:kCurrentTab];
3681 [self hidePageInfoPopupForView:nil];
3682}
3683
sdefresnee65fd872016-12-19 13:38:133684- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133685 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3686
3687 ReadingListModel* readingModel =
3688 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013689 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3690 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133691
pinkerton07e27842017-03-02 15:29:023692 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
gambarde31ad3ba2017-01-19 14:40:033693 [self showSnackbar:l10n_util::GetNSString(
3694 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133695}
3696
3697#pragma mark - Keyboard commands management
3698
3699- (BOOL)shouldRegisterKeyboardCommands {
3700 if ([self presentedViewController])
3701 return NO;
3702
3703 if (_voiceSearchController && _voiceSearchController->IsVisible())
3704 return NO;
3705
3706 // If there is no first responder, try to make the webview the first
3707 // responder.
3708 if (!GetFirstResponder()) {
stkhapuginc9eee7b2017-04-10 15:49:273709 [_model.currentTab.webController.webViewProxy becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133710 }
3711
3712 return YES;
3713}
3714
3715- (KeyCommandsProvider*)keyCommandsProvider {
3716 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273717 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133718 }
stkhapuginc9eee7b2017-04-10 15:49:273719 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133720}
3721
3722#pragma mark - KeyCommandsPlumbing
3723
3724- (BOOL)isOffTheRecord {
3725 return _isOffTheRecord;
3726}
3727
3728- (NSUInteger)tabsCount {
3729 return [_model count];
3730}
3731
lpromero47ea8862017-01-13 17:51:063732- (BOOL)canGoBack {
3733 return [_model currentTab].canGoBack;
3734}
3735
3736- (BOOL)canGoForward {
3737 return [_model currentTab].canGoForward;
3738}
3739
sdefresnee65fd872016-12-19 13:38:133740- (void)focusTabAtIndex:(NSUInteger)index {
3741 if ([_model count] > index) {
3742 [_model setCurrentTab:[_model tabAtIndex:index]];
3743 }
3744}
3745
3746- (void)focusNextTab {
3747 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3748 NSInteger modelCount = [_model count];
3749 if (currentTabIndex < modelCount - 1) {
3750 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3751 [_model setCurrentTab:nextTab];
3752 } else {
3753 [_model setCurrentTab:[_model tabAtIndex:0]];
3754 }
3755}
3756
3757- (void)focusPreviousTab {
3758 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3759 if (currentTabIndex > 0) {
3760 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3761 [_model setCurrentTab:previousTab];
3762 } else {
3763 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3764 [_model setCurrentTab:lastTab];
3765 }
3766}
3767
3768- (void)reopenClosedTab {
3769 sessions::TabRestoreService* const tabRestoreService =
3770 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3771 if (!tabRestoreService || tabRestoreService->entries().empty())
3772 return;
3773
3774 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3775 tabRestoreService->entries().front();
3776 // Only handle the TAB type.
3777 if (entry->type != sessions::TabRestoreService::TAB)
3778 return;
3779
Mark Cogandfcdea72017-07-18 13:47:383780 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133781 TabRestoreServiceDelegateImplIOS* const delegate =
3782 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3783 _browserState);
3784 tabRestoreService->RestoreEntryById(delegate, entry->id,
3785 WindowOpenDisposition::CURRENT_TAB);
3786}
3787
3788- (void)focusOmnibox {
3789 [_toolbarController focusOmnibox];
3790}
3791
3792#pragma mark - UIResponder
3793
3794- (NSArray*)keyCommands {
3795 if (![self shouldRegisterKeyboardCommands]) {
3796 return nil;
3797 }
3798 return [self.keyCommandsProvider
3799 keyCommandsForConsumer:self
Mark Cogan6c58ea92017-07-06 13:08:243800 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133801 editingText:![self isFirstResponder]];
3802}
3803
3804#pragma mark -
3805
3806// Induce an intentional crash in the browser process.
3807- (void)induceBrowserCrash {
3808 CHECK(false);
3809 // Call another function, so that the above CHECK can't be tail-call
3810 // optimized. This ensures that this method's name will show up in the stack
3811 // for easier identification.
3812 CHECK(true);
3813}
3814
3815- (void)loadURL:(const GURL&)url
3816 referrer:(const web::Referrer&)referrer
3817 transition:(ui::PageTransition)transition
3818 rendererInitiated:(BOOL)rendererInitiated {
3819 [[OmniboxGeolocationController sharedInstance]
3820 locationBarDidSubmitURL:url
3821 transition:transition
3822 browserState:_browserState];
3823
3824 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3825 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3826 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3827 }
3828
3829 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243830 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133831 // restart.
3832 if (url.host() == kChromeUIBrowserCrashHost) {
3833 [self induceBrowserCrash];
3834 // In debug the app can continue working even after the CHECK. Adding a
3835 // return avoids the crash url to be added to the history.
3836 return;
3837 }
3838
Rohit Rao44f204302017-08-10 14:49:543839 PrerenderService* prerenderService =
3840 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3841 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593842 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543843 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593844 DCHECK(newWebState);
3845
sdefresnee65fd872016-12-19 13:38:133846 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593847 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133848 DCHECK(oldTab);
3849 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593850
kkhorimotod804c5732017-03-15 23:44:523851 bool canPruneItems =
3852 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593853
kkhorimotod804c5732017-03-15 23:44:523854 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523855 [newTab navigationManager]->CopyStateFromAndPrune(
3856 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593857
3858 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3859 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133860
3861 // Set isPrerenderTab to NO after replacing the tab. This will allow the
3862 // BrowserViewController to detect that a pre-rendered tab is switched in,
3863 // and show the prerendering animation.
3864 newTab.isPrerenderTab = NO;
3865
sdefresne2f7781c2017-03-02 19:12:463866 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133867 return;
3868 }
3869 }
3870
3871 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:543872 if (prerenderService) {
3873 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:133874 }
3875
sdefresnee65fd872016-12-19 13:38:133876 // Some URLs are not allowed while in incognito. If we are in incognito and
3877 // load a disallowed URL, instead create a new tab not in the incognito state.
3878 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3879 [self webPageOrderedOpen:url
3880 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:133881 inIncognito:NO
3882 inBackground:NO
3883 appendTo:kCurrentTab];
3884 return;
3885 }
3886
mrefaata84d5a02017-06-08 17:13:293887 // If this is a reload initiated from the omnibox.
3888 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
3889 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
3890 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
3891 [[_model currentTab] navigationManager]->Reload(
3892 web::ReloadType::NORMAL, true /* check_for_repost */);
3893 return;
3894 }
3895
sdefresnee65fd872016-12-19 13:38:133896 web::NavigationManager::WebLoadParams params(urlToLoad);
3897 params.referrer = referrer;
3898 params.transition_type = transition;
3899 params.is_renderer_initiated = rendererInitiated;
3900 DCHECK([_model currentTab]);
sdefresne7d699dd2017-04-05 13:05:233901 [[_model currentTab] navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:133902}
3903
3904- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:543905 PrerenderService* prerenderService =
3906 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3907 if (prerenderService) {
3908 prerenderService->CancelPrerender();
3909 }
sdefresnee65fd872016-12-19 13:38:133910 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:183911 if ([self currentWebState])
3912 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:133913}
3914
3915- (web::WebState*)currentWebState {
3916 return [[_model currentTab] webState];
3917}
3918
3919// This is called from within an animation block.
3920- (void)toolbarHeightChanged {
3921 if ([self headerHeight] != 0) {
3922 // Ensure full screen height is updated.
3923 Tab* currentTab = [_model currentTab];
3924 BOOL visible = self.isToolbarOnScreen;
3925 [currentTab updateFullscreenWithToolbarVisible:visible];
3926 }
3927}
3928
3929// Load a new URL on a new page/tab.
3930- (void)webPageOrderedOpen:(const GURL&)URL
3931 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133932 inBackground:(BOOL)inBackground
3933 appendTo:(OpenPosition)appendTo {
3934 Tab* adjacentTab = nil;
3935 if (appendTo == kCurrentTab)
3936 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:353937 [_model insertTabWithURL:URL
3938 referrer:referrer
3939 transition:ui::PAGE_TRANSITION_LINK
3940 opener:adjacentTab
3941 openedByDOM:NO
3942 atIndex:TabModelConstants::kTabPositionAutomatically
3943 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:133944}
3945
3946- (void)webPageOrderedOpen:(const GURL&)url
3947 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:133948 inIncognito:(BOOL)inIncognito
3949 inBackground:(BOOL)inBackground
3950 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003951 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:233952 // feature_engagement::Tracker based on |inIncognito|.
3953 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003954
sdefresnee65fd872016-12-19 13:38:133955 if (inIncognito == _isOffTheRecord) {
3956 [self webPageOrderedOpen:url
3957 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133958 inBackground:inBackground
3959 appendTo:appendTo];
3960 return;
3961 }
3962 // When sending an open command that switches modes, ensure the tab
3963 // ends up appended to the end of the model, not just next to what is
3964 // currently selected in the other mode. This is done with the |append|
3965 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:273966 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:133967 initWithURL:url
3968 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:133969 inIncognito:inIncognito
3970 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:273971 appendTo:kLastTab];
sdefresnee65fd872016-12-19 13:38:133972 [self chromeExecuteCommand:command];
3973}
3974
3975- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:053976 WebStateList* webStateList = [_model webStateList];
3977 webStateList->ReplaceWebStateAt(
3978 webStateList->active_index(),
3979 session_util::CreateWebStateWithNavigationEntries(
3980 [_model browserState], sessionTab->current_navigation_index,
3981 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:133982}
3983
3984- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:433985 DCHECK(javascript);
3986 javascript = [javascript stringByRemovingPercentEncoding];
3987 web::WebState* webState = [[_model currentTab] webState];
3988 if (webState) {
3989 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3990 }
sdefresnee65fd872016-12-19 13:38:133991}
3992
3993#pragma mark - WebToolbarDelegate methods
3994
3995- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
3996 if (_locationBarHasFocus)
3997 return; // TODO(crbug.com/244366): This should not be necessary.
3998 _locationBarHasFocus = YES;
3999 [[NSNotificationCenter defaultCenter]
4000 postNotificationName:ios_internal::
4001 kLocationBarBecomesFirstResponderNotification
4002 object:nil];
4003 [_sideSwipeController setEnabled:NO];
4004 if ([[_model currentTab].webController wantsKeyboardShield]) {
4005 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
4006 [_typingShield setAlpha:0.0];
4007 [_typingShield setHidden:NO];
4008 [UIView animateWithDuration:0.3
4009 animations:^{
4010 [_typingShield setAlpha:1.0];
4011 }];
4012 }
4013 [[OmniboxGeolocationController sharedInstance]
4014 locationBarDidBecomeFirstResponder:_browserState];
4015}
4016
4017- (IBAction)locationBarDidResignFirstResponder:(id)sender {
4018 if (!_locationBarHasFocus)
4019 return; // TODO(crbug.com/244366): This should not be necessary.
4020 _locationBarHasFocus = NO;
4021 [_sideSwipeController setEnabled:YES];
4022 [[NSNotificationCenter defaultCenter]
4023 postNotificationName:ios_internal::
4024 kLocationBarResignsFirstResponderNotification
4025 object:nil];
4026 [UIView animateWithDuration:0.3
4027 animations:^{
4028 [_typingShield setAlpha:0.0];
4029 }
4030 completion:^(BOOL finished) {
4031 // This can happen if one quickly resigns the omnibox and then taps
4032 // on the omnibox again during this animation. If the animation is
4033 // interrupted and the toolbar controller is first responder, it's safe
4034 // to assume the |_typingShield| shouldn't be hidden here.
4035 if (!finished && [_toolbarController isOmniboxFirstResponder])
4036 return;
4037 [_typingShield setHidden:YES];
4038 }];
4039 [[OmniboxGeolocationController sharedInstance]
4040 locationBarDidResignFirstResponder:_browserState];
4041
4042 // If a load was cancelled by an omnibox edit, but nothing is loading when
4043 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4044 if (_locationBarEditCancelledLoad) {
4045 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294046
4047 web::WebState* webState = [_model currentTab].webState;
4048 if (!_toolbarModelIOS->IsLoading() && webState)
4049 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4050 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134051 }
4052}
4053
4054- (IBAction)locationBarBeganEdit:(id)sender {
4055 // On handsets, if a page is currently loading it should be stopped.
4056 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354057 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134058 _locationBarEditCancelledLoad = YES;
4059 }
4060}
4061
4062- (IBAction)prepareToEnterTabSwitcher:(id)sender {
4063 [[_model currentTab] updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4064}
4065
4066- (ToolbarModelIOS*)toolbarModelIOS {
4067 return _toolbarModelIOS.get();
4068}
4069
4070- (void)updateToolbarBackgroundAlpha:(CGFloat)alpha {
4071 [_toolbarController setBackgroundAlpha:alpha];
4072}
4073
4074- (void)updateToolbarControlsAlpha:(CGFloat)alpha {
4075 [_toolbarController setControlsAlpha:alpha];
4076}
4077
4078- (void)willUpdateToolbarSnapshot {
4079 [[_model currentTab].overscrollActionsController clear];
4080}
4081
4082- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4083 CGRect frame = [_contentArea frame];
4084 if (!fullScreen) {
4085 // Changing the origin here is unnecessary, it's set in page_animation_util.
4086 frame.size.height -= [self headerHeight];
4087 }
4088
4089 CGFloat shortAxis = frame.size.width;
4090 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
4091 shortAxis -= shortInset + 2 * ios_internal::page_animation_util::kCardMargin;
4092 CGFloat aspectRatio = frame.size.height / frame.size.width;
4093 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4094 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4095 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4096 CGRect cardFrame = {frame.origin, cardSize};
4097
4098 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174099 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134100 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4101 : CardCloseButtonSide::LEADING;
4102 [_contentArea addSubview:card];
4103 return card;
4104}
4105
Mark Cogan6ebbde02017-07-07 12:50:134106#pragma mark - BrowserCommands
4107
4108- (void)goBack {
4109 [[_model currentTab] goBack];
4110}
4111
4112- (void)goForward {
4113 [[_model currentTab] goForward];
4114}
4115
Mark Coganb9aac6432017-07-07 13:26:354116- (void)stopLoading {
4117 [_model currentTab].webState->Stop();
4118}
4119
4120- (void)reload {
4121 web::WebState* webState = [_model currentTab].webState;
4122 if (webState) {
4123 // |check_for_repost| is true because the reload is explicitly initiated
4124 // by the user.
4125 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4126 true /* check_for_repost */);
4127 }
4128}
4129
Mark Cogan8e791022017-07-10 09:55:354130- (void)bookmarkPage {
4131 [self initializeBookmarkInteractionController];
4132 [_bookmarkInteractionController
4133 presentBookmarkForTab:[_model currentTab]
4134 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()
4135 inView:[_toolbarController bookmarkButtonView]
4136 originRect:[_toolbarController bookmarkButtonAnchorRect]];
4137}
4138
Mark Cogan6acee7f2017-07-11 09:01:404139- (void)showToolsMenu {
4140 DCHECK(_browserState);
4141 DCHECK(self.visible || self.dismissingModal);
4142
4143 // Record the time this menu was requested; to be stored in the configuration
4144 // object.
4145 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
4146
4147 // Dismiss the omnibox (if open).
4148 [_toolbarController cancelOmniboxEdit];
4149 // Dismiss the soft keyboard (if open).
4150 [[_model currentTab].webController dismissKeyboard];
4151 // Dismiss Find in Page focus.
4152 [self updateFindBar:NO shouldFocus:NO];
4153
4154 ToolsMenuConfiguration* configuration =
4155 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]];
4156 configuration.requestStartTime =
4157 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4158 if ([_model count] == 0)
4159 [configuration setNoOpenedTabs:YES];
4160
4161 if (_isOffTheRecord)
4162 [configuration setInIncognito:YES];
4163
4164 if (!_readingListMenuNotifier) {
4165 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4166 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4167 _browserState)];
4168 }
Cooper Knaake4f495cf2017-07-27 23:30:034169
4170 feature_engagement::Tracker* engagementTracker =
4171 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4172 if (engagementTracker->ShouldTriggerHelpUI(
4173 feature_engagement::kIPHBadgedReadingListFeature)) {
4174 [configuration setShowReadingListNewBadge:YES];
4175 [configuration setEngagementTracker:engagementTracker];
4176 }
Mark Cogan6acee7f2017-07-11 09:01:404177 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4178
4179 [configuration setUserAgentType:self.userAgentType];
4180
Helen Yang9175bd52017-08-12 00:28:404181 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4182 [configuration setHighlightNewIncognitoTabCell:YES];
4183 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4184 }
4185
4186 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4187 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4188 }
4189
Mark Cogan6acee7f2017-07-11 09:01:404190 [_toolbarController showToolsMenuPopupWithConfiguration:configuration];
4191
4192 ToolsPopupController* toolsPopupController =
4193 [_toolbarController toolsPopupController];
4194 if ([_model currentTab]) {
4195 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4196 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4197 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
Mark Cogan6acee7f2017-07-11 09:01:404198 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4199
4200 if (!IsIPadIdiom())
4201 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4202 }
4203}
4204
Mark Cogandfcdea72017-07-18 13:47:384205- (void)openNewTab:(OpenNewTabCommand*)command {
4206 if (self.isOffTheRecord != command.incognito) {
4207 // Not for this browser state, send it on its way.
4208 [self.dispatcher switchModesAndOpenNewTab:command];
4209 return;
4210 }
4211
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004212 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234213 // events to the feature_engagement::Tracker based on |command.userInitiated|
4214 // and |command.incognito|.
4215 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004216
Mark Cogandfcdea72017-07-18 13:47:384217 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4218 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494219 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4220 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384221 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494222 if (oldForegroundTabWasAddedCompletionBlock) {
4223 oldForegroundTabWasAddedCompletionBlock();
4224 }
Mark Cogandfcdea72017-07-18 13:47:384225 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4226 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4227 if (offTheRecord) {
4228 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4229 timeDelta);
4230 } else {
4231 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4232 }
4233 };
4234
4235 [self setLastTapPoint:command];
4236 DCHECK(self.visible || self.dismissingModal);
4237 Tab* currentTab = [_model currentTab];
4238 if (currentTab) {
4239 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4240 }
4241 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4242 transition:ui::PAGE_TRANSITION_TYPED];
4243}
4244
Mark Cogan123895002017-07-20 12:54:064245- (void)printTab {
4246 Tab* currentTab = [_model currentTab];
4247 // The UI should prevent users from printing non-printable pages. However, a
4248 // redirection to an un-printable page can happen before it is reflected in
4249 // the UI.
4250 if (![currentTab viewForPrinting]) {
4251 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
4252 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
4253 return;
4254 }
4255 DCHECK(_browserState);
4256 if (!_printController) {
4257 _printController = [[PrintController alloc]
4258 initWithContextGetter:_browserState->GetRequestContext()];
4259 }
4260 [_printController printView:[currentTab viewForPrinting]
4261 withTitle:[currentTab title]
4262 viewController:self];
4263}
4264
Mark Coganfa25b052017-07-20 17:31:034265- (void)addToReadingList:(ReadingListAddCommand*)command {
4266 [self addToReadingListURL:[command URL] title:[command title]];
4267}
4268
sczs3a8c8602017-08-01 20:14:084269- (void)showReadingList {
4270 _readingListCoordinator = [[ReadingListCoordinator alloc]
4271 initWithBaseViewController:self
4272 browserState:self.browserState
4273 loader:self];
4274
4275 [_readingListCoordinator start];
4276}
4277
Jean-François Geyelinedef9552017-08-07 09:56:564278- (void)preloadVoiceSearch {
4279 // Preload VoiceSearchController and views and view controllers needed
4280 // for voice search.
4281 [self ensureVoiceSearchControllerCreated];
4282 _voiceSearchController->PrepareToAppear();
4283}
4284
edchinc5720722017-08-14 22:06:314285#if !defined(NDEBUG)
4286- (void)viewSource {
4287 Tab* tab = [_model currentTab];
4288 DCHECK(tab);
4289 CRWWebController* webController = tab.webController;
4290 NSString* script = @"document.documentElement.outerHTML;";
4291 __weak Tab* weakTab = tab;
4292 __weak BrowserViewController* weakSelf = self;
4293 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4294 Tab* strongTab = weakTab;
4295 if (!strongTab)
4296 return;
4297 if (![result isKindOfClass:[NSString class]])
4298 result = @"Not an HTML page";
4299 std::string base64HTML;
4300 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4301 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
4302 web::Referrer referrer([strongTab lastCommittedURL],
4303 web::ReferrerPolicyDefault);
4304
4305 [[weakSelf tabModel]
4306 insertTabWithURL:URL
4307 referrer:referrer
4308 transition:ui::PAGE_TRANSITION_LINK
4309 opener:strongTab
4310 openedByDOM:YES
4311 atIndex:TabModelConstants::kTabPositionAutomatically
4312 inBackground:NO];
4313 };
4314 [webController executeJavaScript:script
4315 completionHandler:completionHandlerBlock];
4316}
4317#endif // !defined(NDEBUG)
4318
edchin2134c042017-08-18 13:57:354319// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4320- (void)showRateThisAppDialog {
4321 DCHECK(!_rateThisAppDialog);
4322
4323 // Store the current timestamp whenever this dialog is shown.
4324 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4325 base::Time::Now().ToInternalValue());
4326
4327 // Some versions of iOS7 do not support linking directly to the "Ratings and
4328 // Reviews" appstore page. For iOS7 fall back to an alternative URL that
4329 // links to the main appstore page for the Chrome app.
4330 NSURL* storeURL =
4331 [NSURL URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4332 @"MZStore.woa/wa/"
4333 @"viewContentsUserReviews?type=Purple+Software&id="
4334 @"535886823&pt=9008&ct=rating")];
4335
4336 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
4337 [self clearPresentedStateWithCompletion:nil];
4338
4339 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4340 [_rateThisAppDialog setAppStoreURL:storeURL];
4341 [_rateThisAppDialog setDelegate:self];
4342 [_rateThisAppDialog show];
4343}
4344
Gregory Chatzinoff3f40c1542017-08-30 07:50:044345- (void)showFindInPage {
4346 if (!self.canShowFindBar)
4347 return;
4348
4349 if (!_findBarController) {
4350 _findBarController =
4351 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4352 _findBarController.dispatcher = self.dispatcher;
4353 }
4354
4355 Tab* tab = [_model currentTab];
4356 DCHECK(tab);
4357 auto* helper = FindTabHelper::FromWebState(tab.webState);
4358 DCHECK(!helper->IsFindUIActive());
4359 helper->SetFindUIActive(true);
4360 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4361}
4362
4363- (void)closeFindInPage {
4364 __weak BrowserViewController* weakSelf = self;
4365 Tab* currentTab = [_model currentTab];
4366 if (currentTab) {
4367 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4368 [weakSelf updateFindBar:NO shouldFocus:NO];
4369 });
4370 }
4371}
4372
4373- (void)searchFindInPage {
4374 DCHECK([_model currentTab]);
4375 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4376 __weak BrowserViewController* weakSelf = self;
4377 helper->StartFinding(
4378 [_findBarController searchTerm], ^(FindInPageModel* model) {
4379 BrowserViewController* strongSelf = weakSelf;
4380 if (!strongSelf) {
4381 return;
4382 }
4383 [strongSelf->_findBarController updateResultsCount:model];
4384 });
4385
4386 if (!_isOffTheRecord)
4387 helper->PersistSearchTerm();
4388}
4389
4390- (void)findNextStringInPage {
4391 Tab* currentTab = [_model currentTab];
4392 DCHECK(currentTab);
4393 // TODO(crbug.com/603524): Reshow find bar if necessary.
4394 FindTabHelper::FromWebState(currentTab.webState)
4395 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4396 [_findBarController updateResultsCount:model];
4397 });
4398}
4399
4400- (void)findPreviousStringInPage {
4401 Tab* currentTab = [_model currentTab];
4402 DCHECK(currentTab);
4403 // TODO(crbug.com/603524): Reshow find bar if necessary.
4404 FindTabHelper::FromWebState(currentTab.webState)
4405 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4406 [_findBarController updateResultsCount:model];
4407 });
4408}
4409
sdefresnee65fd872016-12-19 13:38:134410#pragma mark - Command Handling
4411
4412- (IBAction)chromeExecuteCommand:(id)sender {
4413 NSInteger command = [sender tag];
4414
4415 if (!_model || !_browserState)
4416 return;
4417
4418 switch (command) {
sdefresnee65fd872016-12-19 13:38:134419 case IDC_HELP_PAGE_VIA_MENU:
4420 [self showHelpPage];
4421 break;
sdefresnee65fd872016-12-19 13:38:134422 case IDC_SHOW_MAIL_COMPOSER:
4423 [self showMailComposer:sender];
4424 break;
sdefresnee65fd872016-12-19 13:38:134425 case IDC_REQUEST_DESKTOP_SITE:
liaoyuke6ab362012017-04-12 16:10:074426 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
sdefresnee65fd872016-12-19 13:38:134427 break;
liaoyukeea9f3ee62017-03-07 22:05:394428 case IDC_REQUEST_MOBILE_SITE:
Yuke Liao705611d2017-06-01 18:03:064429 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
liaoyukeea9f3ee62017-03-07 22:05:394430 break;
sdefresnee65fd872016-12-19 13:38:134431 case IDC_SHOW_BOOKMARK_MANAGER: {
4432 if (IsIPadIdiom()) {
4433 [self showAllBookmarks];
4434 } else {
4435 [self initializeBookmarkInteractionController];
4436 [_bookmarkInteractionController presentBookmarks];
4437 }
4438 break;
4439 }
4440 case IDC_SHOW_OTHER_DEVICES: {
4441 if (IsIPadIdiom()) {
Gauthier Ambardf520c022017-08-29 07:42:234442 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
sdefresnee65fd872016-12-19 13:38:134443 } else {
Gauthier Ambardd4287fc2017-08-29 09:14:424444 if (!self.recentTabsCoordinator) {
4445 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4446 initWithBaseViewController:self];
4447 self.recentTabsCoordinator.loader = self;
4448 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4449 self.recentTabsCoordinator.browserState = _browserState;
4450 }
4451 [self.recentTabsCoordinator start];
sdefresnee65fd872016-12-19 13:38:134452 }
4453 break;
4454 }
sdefresnee65fd872016-12-19 13:38:134455 case IDC_SHOW_PAGE_INFO:
4456 DCHECK([sender isKindOfClass:[UIButton class]]);
4457 [self showPageInfoPopupForView:sender];
4458 break;
4459 case IDC_HIDE_PAGE_INFO:
4460 [[NSNotificationCenter defaultCenter]
4461 postNotificationName:ios_internal::kPageInfoWillHideNotification
4462 object:nil];
4463 [self hidePageInfoPopupForView:sender];
4464 break;
4465 case IDC_SHOW_SECURITY_HELP:
4466 [self showSecurityHelpPage];
4467 break;
sdefresnee65fd872016-12-19 13:38:134468 default:
4469 // Unknown commands get sent up the responder chain.
4470 [super chromeExecuteCommand:sender];
4471 break;
4472 }
4473}
4474
4475- (void)closeCurrentTab {
4476 Tab* currentTab = [_model currentTab];
4477 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4478 if (tabIndex == NSNotFound)
4479 return;
4480
jif7fed8122017-02-08 13:15:254481 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4482 // iPad.
sdefresnee65fd872016-12-19 13:38:134483 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4484 exitingPage.image =
4485 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4486
4487 // Close the actual tab, and add its image as a subview.
4488 [_model closeTabAtIndex:tabIndex];
4489
4490 // Do not animate close in iPad.
4491 if (!IsIPadIdiom()) {
4492 [_contentArea addSubview:exitingPage];
4493 ios_internal::page_animation_util::AnimateOutWithCompletion(
4494 exitingPage, 0, YES, IsPortrait(), ^{
4495 [exitingPage removeFromSuperview];
4496 });
4497 }
4498}
4499
sdefresnee65fd872016-12-19 13:38:134500- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion {
Rohit Rao01e0e002017-08-14 20:49:434501 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134502 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4503 [_bookmarkInteractionController dismissSnackbar];
4504 [_toolbarController cancelOmniboxEdit];
4505 [_dialogPresenter cancelAllDialogs];
4506 [self hidePageInfoPopupForView:nil];
Cooper Knaakd0a974cd2017-08-10 18:05:474507 [self.tabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134508 if (_voiceSearchController)
4509 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364510
4511 Tab* currentTab = [_model currentTab];
4512 [currentTab dismissModals];
4513
rohitrao005a6432017-03-16 20:52:424514 if (currentTab) {
4515 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4516 if (findHelper) {
4517 findHelper->StopFinding(^{
4518 [self updateFindBar:NO shouldFocus:NO];
4519 });
4520 }
4521 }
rohitraob2bf3cb2017-02-10 14:10:364522
sdefresnee65fd872016-12-19 13:38:134523 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134524 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274525 _printController = nil;
jif7fed8122017-02-08 13:15:254526 [_toolbarController dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134527 [_contextMenuCoordinator stop];
4528 [self dismissRateThisAppDialog];
4529
4530 if (self.presentedViewController) {
4531 // Dismisses any other modal controllers that may be present, e.g. Recent
4532 // Tabs.
4533 // Note that currently, some controllers like the bookmark ones were already
4534 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
4535 // but are still reported as the presentedViewController. The result is that
4536 // this will call -dismissViewControllerAnimated:completion: a second time
4537 // on it. It is not per se an issue, as it is a no-op. The problem is that
4538 // in such a case, the completion block is not called.
4539 // To ensure the completion is called, nil is passed here, and the
4540 // completion is called below.
4541 [self dismissViewControllerAnimated:NO completion:nil];
4542 // Dismissed controllers will be so after a delay. Queue the completion
4543 // callback after that.
4544 if (completion) {
4545 dispatch_after(
4546 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4547 dispatch_get_main_queue(), ^{
4548 completion();
4549 });
4550 }
4551 } else if (completion) {
4552 // If no view controllers are presented, we should be ok with dispatching
4553 // the completion block directly.
4554 dispatch_async(dispatch_get_main_queue(), completion);
4555 }
4556}
4557
4558- (void)showHelpPage {
4559 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4560 [self webPageOrderedOpen:helpUrl
4561 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134562 inBackground:NO
4563 appendTo:kCurrentTab];
4564}
4565
sdefresnee65fd872016-12-19 13:38:134566#pragma mark - Find Bar
4567
4568- (void)hideFindBarWithAnimation:(BOOL)animate {
4569 [_findBarController hideFindBarView:animate];
4570}
4571
4572- (void)showFindBarWithAnimation:(BOOL)animate
4573 selectText:(BOOL)selectText
4574 shouldFocus:(BOOL)shouldFocus {
4575 DCHECK(_findBarController);
4576 Tab* tab = [_model currentTab];
4577 DCHECK(tab);
4578 CRWWebController* webController = tab.webController;
4579
4580 CGRect referenceFrame = CGRectZero;
4581 if (IsIPadIdiom()) {
4582 referenceFrame = webController.visibleFrame;
4583 referenceFrame.origin.y -= kIPadFindBarOverlap;
4584 } else {
4585 referenceFrame = _contentArea.frame;
4586 }
4587
4588 CGRect omniboxFrame = [_toolbarController visibleOmniboxFrame];
4589 [_findBarController addFindBarView:animate
4590 intoView:self.view
4591 withFrame:referenceFrame
4592 alignWithFrame:omniboxFrame
4593 selectText:selectText];
4594 [self updateFindBar:YES shouldFocus:shouldFocus];
4595}
4596
sdefresnee65fd872016-12-19 13:38:134597- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324598 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4599 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4600 // in a crash.
4601 if (![_model currentTab]) {
4602 return;
4603 }
rohitrao005a6432017-03-16 20:52:424604 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4605 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134606 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424607 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134608 }
4609
4610 [self setFramesForHeaders:[self headerViews]
4611 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424612 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134613 initialUpdate:initialUpdate
4614 focusTextfield:shouldFocus];
4615 } else {
4616 [self hideFindBarWithAnimation:YES];
4617 }
4618}
4619
4620- (void)showAllBookmarks {
4621 DCHECK(self.visible || self.dismissingModal);
4622 GURL URL(kChromeUIBookmarksURL);
4623 Tab* tab = [_model currentTab];
4624 web::NavigationManager::WebLoadParams params(URL);
4625 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234626 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134627}
4628
Gauthier Ambardf520c022017-08-29 07:42:234629- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134630 DCHECK(self.visible || self.dismissingModal);
4631 GURL url(kChromeUINewTabURL);
4632 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4633 if (fragment != "") {
4634 GURL::Replacements replacement;
4635 replacement.SetRefStr(fragment);
4636 url = url.ReplaceComponents(replacement);
4637 }
4638 Tab* tab = [_model currentTab];
4639 web::NavigationManager::WebLoadParams params(url);
4640 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234641 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134642}
4643
sdefresnee65fd872016-12-19 13:38:134644- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274645 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134646 base::RecordAction(base::UserMetricsAction(
4647 "IOSRateThisAppDialogDismissedProgramatically"));
4648 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274649 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134650 }
4651}
4652
Jean-François Geyelin5d2e184c2017-07-28 19:48:004653- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4654 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134655 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124656 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134657 _startVoiceSearchAfterNewTabAnimation = YES;
4658 return;
4659 }
4660
4661 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4662 // dismiss the keyboard.
4663 [self closeFindInPage];
4664 [[_model currentTab].webController dismissKeyboard];
4665
4666 // Ensure that voice search objects are created.
4667 [self ensureVoiceSearchControllerCreated];
4668 [self ensureVoiceSearchBarCreated];
4669
4670 // Present voice search.
4671 [_voiceSearchBar prepareToPresentVoiceSearch];
4672 _voiceSearchController->StartRecognition(self, [_model currentTab]);
4673 [_toolbarController cancelOmniboxEdit];
4674}
4675
4676#pragma mark - ToolbarOwner
4677
4678- (ToolbarController*)relinquishedToolbarController {
4679 if (_isToolbarControllerRelinquished)
4680 return nil;
4681
4682 ToolbarController* relinquishedToolbarController = nil;
4683 if ([_toolbarController view].hidden) {
4684 Tab* currentTab = [_model currentTab];
kkhorimotob110b262017-06-01 18:38:254685 if (currentTab && UrlHasChromeScheme(currentTab.lastCommittedURL)) {
sdefresnee65fd872016-12-19 13:38:134686 // Use the native content controller's toolbar when the BVC's is hidden.
4687 id nativeController = [self nativeControllerForTab:currentTab];
4688 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4689 relinquishedToolbarController =
4690 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274691 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134692 }
4693 }
4694 } else {
stkhapuginc9eee7b2017-04-10 15:49:274695 relinquishedToolbarController = _toolbarController;
sdefresnee65fd872016-12-19 13:38:134696 }
4697 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4698 return relinquishedToolbarController;
4699}
4700
4701- (void)reparentToolbarController {
4702 if (_isToolbarControllerRelinquished) {
4703 if ([[_toolbarController view] isDescendantOfView:self.view]) {
4704 // A native content controller's toolbar has been relinquished.
4705 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274706 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134707 } else if ([_findBarController isFindInPageShown]) {
4708 [self.view insertSubview:[_toolbarController view]
4709 belowSubview:[_findBarController view]];
4710 } else {
4711 [self.view addSubview:[_toolbarController view]];
4712 }
sdefresnee65fd872016-12-19 13:38:134713 _isToolbarControllerRelinquished = NO;
4714 }
4715}
4716
4717#pragma mark - TabModelObserver methods
4718
4719// Observer method, tab inserted.
4720- (void)tabModel:(TabModel*)model
4721 didInsertTab:(Tab*)tab
4722 atIndex:(NSUInteger)modelIndex
4723 inForeground:(BOOL)fg {
4724 DCHECK(tab);
4725 [self installDelegatesForTab:tab];
4726
4727 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194728 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134729 }
4730}
4731
4732// Observer method, active tab changed.
4733- (void)tabModel:(TabModel*)model
4734 didChangeActiveTab:(Tab*)newTab
4735 previousTab:(Tab*)previousTab
4736 atIndex:(NSUInteger)index {
4737 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4738 // Currently this observer method is always called with a non-nil |newTab|,
4739 // but that may change in the future. Remove this DCHECK when it does.
4740 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274741 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304742 DCHECK(newTab.webState);
4743 infobars::InfoBarManager* infoBarManager =
4744 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134745 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4746 }
4747 [self updateVoiceSearchBarVisibilityAnimated:NO];
4748
Mohamad Ahmadi7d09ec32017-07-11 22:32:194749 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134750
4751 [self tabSelected:newTab];
4752 DCHECK_EQ(newTab, [model currentTab]);
4753 [self installDelegatesForTab:newTab];
4754}
4755
4756// Observer method, tab changed.
4757- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4758 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4759 if (tab == [_model currentTab]) {
4760 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134761 }
4762}
4763
sdefresne49cf2862017-03-15 13:46:144764// Observer method, tab replaced.
4765- (void)tabModel:(TabModel*)model
4766 didReplaceTab:(Tab*)oldTab
4767 withTab:(Tab*)newTab
4768 atIndex:(NSUInteger)index {
4769 [self uninstallDelegatesForTab:oldTab];
4770 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264771
michaeldo79909fb2017-05-09 23:42:504772 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304773 infobars::InfoBarManager* infoBarManager = nullptr;
4774 if (newTab) {
4775 DCHECK(newTab.webState);
4776 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4777 }
michaeldo79909fb2017-05-09 23:42:504778 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4779 }
4780
kkhorimotofa0844cc2017-03-20 17:01:264781 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4782 if (self.active && model.currentTab == newTab)
4783 [self displayTab:newTab isNewSelection:NO];
sdefresne49cf2862017-03-15 13:46:144784}
4785
sdefresnee65fd872016-12-19 13:38:134786// A tab has been removed, remove its views from display if necessary.
4787- (void)tabModel:(TabModel*)model
4788 didRemoveTab:(Tab*)tab
4789 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144790 [self uninstallDelegatesForTab:tab];
4791
kkhorimoto496fdd72017-06-12 19:56:314792 // Cancel dialogs for |tab|'s WebState.
4793 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4794
sdefresnee65fd872016-12-19 13:38:134795 // Ignore changes while the tab stack view is visible (or while suspended).
4796 // The display will be refreshed when this view becomes active again.
4797 if (!self.visible || !model.webUsageEnabled)
4798 return;
4799
4800 // Remove the find bar for now.
4801 [self hideFindBarWithAnimation:NO];
4802}
4803
4804- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4805 if (tab == [model currentTab]) {
4806 [_contentArea displayContentView:nil];
4807 [_toolbarController selectedTabChanged];
4808 }
4809
Mohamad Ahmadi7d09ec32017-07-11 22:32:194810 [_paymentRequestManager stopTrackingWebState:tab.webState];
4811
sdefresnee65fd872016-12-19 13:38:134812 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4813 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194814 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134815 }
4816}
4817
4818// Called when the number of tabs changes. Update the toolbar accordingly.
4819- (void)tabModelDidChangeTabCount:(TabModel*)model {
4820 DCHECK(model == _model);
sdefresnee65fd872016-12-19 13:38:134821 [_toolbarController setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134822}
4823
4824#pragma mark - Upgrade Detection
4825
4826- (void)showUpgrade:(UpgradeCenter*)center {
4827 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274828 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134829 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304830 DCHECK(tab.webState);
4831 infobars::InfoBarManager* infoBarManager =
4832 InfoBarManagerImpl::FromWebState(tab.webState);
4833 DCHECK(infoBarManager);
4834 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134835 }
4836}
4837
sdefresnee65fd872016-12-19 13:38:134838
4839#pragma mark - InfoBarControllerDelegate
4840
4841- (void)infoBarContainerStateChanged:(bool)isAnimating {
4842 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4843 DCHECK(infoBarContainerView);
4844 CGRect containerFrame = infoBarContainerView.frame;
4845 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4846 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4847 containerFrame.size.height = height;
4848 BOOL isViewVisible = self.visible;
4849 [UIView animateWithDuration:0.1
4850 animations:^{
4851 [infoBarContainerView setFrame:containerFrame];
4852 }
4853 completion:^(BOOL finished) {
4854 if (!isViewVisible)
4855 return;
4856 UIAccessibilityPostNotification(
4857 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4858 }];
4859}
4860
4861- (BOOL)shouldAutorotate {
4862 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4863 // Don't rotate if a voice search is being presented or dismissed. Once the
4864 // transition animations finish, only the Voice Search UIViewController's
4865 // |-shouldAutorotate| will be called.
4866 return NO;
4867 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4868 // Don't auto rotate if side swipe controller view says not to.
4869 return NO;
4870 } else {
4871 return [super shouldAutorotate];
4872 }
4873}
4874
4875// Always return yes, as this tap should work with various recognizers,
4876// including UITextTapRecognizer, UILongPressGestureRecognizer,
4877// UIScrollViewPanGestureRecognizer and others.
4878- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4879 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4880 (UIGestureRecognizer*)otherGestureRecognizer {
4881 return YES;
4882}
4883
4884// Tap gestures should only be recognized within |_contentArea|.
4885- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4886 CGPoint location = [gesture locationInView:self.view];
4887
4888 // Only allow touches on descendant views of |_contentArea|.
4889 UIView* hitView = [self.view hitTest:location withEvent:nil];
4890 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4891}
4892
4893#pragma mark - SideSwipeController Delegate Methods
4894
4895- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4896 DCHECK(!IsIPadIdiom());
4897 // Update frame incase orientation changed while |_contentArea| was out of
4898 // the view hierarchy.
4899 [_contentArea setFrame:[sideSwipeView frame]];
4900
4901 [self.view insertSubview:_contentArea atIndex:0];
4902 [self updateVoiceSearchBarVisibilityAnimated:NO];
4903 [self updateToolbar];
4904
4905 // Reset horizontal stack view.
4906 [sideSwipeView removeFromSuperview];
4907 [_sideSwipeController setInSwipe:NO];
4908 [_infoBarContainer->view() setHidden:NO];
4909}
4910
4911- (UIView*)contentView {
4912 return _contentArea;
4913}
4914
4915- (TabStripController*)tabStripController {
4916 return _tabStripController;
4917}
4918
4919- (WebToolbarController*)toolbarController {
4920 return _toolbarController;
4921}
4922
4923- (BOOL)preventSideSwipe {
4924 if ([_toolbarController toolsPopupController])
4925 return YES;
4926
4927 if (_voiceSearchController && _voiceSearchController->IsVisible())
4928 return YES;
4929
sdefresnee65fd872016-12-19 13:38:134930 if (!self.active)
4931 return YES;
4932
4933 return NO;
4934}
4935
4936- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
4937 if (visible) {
4938 [self updateVoiceSearchBarVisibilityAnimated:NO];
4939 [self updateToolbar];
4940 [_infoBarContainer->view() setHidden:NO];
4941 } else {
4942 // Hide UI accessories such as find bar and first visit overlays
4943 // for welcome page.
4944 [self hideFindBarWithAnimation:NO];
4945 [_infoBarContainer->view() setHidden:YES];
4946 [_voiceSearchBar setHidden:YES];
4947 }
4948}
4949
4950- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
4951 BOOL seenToolbar = NO;
4952 BOOL seenInfoBarContainer = NO;
4953 BOOL seenContentArea = NO;
4954 for (UIView* view in views.subviews) {
4955 if (view == [_toolbarController view])
4956 seenToolbar = YES;
4957 else if (view == _infoBarContainer->view())
4958 seenInfoBarContainer = YES;
4959 else if (view == _contentArea)
4960 seenContentArea = YES;
4961 if ((seenToolbar && !seenInfoBarContainer) ||
4962 (seenInfoBarContainer && !seenContentArea))
4963 return NO;
4964 }
4965 return YES;
4966}
4967
4968#pragma mark - PreloadControllerDelegate methods
4969
rohitraoeeb5293b2017-06-15 14:40:024970- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:444971 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:134972}
4973
rohitraoeeb5293b2017-06-15 14:40:024974- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
4975 return [self hasControllerForURL:url];
4976}
4977
sdefresnee65fd872016-12-19 13:38:134978#pragma mark - BookmarkBridgeMethods
4979
4980// If an added or removed bookmark is the same as the current url, update the
4981// toolbar so the star highlight is kept in sync.
4982- (void)bookmarkNodeModified:(const BookmarkNode*)node {
kkhorimotob110b262017-06-01 18:38:254983 if ([_model currentTab] &&
4984 node->url() == [_model currentTab].lastCommittedURL) {
sdefresnee65fd872016-12-19 13:38:134985 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:254986 }
sdefresnee65fd872016-12-19 13:38:134987}
4988
4989// If all bookmarks are removed, update the toolbar so the star highlight is
4990// kept in sync.
4991- (void)allBookmarksRemoved {
4992 [self updateToolbar];
4993}
4994
sdefresnee65fd872016-12-19 13:38:134995- (void)showErrorAlertWithStringTitle:(NSString*)title
4996 message:(NSString*)message {
4997 // Dismiss current alert.
4998 [_alertCoordinator stop];
4999
stkhapuginc9eee7b2017-04-10 15:49:275000 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5001 message:message
5002 viewController:self];
sdefresnee65fd872016-12-19 13:38:135003 [_alertCoordinator start];
5004}
5005
5006- (void)showSnackbar:(NSString*)message {
5007 [_dependencyFactory showSnackbarWithMessage:message];
5008}
5009
5010#pragma mark - Show Mail Composer methods
5011
5012- (void)showMailComposer:(id)sender {
5013 ShowMailComposerCommand* command = (ShowMailComposerCommand*)sender;
5014 if (![MFMailComposeViewController canSendMail]) {
5015 NSString* alertTitle =
5016 l10n_util::GetNSString([command emailNotConfiguredAlertTitleId]);
5017 NSString* alertMessage =
5018 l10n_util::GetNSString([command emailNotConfiguredAlertMessageId]);
5019 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5020 return;
5021 }
stkhapuginc9eee7b2017-04-10 15:49:275022 MFMailComposeViewController* mailViewController =
5023 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135024 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
5025 [mailViewController setToRecipients:[command toRecipients]];
5026 [mailViewController setSubject:[command subject]];
5027 [mailViewController setMessageBody:[command body] isHTML:NO];
5028
5029 const base::FilePath& textFile = [command textFileToAttach];
5030 if (!textFile.empty()) {
5031 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5032 NSData* data = [NSData dataWithContentsOfFile:filename];
5033 if (data) {
5034 NSString* displayName =
5035 base::SysUTF8ToNSString(textFile.BaseName().value());
5036 [mailViewController addAttachmentData:data
5037 mimeType:@"text/plain"
5038 fileName:displayName];
5039 }
5040 }
5041
5042 [mailViewController setMailComposeDelegate:self];
5043 [self presentViewController:mailViewController animated:YES completion:nil];
5044}
5045
5046#pragma mark - MFMailComposeViewControllerDelegate methods
5047
5048- (void)mailComposeController:(MFMailComposeViewController*)controller
5049 didFinishWithResult:(MFMailComposeResult)result
5050 error:(NSError*)error {
5051 [self dismissViewControllerAnimated:YES completion:nil];
5052}
5053
5054#pragma mark - StoreKitLauncher methods
5055
5056- (void)productViewControllerDidFinish:
5057 (SKStoreProductViewController*)viewController {
5058 [self dismissViewControllerAnimated:YES completion:nil];
5059}
5060
5061- (void)openAppStore:(NSString*)appId {
5062 if (![appId length])
5063 return;
5064 NSDictionary* product =
5065 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275066 SKStoreProductViewController* storeViewController =
5067 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135068 [storeViewController setDelegate:self];
5069 [storeViewController loadProductWithParameters:product completionBlock:nil];
5070 [self presentViewController:storeViewController animated:YES completion:nil];
5071}
5072
5073#pragma mark - TabDialogDelegate methods
5074
sdefresnee65fd872016-12-19 13:38:135075- (void)cancelDialogForTab:(Tab*)tab {
5076 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5077}
5078
5079#pragma mark - FKFeedbackPromptDelegate methods
5080
5081- (void)userTappedRateApp:(UIView*)view {
5082 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275083 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135084}
5085
5086- (void)userTappedSendFeedback:(UIView*)view {
5087 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275088 _rateThisAppDialog = nil;
sczsb8f81c32017-08-29 15:45:495089 [self.dispatcher showReportAnIssue];
sdefresnee65fd872016-12-19 13:38:135090}
5091
5092- (void)userTappedDismiss:(UIView*)view {
5093 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275094 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135095}
5096
5097#pragma mark - VoiceSearchBarDelegate
5098
5099- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275100 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135101 [self ensureVoiceSearchControllerCreated];
5102 return _voiceSearchController->IsTextToSpeechEnabled() &&
5103 _voiceSearchController->IsTextToSpeechSupported();
5104}
5105
5106- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275107 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135108 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5109}
5110
5111#pragma mark - VoiceSearchPresenter
5112
5113- (UIView*)voiceSearchButton {
5114 return _voiceSearchButton;
5115}
5116
5117- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5118 return [self currentLogoAnimationControllerOwner];
5119}
5120
Rohit Rao01e0e002017-08-14 20:49:435121#pragma mark - ActivityService Providers
5122
5123- (void)presentActivityServiceViewController:(UIViewController*)controller {
5124 [self presentViewController:controller animated:YES completion:nil];
5125}
5126
5127- (void)activityServiceDidEndPresenting {
5128 self.presenting = NO;
5129 [self.dialogPresenter tryToPresent];
5130}
5131
Rohit Raocda0a992017-08-16 15:37:115132#pragma mark - QRScanner Requirements
5133
5134- (void)presentQRScannerViewController:(UIViewController*)controller {
5135 [self presentViewController:controller animated:YES completion:nil];
5136}
5137
5138- (void)dismissQRScannerViewController:(UIViewController*)controller
5139 completion:(void (^)(void))completion {
5140 DCHECK_EQ(controller, self.presentedViewController);
5141 [self dismissViewControllerAnimated:YES completion:completion];
5142}
5143
sczsdd860eba2017-08-10 01:55:385144#pragma mark - TabHistoryPresenter
5145
sczs0a726d22017-08-21 22:40:135146- (UIView*)viewForTabHistoryPresentation {
5147 return self.view;
5148}
5149
sczsdd860eba2017-08-10 01:55:385150- (void)prepareForTabHistoryPresentation {
5151 DCHECK(self.visible || self.dismissingModal);
5152 [[self.tabModel currentTab].webController dismissKeyboard];
5153 [_toolbarController cancelOmniboxEdit];
5154}
5155
Mike Doughertya1ec26402017-08-23 19:46:315156#pragma mark - CaptivePortalDetectorTabHelperDelegate
5157
5158- (void)captivePortalBlockingPage:(IOSCaptivePortalBlockingPage*)blockingPage
5159 connectWithLandingURL:(GURL)landingURL {
5160 _captivePortalLoginCoordinator = [[CaptivePortalLoginCoordinator alloc]
5161 initWithBaseViewController:self
5162 landingURL:landingURL];
5163 [_captivePortalLoginCoordinator start];
5164}
5165
sdefresnee65fd872016-12-19 13:38:135166@end