blob: 289c4a699e66eb2f902631dc854cc8daf603ccf0 [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>
sdefresnee65fd872016-12-19 13:38:1310#import <QuartzCore/QuartzCore.h>
11
12#include <stdint.h>
13#include <cmath>
14#include <memory>
15
16#include "base/base64.h"
17#include "base/command_line.h"
mathp9b4c11d2017-07-06 20:24:1318#include "base/feature_list.h"
gambard9efce7a2017-02-09 18:53:1719#include "base/files/file_path.h"
sdefresnee65fd872016-12-19 13:38:1320#include "base/i18n/rtl.h"
21#include "base/ios/block_types.h"
22#include "base/ios/ios_util.h"
sdefresnee65fd872016-12-19 13:38:1323#include "base/logging.h"
24#include "base/mac/bind_objc_block.h"
25#include "base/mac/bundle_locations.h"
26#include "base/mac/foundation_util.h"
sdefresnee65fd872016-12-19 13:38:1327#include "base/macros.h"
28#include "base/memory/ptr_util.h"
asvitkinef1899e32017-01-27 16:30:2929#include "base/metrics/histogram_macros.h"
sdefresnee65fd872016-12-19 13:38:1330#include "base/metrics/user_metrics.h"
31#include "base/metrics/user_metrics_action.h"
sdefresnee65fd872016-12-19 13:38:1332#include "base/strings/sys_string_conversions.h"
rohitraocd324eb72017-04-04 15:36:3933#include "base/strings/utf_string_conversions.h"
Sylvain Defresnefd3ecf22017-07-12 18:47:2434#include "base/task_scheduler/post_task.h"
tzik14236032017-02-15 06:41:0135#include "base/threading/sequenced_worker_pool.h"
sdefresnee65fd872016-12-19 13:38:1336#include "components/bookmarks/browser/base_bookmark_model_observer.h"
37#include "components/bookmarks/browser/bookmark_model.h"
Sylvain Defresne7178d4c2017-09-14 13:22:3738#include "components/favicon/ios/web_favicon_driver.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2339#include "components/feature_engagement/public/event_constants.h"
Cooper Knaake4f495cf2017-07-27 23:30:0340#include "components/feature_engagement/public/feature_constants.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2341#include "components/feature_engagement/public/tracker.h"
gambardbdc07cc2017-02-03 16:43:1142#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
sdefresnee65fd872016-12-19 13:38:1343#include "components/infobars/core/infobar_manager.h"
Mark Coganca30df62017-11-20 14:29:1144#import "components/language/ios/browser/ios_language_detection_tab_helper.h"
mathp9b4c11d2017-07-06 20:24:1345#include "components/payments/core/features.h"
sdefresnee65fd872016-12-19 13:38:1346#include "components/prefs/pref_service.h"
olivierrobin52b6cd6ec2017-03-23 13:55:5447#include "components/reading_list/core/reading_list_model.h"
sdefresnee65fd872016-12-19 13:38:1348#include "components/search_engines/search_engines_pref_names.h"
49#include "components/search_engines/template_url_service.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0550#include "components/sessions/core/session_types.h"
sdefresnee65fd872016-12-19 13:38:1351#include "components/sessions/core/tab_restore_service_helper.h"
edchincd32fdf2017-10-25 12:45:4552#include "components/signin/core/browser/account_reconcilor.h"
53#include "components/signin/core/browser/signin_metrics.h"
54#import "components/signin/ios/browser/account_consistency_service.h"
Eugene Butc90499d52017-09-22 16:02:0955#include "components/signin/ios/browser/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:1356#include "components/strings/grit/components_strings.h"
57#include "components/toolbar/toolbar_model_impl.h"
58#include "ios/chrome/app/tests_hook.h"
59#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
60#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
61#include "ios/chrome/browser/chrome_url_constants.h"
62#include "ios/chrome/browser/chrome_url_util.h"
Eugene But49a7c572017-12-11 20:54:1563#import "ios/chrome/browser/download/pass_kit_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1364#include "ios/chrome/browser/experimental_flags.h"
65#import "ios/chrome/browser/favicon/favicon_loader.h"
66#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2367#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
68#include "ios/chrome/browser/feature_engagement/tracker_util.h"
sdefresnee65fd872016-12-19 13:38:1369#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
70#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3671#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1372#include "ios/chrome/browser/first_run/first_run.h"
73#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
Mark Cogan80aa28d2017-11-30 13:11:3474#include "ios/chrome/browser/infobars/infobar_container_delegate_ios.h"
sdefresnee65fd872016-12-19 13:38:1375#include "ios/chrome/browser/infobars/infobar_container_ios.h"
Mark Cogan80aa28d2017-11-30 13:11:3476#import "ios/chrome/browser/infobars/infobar_container_state_delegate.h"
sdefresnee65fd872016-12-19 13:38:1377#include "ios/chrome/browser/infobars/infobar_container_view.h"
Rohit Raoaf46af92017-08-10 12:52:3078#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
Mark Coganca30df62017-11-20 14:29:1179#import "ios/chrome/browser/language/url_language_histogram_factory.h"
sdefresnee65fd872016-12-19 13:38:1380#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
Mark Coganfc591c8c2018-01-02 16:07:0081#import "ios/chrome/browser/metrics/size_class_recorder.h"
sdefresnee65fd872016-12-19 13:38:1382#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
sdefresnee65fd872016-12-19 13:38:1383#import "ios/chrome/browser/passwords/password_controller.h"
Tomasz Garbusb844e992017-09-29 12:44:5584#include "ios/chrome/browser/passwords/password_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1385#include "ios/chrome/browser/pref_names.h"
Rohit Rao44f204302017-08-10 14:49:5486#import "ios/chrome/browser/prerender/preload_controller_delegate.h"
87#import "ios/chrome/browser/prerender/prerender_service.h"
88#import "ios/chrome/browser/prerender/prerender_service_factory.h"
olivierrobin013ba672017-03-01 21:16:2489#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1390#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
91#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
92#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0593#include "ios/chrome/browser/sessions/session_util.h"
sdefresnee65fd872016-12-19 13:38:1394#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
95#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
edchincd32fdf2017-10-25 12:45:4596#import "ios/chrome/browser/signin/account_consistency_service_factory.h"
97#include "ios/chrome/browser/signin/account_reconcilor_factory.h"
sdefresnee65fd872016-12-19 13:38:1398#import "ios/chrome/browser/snapshots/snapshot_cache.h"
99#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
100#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
Sylvain Defresne17b8aa42017-12-21 16:17:17101#import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
Mike Dougherty4620cf8e2017-10-31 23:37:09102#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
103#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper_delegate.h"
pkld6e73e52017-03-08 15:56:51104#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:28105#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13106#import "ios/chrome/browser/tabs/tab.h"
107#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:19108#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:13109#import "ios/chrome/browser/tabs/tab_model.h"
110#import "ios/chrome/browser/tabs/tab_model_observer.h"
Sylvain Defresne72c530e42017-08-25 15:28:16111#import "ios/chrome/browser/tabs/tab_private.h"
sdefresnee65fd872016-12-19 13:38:13112#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
Mark Coganca30df62017-11-20 14:29:11113#import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
114#import "ios/chrome/browser/translate/language_selection_handler.h"
Rohit Rao01e0e002017-08-14 20:49:43115#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
116#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
sdefresnee65fd872016-12-19 13:38:13117#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
Eugene But35ded552017-09-13 23:31:59118#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13119#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
120#import "ios/chrome/browser/ui/background_generator.h"
121#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
Gauthier Ambard65e949b092017-11-29 08:46:20122#include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
sdefresnee65fd872016-12-19 13:38:13123#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13124#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
Gauthier Ambard522668e2018-01-03 14:10:07125#import "ios/chrome/browser/ui/bubble/bubble_util.h"
Cooper Knaak33f9f402017-08-09 18:04:38126#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
sdefresnee65fd872016-12-19 13:38:13127#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
Mark Cogan5e3da152017-07-11 15:57:30128#import "ios/chrome/browser/ui/commands/application_commands.h"
Mark Cogan6c58ea92017-07-06 13:08:24129#import "ios/chrome/browser/ui/commands/browser_commands.h"
edchin9badb062017-08-16 18:47:54130#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
Mark Cogandfcdea72017-07-18 13:47:38131#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13132#import "ios/chrome/browser/ui/commands/open_url_command.h"
133#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
edchin95c927072017-11-04 00:35:07134#import "ios/chrome/browser/ui/commands/show_signin_command.h"
edchin7f210cd2017-09-28 08:03:53135#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00136#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23137#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13138#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13139#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
140#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
Eugene But87a09532017-12-08 20:02:05141#import "ios/chrome/browser/ui/download/legacy_download_manager_controller.h"
Eugene But49a7c572017-12-11 20:54:15142#import "ios/chrome/browser/ui/download/pass_kit_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13143#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
144#import "ios/chrome/browser/ui/external_file_controller.h"
Louis Romerod11747a2017-10-20 20:10:35145#import "ios/chrome/browser/ui/external_search/external_search_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13146#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
147#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19148#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
149#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_factory.h"
Kurt Horimoto803840622017-10-28 01:20:37150#import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
Kurt Horimoto06b94252017-12-08 19:45:59151#import "ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.h"
152#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h"
153#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h"
Kurt Horimoto62e97c72017-11-03 19:51:47154#import "ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38155#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13156#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
Gauthier Ambard929699412018-01-02 10:05:41157#import "ios/chrome/browser/ui/image_util/image_saver.h"
sdefresnee65fd872016-12-19 13:38:13158#import "ios/chrome/browser/ui/key_commands_provider.h"
Kurt Horimoto1945ef42017-10-26 03:57:26159#import "ios/chrome/browser/ui/location_bar_notification_names.h"
Rohit Rao9a8ad772017-10-30 22:35:59160#import "ios/chrome/browser/ui/main/main_feature_flags.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19161#import "ios/chrome/browser/ui/main_content/main_content_ui.h"
162#import "ios/chrome/browser/ui/main_content/main_content_ui_broadcasting_util.h"
163#import "ios/chrome/browser/ui/main_content/main_content_ui_state.h"
164#import "ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10165#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13166#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42167#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13168#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27169#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
170#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13171#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37172#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
Mark Coganca30df62017-11-20 14:29:11173#import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
sdefresnee65fd872016-12-19 13:38:13174#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11175#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
176#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13177#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03178#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13179#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13180#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53181#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
sczs40443972017-09-13 19:02:39182#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13183#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
edchin9e7a1112017-11-07 18:28:03184#import "ios/chrome/browser/ui/signin_interaction/public/signin_presenter.h"
edchin7f210cd2017-09-28 08:03:53185#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13186#import "ios/chrome/browser/ui/stack_view/card_view.h"
187#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
188#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13189#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03190#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
191#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
192#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
Gauthier Ambard76d826ac2017-11-16 16:27:34193#include "ios/chrome/browser/ui/toolbar/legacy_toolbar_coordinator.h"
Kurt Horimotoea429dd2017-11-28 02:24:30194#import "ios/chrome/browser/ui/toolbar/legacy_toolbar_ui_updater.h"
Gauthier Ambard83207452018-01-04 07:51:39195#import "ios/chrome/browser/ui/toolbar/public/primary_toolbar_coordinator.h"
Gauthier Ambard522668e2018-01-03 14:10:07196#import "ios/chrome/browser/ui/toolbar/public/toolbar_controller_base_feature.h"
sdefresnee65fd872016-12-19 13:38:13197#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
198#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
Gauthier Ambard29939db12017-10-30 16:47:31199#import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
Kurt Horimotoea429dd2017-11-28 02:24:30200#import "ios/chrome/browser/ui/toolbar/toolbar_ui.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19201#import "ios/chrome/browser/ui/toolbar/toolbar_ui_broadcasting_util.h"
sczsc2b4f152017-10-11 01:44:24202#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
Peter Laurense0b80f12017-11-21 07:52:40203#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_configuration_provider.h"
204#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_presentation_provider.h"
sczsbbad1632017-07-29 03:48:00205#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13206#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
Mark Coganca30df62017-11-20 14:29:11207#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13208#include "ios/chrome/browser/ui/ui_util.h"
209#import "ios/chrome/browser/ui/uikit_ui_util.h"
Gauthier Ambard087e3572017-12-20 12:54:47210#import "ios/chrome/browser/ui/util/named_guide.h"
gambard6a138362017-02-06 17:19:28211#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13212#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
213#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51214#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13215#import "ios/chrome/browser/web/error_page_content.h"
Danyao Wang85389a82017-10-25 18:56:27216#import "ios/chrome/browser/web/load_timing_tab_helper.h"
Sylvain Defresne448351332017-12-27 10:38:36217#import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13218#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04219#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05220#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59221#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53222#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04223#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05224#import "ios/chrome/browser/web_state_list/web_state_list.h"
225#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57226#import "ios/chrome/browser/webui/net_export_tab_helper.h"
227#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
228#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13229#include "ios/chrome/grit/ios_chromium_strings.h"
230#include "ios/chrome/grit/ios_strings.h"
231#import "ios/net/request_tracker.h"
232#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
233#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
234#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
235#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
236#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
237#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
238#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
239#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
edchineeb4d422017-10-02 17:39:36240#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
sdefresnee65fd872016-12-19 13:38:13241#include "ios/web/public/navigation_item.h"
242#import "ios/web/public/navigation_manager.h"
243#include "ios/web/public/referrer_util.h"
244#include "ios/web/public/ssl_status.h"
245#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39246#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13247#include "ios/web/public/web_client.h"
248#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13249#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29250#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
Sylvain Defresnee7f2c8a2017-10-17 02:39:19251#import "ios/web/public/web_state/web_state.h"
sdefresnee65fd872016-12-19 13:38:13252#import "ios/web/public/web_state/web_state_delegate_bridge.h"
253#include "ios/web/public/web_thread.h"
254#import "ios/web/web_state/ui/crw_web_controller.h"
255#import "net/base/mac/url_conversions.h"
256#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
257#include "net/ssl/ssl_info.h"
258#include "net/url_request/url_request_context_getter.h"
259#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
260#include "ui/base/l10n/l10n_util.h"
261#include "ui/base/l10n/l10n_util_mac.h"
262#include "ui/base/page_transition_types.h"
263#include "url/gurl.h"
264
stkhapuginf58b10d02017-04-10 13:36:17265#if !defined(__has_feature) || !__has_feature(objc_arc)
266#error "This file requires ARC support."
267#endif
268
sdefresnee65fd872016-12-19 13:38:13269using base::UserMetricsAction;
270using bookmarks::BookmarkNode;
271
sdefresnee65fd872016-12-19 13:38:13272class InfoBarContainerDelegateIOS;
273
sdefresnee65fd872016-12-19 13:38:13274namespace {
275
276typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
277 // Note: these values must match the ContextMenuOption enum in histograms.xml.
278 ACTION_OPEN_IN_NEW_TAB = 0,
279 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
280 ACTION_COPY_LINK_ADDRESS = 2,
281 ACTION_SAVE_IMAGE = 6,
282 ACTION_OPEN_IMAGE = 7,
283 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
284 ACTION_SEARCH_BY_IMAGE = 11,
285 ACTION_OPEN_JAVASCRIPT = 21,
286 ACTION_READ_LATER = 22,
287 NUM_ACTIONS = 23,
288};
289
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28290void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13291 if (is_image) {
292 if (is_link) {
293 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
294 NUM_ACTIONS);
295 } else {
296 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
297 NUM_ACTIONS);
298 }
299 } else {
300 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
301 NUM_ACTIONS);
302 }
303}
304
edchinf5150c682017-09-18 02:50:03305// Returns the status bar background color.
306UIColor* StatusBarBackgroundColor() {
307 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
308}
309
310// Duration of the toolbar animation.
Kurt Horimoto62e97c72017-11-03 19:51:47311const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3;
edchinf5150c682017-09-18 02:50:03312
sdefresnee65fd872016-12-19 13:38:13313const CGFloat kVoiceSearchBarHeight = 59.0;
314
315// Dimensions to use when downsizing an image for search-by-image.
316const CGFloat kSearchByImageMaxImageArea = 90000.0;
317const CGFloat kSearchByImageMaxImageWidth = 600.0;
318const CGFloat kSearchByImageMaxImageHeight = 400.0;
319
sdefresnee65fd872016-12-19 13:38:13320enum HeaderBehaviour {
321 // The header moves completely out of the screen.
322 Hideable = 0,
323 // This header stays on screen and doesn't overlap with the content.
324 Visible,
325 // This header stay on screen and covers part of the content.
326 Overlap
327};
328
sdefresnee65fd872016-12-19 13:38:13329const CGFloat kIPadFindBarOverlap = 11;
330
331bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24332 // Most URLs are allowed in incognito; the following is an exception.
333 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13334}
335
edchineeb4d422017-10-02 17:39:36336// Snackbar category for browser view controller.
337NSString* const kBrowserViewControllerSnackbarCategory =
338 @"BrowserViewControllerSnackbarCategory";
339
rohitrao005a6432017-03-16 20:52:42340} // namespace
sdefresnee65fd872016-12-19 13:38:13341
stkhapugin952ecef2017-04-11 12:11:45342#pragma mark - HeaderDefinition helper
343
344@interface HeaderDefinition : NSObject
345
346// The header view.
347@property(nonatomic, strong) UIView* view;
348// How to place the view, and its behaviour when the headers move.
349@property(nonatomic, assign) HeaderBehaviour behaviour;
350// Reduces the height of a header to adjust for shadows.
351@property(nonatomic, assign) CGFloat heightAdjustement;
352// Nudges that particular header up by this number of points.
353@property(nonatomic, assign) CGFloat inset;
354
355- (instancetype)initWithView:(UIView*)view
356 headerBehaviour:(HeaderBehaviour)behaviour
357 heightAdjustment:(CGFloat)heightAdjustment
358 inset:(CGFloat)inset;
359
360+ (instancetype)definitionWithView:(UIView*)view
361 headerBehaviour:(HeaderBehaviour)behaviour
362 heightAdjustment:(CGFloat)heightAdjustment
363 inset:(CGFloat)inset;
364
365@end
366
367@implementation HeaderDefinition
368@synthesize view = _view;
369@synthesize behaviour = _behaviour;
370@synthesize heightAdjustement = _heightAdjustement;
371@synthesize inset = _inset;
372
373+ (instancetype)definitionWithView:(UIView*)view
374 headerBehaviour:(HeaderBehaviour)behaviour
375 heightAdjustment:(CGFloat)heightAdjustment
376 inset:(CGFloat)inset {
377 return [[self alloc] initWithView:view
378 headerBehaviour:behaviour
379 heightAdjustment:heightAdjustment
380 inset:inset];
381}
382
383- (instancetype)initWithView:(UIView*)view
384 headerBehaviour:(HeaderBehaviour)behaviour
385 heightAdjustment:(CGFloat)heightAdjustment
386 inset:(CGFloat)inset {
387 self = [super init];
388 if (self) {
389 _view = view;
390 _behaviour = behaviour;
391 _heightAdjustement = heightAdjustment;
392 _inset = inset;
393 }
394 return self;
395}
396
397@end
398
399#pragma mark - BVC
400
Rohit Rao01e0e002017-08-14 20:49:43401@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43402 AppRatingPromptDelegate,
Gauthier Ambard65e949b092017-11-29 08:46:20403 BookmarkModelBridgeObserver,
Mike Dougherty4620cf8e2017-10-31 23:37:09404 CaptivePortalDetectorTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13405 CRWNativeContentProvider,
406 CRWWebStateDelegate,
407 DialogPresenterDelegate,
Kurt Horimoto06b94252017-12-08 19:45:59408 FullscreenUIElement,
Kurt Horimoto62e97c72017-11-03 19:51:47409 LegacyFullscreenControllerDelegate,
Mark Cogan80aa28d2017-11-30 13:11:34410 InfobarContainerStateDelegate,
sdefresnee65fd872016-12-19 13:38:13411 KeyCommandsPlumbing,
Kurt Horimotoe9b6002c2017-12-04 23:19:19412 MainContentUI,
edchincd32fdf2017-10-25 12:45:45413 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13414 MFMailComposeViewControllerDelegate,
Mark Cogan849244ee2017-12-29 15:57:19415 NetExportTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13416 NewTabPageControllerObserver,
417 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27418 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13419 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55420 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13421 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11422 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59423 RepostFormTabHelperDelegate,
Gauthier Ambard29939db12017-10-30 16:47:31424 SideSwipeControllerDelegate,
Mark Cogan849244ee2017-12-29 15:57:19425 SigninPresenter,
sdefresnee65fd872016-12-19 13:38:13426 SKStoreProductViewControllerDelegate,
427 SnapshotOverlayProvider,
428 StoreKitLauncher,
429 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19430 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38431 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13432 TabModelObserver,
433 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03434 TabStripPresentation,
Peter Laurense0b80f12017-11-21 07:52:40435 ToolsMenuConfigurationProvider,
sdefresnee65fd872016-12-19 13:38:13436 UIGestureRecognizerDelegate,
Mark Cogan849244ee2017-12-29 15:57:19437 UpgradeCenterClient,
sdefresnee65fd872016-12-19 13:38:13438 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04439 VoiceSearchBarOwner,
440 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13441 // The dependency factory passed on initialization. Used to vend objects used
442 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27443 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13444
445 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27446 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13447
sczsf1620e52017-10-02 22:54:46448 // Facade objects used by |_toolbarCoordinator|.
449 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13450 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
451 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
452
sdefresnee65fd872016-12-19 13:38:13453 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27454 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13455
456 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27457 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13458
459 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27460 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13461
462 // Handles presentation of JavaScript dialogs.
463 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
464
justincohen75011c32017-04-28 16:31:39465 // Handles command dispatching.
466 CommandDispatcher* _dispatcher;
467
sdefresnee65fd872016-12-19 13:38:13468 // Keyboard commands provider. It offloads most of the keyboard commands
469 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27470 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13471
sdefresnee65fd872016-12-19 13:38:13472 // Used to inject Javascript implementing the PaymentRequest API and to
473 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27474 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13475
sdefresnee65fd872016-12-19 13:38:13476 // Used to display the Voice Search UI. Nil if not visible.
477 scoped_refptr<VoiceSearchController> _voiceSearchController;
478
gambard6299cc1d2017-02-21 13:06:03479 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27480 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03481
sdefresnee65fd872016-12-19 13:38:13482 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27483 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13484
sdefresnee65fd872016-12-19 13:38:13485 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27486 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13487
sdefresnee65fd872016-12-19 13:38:13488 // Adapter to let BVC be the delegate for WebState.
489 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
490
491 // YES if new tab is animating in.
492 BOOL _inNewTabAnimation;
493
494 // YES if Voice Search should be started when the new tab animation is
495 // finished.
496 BOOL _startVoiceSearchAfterNewTabAnimation;
497
498 // YES if the user interacts with the location bar.
499 BOOL _locationBarHasFocus;
500 // YES if a load was cancelled due to typing in the location bar.
501 BOOL _locationBarEditCancelledLoad;
502 // YES if waiting for a foreground tab due to expectNewForegroundTab.
503 BOOL _expectingForegroundTab;
504
Sylvain Defresne41170aa2017-06-15 10:25:20505 // Whether or not -shutdown has been called.
506 BOOL _isShutdown;
507
sdefresnee65fd872016-12-19 13:38:13508 // The ChromeBrowserState associated with this BVC.
509 ios::ChromeBrowserState* _browserState; // weak
510
511 // Whether or not Incognito* is enabled.
512 BOOL _isOffTheRecord;
513
514 // The last point within |_contentArea| that's received a touch.
515 CGPoint _lastTapPoint;
516
517 // The time at which |_lastTapPoint| was most recently set.
518 CFTimeInterval _lastTapTime;
519
520 // A single infobar container handles all infobars in all tabs. It keeps
521 // track of infobars for current tab (accessed via infobar helper of
522 // the current tab).
523 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
524
525 // Bridge class to deliver container change notifications to BVC.
526 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
527
528 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37529 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27530 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13531
532 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11533 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13534
sdefresnee65fd872016-12-19 13:38:13535 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27536 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13537
538 // Bridge to register for bookmark changes.
Gauthier Ambard65e949b092017-11-29 08:46:20539 std::unique_ptr<bookmarks::BookmarkModelBridge> _bookmarkModelBridge;
sdefresnee65fd872016-12-19 13:38:13540
541 // Cached pointer to the bookmarks model.
542 bookmarks::BookmarkModel* _bookmarkModel; // weak
543
544 // The controller that shows the bookmarking UI after the user taps the star
545 // button.
stkhapuginc9eee7b2017-04-10 15:49:27546 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13547
sdefresnee65fd872016-12-19 13:38:13548 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27549 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13550
Eugene But56efc322017-08-11 14:03:44551 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28552 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13553
554 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27555 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13556
Jean-François Geyelin3d47c212017-08-03 09:24:09557 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27558 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13559
Rohit Rao01e0e002017-08-14 20:49:43560 // Coordinator for the share menu (Activity Services).
561 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
562
sdefresnee65fd872016-12-19 13:38:13563 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27564 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38565
Rohit Raocda0a992017-08-16 15:37:11566 // Coordinator for the QR scanner.
567 QRScannerLegacyCoordinator* _qrScannerCoordinator;
568
sczsdd860eba2017-08-10 01:55:38569 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13570 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53571
572 // Coordinator for displaying Sad Tab.
573 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27574
575 // Coordinator for Page Info UI.
576 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59577
578 // Coordinator for displaying Repost Form dialog.
579 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22580
edchin7f210cd2017-09-28 08:03:53581 // Coordinator for displaying snackbars.
582 SnackbarCoordinator* _snackbarCoordinator;
583
sczsf1620e52017-10-02 22:54:46584 // Coordinator for the toolbar.
585 LegacyToolbarCoordinator* _toolbarCoordinator;
586
Kurt Horimotoea429dd2017-11-28 02:24:30587 // The toolbar UI updater for the toolbar managed by |_toolbarCoordinator|.
588 LegacyToolbarUIUpdater* _toolbarUIUpdater;
589
Kurt Horimotoe9b6002c2017-12-04 23:19:19590 // The main content UI updater for the content displayed by this BVC.
591 MainContentUIStateUpdater* _mainContentUIUpdater;
592
593 // The forwarder for web scroll view interation events.
594 WebScrollViewMainContentUIForwarder* _webMainContentUIForwarder;
595
Kurt Horimoto06b94252017-12-08 19:45:59596 // The updater that adjusts the toolbar's layout for fullscreen events.
597 std::unique_ptr<FullscreenUIUpdater> _fullscreenUIUpdater;
598
Louis Romerod11747a2017-10-20 20:10:35599 // Coordinator for the External Search UI.
600 ExternalSearchCoordinator* _externalSearchCoordinator;
601
Mark Coganca30df62017-11-20 14:29:11602 // Coordinator for the language selection UI.
603 LanguageSelectionCoordinator* _languageSelectionCoordinator;
604
Eugene But49a7c572017-12-11 20:54:15605 // Coordinator for the PassKit UI presentation.
606 PassKitCoordinator* _passKitCoordinator;
607
Justin Cohenb3170c32017-09-19 01:55:22608 // Fake status bar view used to blend the toolbar into the status bar.
609 UIView* _fakeStatusBarView;
Sylvain Defresnef5d2d952017-11-14 11:15:31610
611 // Stores whether the Tab currently inserted was a pre-rendered Tab. This
612 // is used to determine whether the pre-rendering animation should be played
613 // or not.
614 BOOL _insertedTabWasPrerenderedTab;
sdefresnee65fd872016-12-19 13:38:13615}
616
617// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17618@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13619// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17620@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13621// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17622@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13623// Whether the current tab can enable the request desktop menu item.
624@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
625// Whether the sharing menu should be enabled.
626@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
627// Helper method to check web controller canShowFindBar method.
628@property(nonatomic, assign, readonly) BOOL canShowFindBar;
629// Whether the controller's view is currently available.
630// YES from viewWillAppear to viewWillDisappear.
631@property(nonatomic, assign, getter=isVisible) BOOL visible;
632// Whether the controller's view is currently visible.
633// YES from viewDidAppear to viewWillDisappear.
634@property(nonatomic, assign) BOOL viewVisible;
Kurt Horimotoe9b6002c2017-12-04 23:19:19635// Whether the controller should broadcast its UI.
636@property(nonatomic, assign, getter=isBroadcasting) BOOL broadcasting;
sdefresnee65fd872016-12-19 13:38:13637// Whether the controller is currently dismissing a presented view controller.
638@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
639// Returns YES if the toolbar has not been scrolled out by fullscreen.
640@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
641 BOOL toolbarOnScreen;
642// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12643@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13644// Whether BVC prefers to hide the status bar. This value is used to determine
645// the response from the |prefersStatusBarHidden| method.
646@property(nonatomic, assign) BOOL hideStatusBar;
647// Whether the VoiceSearchBar should be displayed.
648@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
649// Coordinator for displaying a modal overlay with activity indicator to prevent
650// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17651@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13652 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39653// A block to be run when the |tabWasAdded:| method completes the animation
654// for the presentation of a new tab. Can be used to record performance metrics.
655@property(nonatomic, strong, nullable)
656 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42657// Coordinator for Recent Tabs.
658@property(nonatomic, strong)
659 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03660// Coordinator for tablet tab strip.
661@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
662// A weak reference to the view of the tab strip on tablet.
663@property(nonatomic, weak) UIView* tabStripView;
Gauthier Ambard929699412018-01-02 10:05:41664// Helper for saving images.
665@property(nonatomic, strong) ImageSaver* imageSaver;
sdefresnee65fd872016-12-19 13:38:13666
Gauthier Ambard929699412018-01-02 10:05:41667// The user agent type used to load the currently visible page. User agent
668// type is NONE if there is no visible page or visible page is a native
669// page.
liaoyukeea9f3ee62017-03-07 22:05:39670@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
671
stkhapugin952ecef2017-04-11 12:11:45672// Returns the header views, all the chrome on top of the page, including the
673// ones that cannot be scrolled off screen by full screen.
674@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
675
Cooper Knaakd0a974cd2017-08-10 18:05:47676// Used to display the new tab tip in-product help promotion bubble. |nil| if
677// the new tab tip bubble has not yet been presented. Once the bubble is
678// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38679@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47680 BubbleViewControllerPresenter* tabTipBubblePresenter;
Helen Yang9175bd52017-08-12 00:28:40681// Used to display the new incognito tab tip in-product help promotion bubble.
682@property(nonatomic, strong)
683 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
684
Gauthier Ambard83207452018-01-04 07:51:39685// Primary toolbar.
686@property(nonatomic, readonly) id<PrimaryToolbarCoordinator>
687 primaryToolbarCoordinator;
688// TODO(crbug.com/788705): Removes this property and associated calls.
689// Returns the LegacyToolbarCoordinator. This property is here to separate
690// methods which will be removed during cleanup to other methods. Uses this
691// property only for deprecated methods.
Gauthier Ambardc4d04f12018-01-04 16:58:24692@property(nonatomic, readonly) id<LegacyToolbarCoordinator>
693 legacyToolbarCoordinator;
Gauthier Ambard83207452018-01-04 07:51:39694
Justin Cohen9fe9ef672017-12-01 20:37:43695// Vertical offset for fullscreen toolbar.
Gauthier Ambard83207452018-01-04 07:51:39696@property(nonatomic, strong) NSLayoutConstraint* primaryToolbarOffsetConstraint;
Mark Cogan849244ee2017-12-29 15:57:19697// Y-dimension offset for placement of the header.
698@property(nonatomic, readonly) CGFloat headerOffset;
Mark Cogan849244ee2017-12-29 15:57:19699// Height of the header view for the tab model's current tab.
700@property(nonatomic, readonly) CGFloat headerHeight;
701
Mark Cogan776e0282018-01-02 09:00:06702// The webState of the active tab.
703@property(nonatomic, readonly) web::WebState* currentWebState;
sdefresnee65fd872016-12-19 13:38:13704
Mark Cogan776e0282018-01-02 09:00:06705// BVC initialization
706// ------------------
707// If the BVC is initialized with a valid browser state & tab model immediately,
708// the path is straightforward: functionality is enabled, and the UI is built
709// when -viewDidLoad is called.
710// If the BVC is initialized without a browser state or tab model, the tab model
711// and browser state may or may not be provided before -viewDidLoad is called.
712// In most cases, they will not, to improve startup performance.
713// In order to handle this, initialization of various aspects of BVC have been
714// broken out into the following functions, which have expectations (enforced
715// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
716
sdefresnee65fd872016-12-19 13:38:13717// Updates non-view-related functionality with the given browser state and tab
718// model.
719// Does not matter whether or not the view has been loaded.
720- (void)updateWithTabModel:(TabModel*)model
721 browserState:(ios::ChromeBrowserState*)browserState;
722// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
723// the status bar to mimic this layout.
724- (void)installFakeStatusBar;
725// Builds the UI parts of tab strip and the toolbar. Does not matter whether
726// or not browser state and tab model are valid.
727- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50728// Sets up the constraints on the toolbar.
729- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13730// Updates view-related functionality with the given tab model and browser
731// state. The view must have been loaded. Uses |_browserState| and |_model|.
732- (void)addUIFunctionalityForModelAndBrowserState;
Justin Cohen4eeada32017-11-13 18:21:28733// Sets the correct frame and hierarchy for subviews and helper views. Only
734// insert views on |initialLayout|.
735- (void)setUpViewLayout:(BOOL)initialLayout;
sdefresnee65fd872016-12-19 13:38:13736// Makes |tab| the currently visible tab, displaying its view. Calls
737// -selectedTabChanged on the toolbar only if |newSelection| is YES.
738- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
739// Initializes the bookmark interaction controller if not already initialized.
740- (void)initializeBookmarkInteractionController;
Mark Cogan776e0282018-01-02 09:00:06741// Installs the BVC as overscroll actions controller of |nativeContent| if
742// needed. Sets the style of the overscroll actions toolbar.
743- (void)setOverScrollActionControllerToStaticNativeContent:
744 (StaticHtmlNativeContent*)nativeContent;
745
746// UI Configuration, update and Layout
747// -----------------------------------
sdefresnee65fd872016-12-19 13:38:13748// Updates the toolbar display based on the current tab.
749- (void)updateToolbar;
Kurt Horimotoe9b6002c2017-12-04 23:19:19750// Starts or stops broadcasting the toolbar UI and main content UI depending on
751// whether the BVC is visible and active.
752- (void)updateBroadcastState;
sdefresnee65fd872016-12-19 13:38:13753// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12754// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13755- (void)updateDialogPresenterActiveState;
756// Dismisses popups and modal dialogs that are displayed above the BVC upon size
757// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
758// is performed.
759// TODO(crbug.com/522721): Support size changes for all popups and modal
760// dialogs.
761- (void)dismissPopups;
Mark Cogan776e0282018-01-02 09:00:06762// Returns whether |tab| is scrolled to the top.
763- (BOOL)isTabScrolledToTop:(Tab*)tab;
764// Returns the footer view if one exists (e.g. the voice search bar).
765- (UIView*)footerView;
766// Returns the header height needed for |tab|.
767- (CGFloat)headerHeightForTab:(Tab*)tab;
768// Sets the frame for the headers.
769- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
770 atOffset:(CGFloat)headerOffset;
771// Adds a CardView on top of the contentArea either taking the size of the full
772// screen or just the size of the space under the header.
773// Returns the CardView that was added.
774- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
Cooper Knaakd0a974cd2017-08-10 18:05:47775
Mark Cogan776e0282018-01-02 09:00:06776// Showing and Dismissing child UI
777// -------------------------------
778// Show the bookmarks page.
779- (void)showAllBookmarks;
780// Shows a panel within the New Tab Page.
781- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
782// Dismisses the "rate this app" dialog.
783- (void)dismissRateThisAppDialog;
784
785// Bubble Views
786// ------------
Cooper Knaakd0a974cd2017-08-10 18:05:47787// Returns a bubble associated with an in-product help promotion if
788// it is valid to show the promotion and |nil| otherwise. |feature| is the
789// base::Feature object associated with the given promotion. |direction| is the
790// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21791// arrow on the button. |text| is the text displayed by the bubble. This method
792// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47793- (BubbleViewControllerPresenter*)
794bubblePresenterForFeature:(const base::Feature&)feature
795 direction:(BubbleArrowDirection)direction
796 alignment:(BubbleAlignment)alignment
797 text:(NSString*)text;
798
Cooper Knaak120cee5e2017-08-10 20:57:00799// Waits to present a bubble associated with the new tab tip in-product help
800// promotion until the feature engagement tracker database is fully initialized.
801// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
802// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11803// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21804// determines it is not valid to present it. This method requires that
805// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00806- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11807// Optionally presents a bubble associated with the new tab tip in-product help
808// promotion. If the feature engagement tracker determines it is valid to show
809// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
810// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21811// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
812// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00813- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40814// Waits to present a bubble associated with the new incognito tab tip
815// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21816// fully initialized. This method requires that |self.browserState| is
817// not NULL.
Helen Yang9175bd52017-08-12 00:28:40818- (void)presentNewIncognitoTabTipBubbleOnInitialized;
819// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21820// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40821- (void)presentNewIncognitoTabTipBubble;
Cooper Knaak120cee5e2017-08-10 20:57:00822
Mark Cogan776e0282018-01-02 09:00:06823// Find Bar UI
824// -----------
sdefresnee65fd872016-12-19 13:38:13825// Update find bar with model data. If |shouldFocus| is set to YES, the text
826// field will become first responder.
827- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13828// Hide find bar.
829- (void)hideFindBarWithAnimation:(BOOL)animate;
830// Shows find bar. If |selectText| is YES, all text inside the Find Bar
831// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
832// set to be first responder.
833- (void)showFindBarWithAnimation:(BOOL)animate
834 selectText:(BOOL)selectText
835 shouldFocus:(BOOL)shouldFocus;
Mark Cogan776e0282018-01-02 09:00:06836// Redisplays the find bar if necessary furing a view controller size change,
837// using the transition coordinator |coordinator|.
838- (void)reshowFindBarIfNeededWithCoordinator:
839 (id<UIViewControllerTransitionCoordinator>)coordinator;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36840
Mark Cogan776e0282018-01-02 09:00:06841// Alerts
842// ------
843// Shows a self-dismissing snackbar displaying |message|.
844- (void)showSnackbar:(NSString*)message;
845// Shows an alert dialog with |title| and |message|.
846- (void)showErrorAlertWithStringTitle:(NSString*)title
847 message:(NSString*)message;
848
849// Tap Handling
850// ------------
851// Record the last tap point based on the |originPoint| (if any) passed in
852// |command|.
853- (void)setLastTapPoint:(OpenNewTabCommand*)command;
854// Returns the last stored |_lastTapPoint| if it's been set within the past
855// second.
856- (CGPoint)lastTapPoint;
857// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
858- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
859
860// Tab creation and selection
861// --------------------------
sdefresnee65fd872016-12-19 13:38:13862// Called when either a tab finishes loading or when a tab with finished content
863// is added directly to the model via pre-rendering. The tab must be non-nil and
864// must be a member of the tab model controlled by this BrowserViewController.
865- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
Mark Cogan776e0282018-01-02 09:00:06866// Adds a new tab with |url| and |postData| at the end of the model, and make it
867// the selected tab and return it.
868- (Tab*)addSelectedTabWithURL:(const GURL&)url
869 postData:(TemplateURLRef::PostContent*)postData
870 transition:(ui::PageTransition)transition;
871// Internal method that all of the similar public and private methods call.
872// Adds a new tab with |url| and |postData| (if not null) at |position| in the
873// tab model (or at the end if |position is NSNotFound|, with |transition| as
874// the page transition type. If |tabAddedCompletion| is nonnull, it's called
875// synchronously after the tab is added.
876- (Tab*)addSelectedTabWithURL:(const GURL&)url
877 postData:(TemplateURLRef::PostContent*)postData
878 atIndex:(NSUInteger)position
879 transition:(ui::PageTransition)transition
880 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion;
881// Whether the given tab's URL is an application specific URL.
882- (BOOL)isTabNativePage:(Tab*)tab;
883// Returns the view to use when animating a page in or out, positioning it to
884// fill the content area but not actually adding it to the view hierarchy.
885- (UIImageView*)pageOpenCloseAnimationView;
886// Add all delegates to the provided |tab|.
887- (void)installDelegatesForTab:(Tab*)tab;
888// Remove delegates from the provided |tab|.
889- (void)uninstallDelegatesForTab:(Tab*)tab;
890// Called when a tab is selected in the model. Make any required view changes.
891// The notification will not be sent when the tab is already the selected tab.
892// |notifyToolbar| indicates whether the toolbar is notified that the tab has
893// changed.
894- (void)tabSelected:(Tab*)tab notifyToolbar:(BOOL)notifyToolbar;
895// Returns the native controller being used by |tab|'s web controller.
896- (id)nativeControllerForTab:(Tab*)tab;
897
Mark Cogan776e0282018-01-02 09:00:06898// Voice Search
899// ------------
sdefresnee65fd872016-12-19 13:38:13900// Lazily instantiates |_voiceSearchController|.
901- (void)ensureVoiceSearchControllerCreated;
902// Lazily instantiates |_voiceSearchBar| and adds it to the view.
903- (void)ensureVoiceSearchBarCreated;
904// Shows/hides the voice search bar.
905- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
sdefresnee65fd872016-12-19 13:38:13906
Mark Cogan776e0282018-01-02 09:00:06907// Reading List
908// ------------
sdefresnee65fd872016-12-19 13:38:13909// Adds the given url to the reading list.
910- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
Mark Cogan776e0282018-01-02 09:00:06911
sdefresnee65fd872016-12-19 13:38:13912@end
913
sdefresnee65fd872016-12-19 13:38:13914@implementation BrowserViewController
Mark Cogan5bd86ba2017-12-28 14:32:38915// Public synthesized propeties.
sdefresnee65fd872016-12-19 13:38:13916@synthesize contentArea = _contentArea;
917@synthesize typingShield = _typingShield;
918@synthesize active = _active;
Mark Cogan5bd86ba2017-12-28 14:32:38919// Private synthesized properties
sdefresnee65fd872016-12-19 13:38:13920@synthesize visible = _visible;
921@synthesize viewVisible = _viewVisible;
Kurt Horimotoe9b6002c2017-12-04 23:19:19922@synthesize broadcasting = _broadcasting;
sdefresnee65fd872016-12-19 13:38:13923@synthesize dismissingModal = _dismissingModal;
924@synthesize hideStatusBar = _hideStatusBar;
925@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39926@synthesize foregroundTabWasAddedCompletionBlock =
927 _foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42928@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03929@synthesize tabStripCoordinator = _tabStripCoordinator;
930@synthesize tabStripView = _tabStripView;
Mark Cogan5bd86ba2017-12-28 14:32:38931@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
932@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambard83207452018-01-04 07:51:39933@synthesize primaryToolbarOffsetConstraint = _primaryToolbarOffsetConstraint;
Gauthier Ambard929699412018-01-02 10:05:41934@synthesize imageSaver = _imageSaver;
Mark Cogan5bd86ba2017-12-28 14:32:38935// DialogPresenterDelegate property
936@synthesize dialogPresenterDelegateIsPresenting =
937 _dialogPresenterDelegateIsPresenting;
sdefresnee65fd872016-12-19 13:38:13938
939#pragma mark - Object lifecycle
940
Mark Cogan5e3da152017-07-11 15:57:30941- (instancetype)
942 initWithTabModel:(TabModel*)model
943 browserState:(ios::ChromeBrowserState*)browserState
944 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
945applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:13946 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
947 if (self) {
948 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17949
stkhapuginc9eee7b2017-04-10 15:49:27950 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:27951 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
952 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39953 _dispatcher = [[CommandDispatcher alloc] init];
954 [_dispatcher startDispatchingToTarget:self
955 forProtocol:@protocol(UrlLoader)];
956 [_dispatcher startDispatchingToTarget:self
957 forProtocol:@protocol(WebToolbarDelegate)];
958 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:24959 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:30960 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
961 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:32962 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
963 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
964 // dispatched to the endpoint as well. Since this is potentially
965 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
966 DCHECK(!applicationCommandEndpoint ||
967 [applicationCommandEndpoint
968 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
969 [_dispatcher
970 startDispatchingToTarget:applicationCommandEndpoint
971 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:39972
edchin7f210cd2017-09-28 08:03:53973 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
974 _snackbarCoordinator.dispatcher = _dispatcher;
975 [_snackbarCoordinator start];
976
Mark Coganca30df62017-11-20 14:29:11977 _languageSelectionCoordinator =
978 [[LanguageSelectionCoordinator alloc] initWithBaseViewController:self];
979 _languageSelectionCoordinator.presenter =
980 [[VerticalAnimationContainer alloc] init];
981
Eugene But49a7c572017-12-11 20:54:15982 _passKitCoordinator =
983 [[PassKitCoordinator alloc] initWithBaseViewController:self];
984
sdefresnee65fd872016-12-19 13:38:13985 _javaScriptDialogPresenter.reset(
986 new JavaScriptDialogPresenterImpl(_dialogPresenter));
987 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
988 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:15989 [[UpgradeCenter sharedInstance] registerClient:self
990 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:13991 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:13992 if (model && browserState)
993 [self updateWithTabModel:model browserState:browserState];
sdefresnee65fd872016-12-19 13:38:13994 }
995 return self;
996}
997
998- (instancetype)initWithNibName:(NSString*)nibNameOrNil
999 bundle:(NSBundle*)nibBundleOrNil {
1000 NOTREACHED();
1001 return nil;
1002}
1003
1004- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1005 NOTREACHED();
1006 return nil;
1007}
1008
1009- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201010 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131011}
1012
Mark Cogan5bd86ba2017-12-28 14:32:381013#pragma mark - Public Properties
sdefresnee65fd872016-12-19 13:38:131014
edchin3365c7d2017-09-01 22:20:371015- (id<ApplicationCommands,
1016 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371017 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531018 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371019 UrlLoader,
1020 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561021 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531022 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1023 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241024}
1025
sdefresnee65fd872016-12-19 13:38:131026- (void)setActive:(BOOL)active {
1027 if (_active == active) {
1028 return;
1029 }
1030 _active = active;
1031
1032 // If not active, display an activity indicator overlay over the view to
1033 // prevent interaction with the web page.
1034 // TODO(crbug.com/637093): This coordinator should be managed by the
1035 // coordinator used to present BrowserViewController, when implemented.
1036 if (active) {
1037 [self.activityOverlayCoordinator stop];
1038 self.activityOverlayCoordinator = nil;
1039 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171040 self.activityOverlayCoordinator =
1041 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131042 [self.activityOverlayCoordinator start];
1043 }
1044
1045 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091046 ActiveStateManager* active_state_manager =
1047 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131048 active_state_manager->SetActive(active);
1049 }
1050
1051 [_model setWebUsageEnabled:active];
1052 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191053 [self updateBroadcastState];
sdefresnee65fd872016-12-19 13:38:131054
1055 if (active) {
1056 // Make sure the tab (if any; it's possible to get here without a current
1057 // tab if the caller is about to create one) ends up on screen completely.
1058 Tab* currentTab = [_model currentTab];
1059 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441060 [self loadViewIfNeeded];
Sylvain Defresne448351332017-12-27 10:38:361061 if (_expectingForegroundTab) {
1062 PagePlaceholderTabHelper::FromWebState(currentTab.webState)
1063 ->AddPlaceholderForNextNavigation();
1064 }
sdefresnee65fd872016-12-19 13:38:131065 if (currentTab)
1066 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341067 } else {
1068 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131069 }
sdefresnee65fd872016-12-19 13:38:131070 [_paymentRequestManager enablePaymentRequest:active];
1071
1072 [self setNeedsStatusBarAppearanceUpdate];
1073}
1074
sdefresnee65fd872016-12-19 13:38:131075- (BOOL)isPlayingTTS {
1076 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1077}
1078
Mark Cogan5bd86ba2017-12-28 14:32:381079- (TabModel*)tabModel {
1080 return _model;
1081}
1082
sdefresne6165c8742017-01-16 15:42:021083- (ios::ChromeBrowserState*)browserState {
1084 return _browserState;
1085}
1086
Mark Cogan5bd86ba2017-12-28 14:32:381087#pragma mark - Private Properties
sdefresne6165c8742017-01-16 15:42:021088
sdefresnee65fd872016-12-19 13:38:131089- (SideSwipeController*)sideSwipeController {
1090 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271091 _sideSwipeController =
1092 [[SideSwipeController alloc] initWithTabModel:_model
1093 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131094 [_sideSwipeController setSnapshotDelegate:self];
Gauthier Ambard83207452018-01-04 07:51:391095 _sideSwipeController.toolbarInteractionHandler =
1096 self.primaryToolbarCoordinator;
sdefresnee65fd872016-12-19 13:38:131097 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031098 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131099 }
1100 return _sideSwipeController;
1101}
1102
sdefresnee65fd872016-12-19 13:38:131103- (DialogPresenter*)dialogPresenter {
1104 return _dialogPresenter;
1105}
1106
Mark Cogan776e0282018-01-02 09:00:061107- (KeyCommandsProvider*)keyCommandsProvider {
1108 if (!_keyCommandsProvider) {
1109 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
1110 }
1111 return _keyCommandsProvider;
1112}
1113
sdefresnee65fd872016-12-19 13:38:131114- (BOOL)canUseDesktopUserAgent {
1115 Tab* tab = [_model currentTab];
1116 if ([self isTabNativePage:tab])
1117 return NO;
1118
1119 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441120 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131121}
1122
1123// Whether the sharing menu should be shown.
1124- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191125 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251126 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131127}
1128
1129- (BOOL)canShowFindBar {
1130 // Make sure web controller can handle find in page.
1131 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421132 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131133 return NO;
rohitrao005a6432017-03-16 20:52:421134 }
sdefresnee65fd872016-12-19 13:38:131135
rohitrao005a6432017-03-16 20:52:421136 auto* helper = FindTabHelper::FromWebState(tab.webState);
1137 return (helper && helper->CurrentPageSupportsFindInPage() &&
1138 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131139}
1140
liaoyukeea9f3ee62017-03-07 22:05:391141- (web::UserAgentType)userAgentType {
1142 web::WebState* webState = [_model currentTab].webState;
1143 if (!webState)
1144 return web::UserAgentType::NONE;
1145 web::NavigationItem* visibleItem =
1146 webState->GetNavigationManager()->GetVisibleItem();
1147 if (!visibleItem)
1148 return web::UserAgentType::NONE;
1149
1150 return visibleItem->GetUserAgentType();
1151}
1152
sdefresnee65fd872016-12-19 13:38:131153- (void)setVisible:(BOOL)visible {
1154 if (_visible == visible)
1155 return;
Peter Laurense0b80f12017-11-21 07:52:401156
sdefresnee65fd872016-12-19 13:38:131157 _visible = visible;
1158}
1159
1160- (void)setViewVisible:(BOOL)viewVisible {
1161 if (_viewVisible == viewVisible)
1162 return;
1163 _viewVisible = viewVisible;
1164 self.visible = viewVisible;
1165 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191166 [self updateBroadcastState];
1167}
1168
1169- (void)setBroadcasting:(BOOL)broadcasting {
1170 if (_broadcasting == broadcasting)
1171 return;
1172 _broadcasting = broadcasting;
1173 if (base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
1174 // TODO(crbug.com/790886): Use the Browser's broadcaster once Browsers are
1175 // supported.
Kurt Horimoto06b94252017-12-08 19:45:591176 FullscreenController* fullscreenController =
1177 FullscreenControllerFactory::GetInstance()->GetForBrowserState(
1178 _browserState);
1179 ChromeBroadcaster* broadcaster = fullscreenController->broadcaster();
Kurt Horimotoe9b6002c2017-12-04 23:19:191180 if (_broadcasting) {
1181 _toolbarUIUpdater = [[LegacyToolbarUIUpdater alloc]
1182 initWithToolbarUI:[[ToolbarUIState alloc] init]
1183 toolbarOwner:self
1184 webStateList:[_model webStateList]];
1185 [_toolbarUIUpdater startUpdating];
1186 StartBroadcastingToolbarUI(_toolbarUIUpdater.toolbarUI, broadcaster);
Kurt Horimoto06b94252017-12-08 19:45:591187
Kurt Horimotoe9b6002c2017-12-04 23:19:191188 _mainContentUIUpdater = [[MainContentUIStateUpdater alloc]
1189 initWithState:[[MainContentUIState alloc] init]];
1190 _webMainContentUIForwarder = [[WebScrollViewMainContentUIForwarder alloc]
1191 initWithUpdater:_mainContentUIUpdater
1192 webStateList:[_model webStateList]];
1193 StartBroadcastingMainContentUI(self, broadcaster);
Kurt Horimoto06b94252017-12-08 19:45:591194
1195 _fullscreenUIUpdater = base::MakeUnique<FullscreenUIUpdater>(self);
1196 fullscreenController->AddObserver(_fullscreenUIUpdater.get());
1197
1198 fullscreenController->SetWebStateList([_model webStateList]);
Kurt Horimotoe9b6002c2017-12-04 23:19:191199 } else {
1200 StopBroadcastingToolbarUI(broadcaster);
1201 StopBroadcastingMainContentUI(broadcaster);
1202 [_toolbarUIUpdater stopUpdating];
1203 _toolbarUIUpdater = nil;
1204 _mainContentUIUpdater = nil;
1205 [_webMainContentUIForwarder disconnect];
1206 _webMainContentUIForwarder = nil;
Kurt Horimoto06b94252017-12-08 19:45:591207 fullscreenController->RemoveObserver(_fullscreenUIUpdater.get());
1208 _fullscreenUIUpdater = nullptr;
1209 fullscreenController->SetWebStateList(nullptr);
Kurt Horimotoe9b6002c2017-12-04 23:19:191210 }
1211 }
sdefresnee65fd872016-12-19 13:38:131212}
1213
1214- (BOOL)isToolbarOnScreen {
Mark Cogan849244ee2017-12-29 15:57:191215 return self.headerHeight - [self currentHeaderOffset] > 0;
sdefresnee65fd872016-12-19 13:38:131216}
1217
kkhorimotoa44349c12017-04-12 23:02:121218- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1219 if (_inNewTabAnimation == inNewTabAnimation)
1220 return;
1221 _inNewTabAnimation = inNewTabAnimation;
1222 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191223 [self updateBroadcastState];
kkhorimotoa44349c12017-04-12 23:02:121224}
1225
sdefresnee65fd872016-12-19 13:38:131226- (BOOL)isInNewTabAnimation {
1227 return _inNewTabAnimation;
1228}
1229
1230- (BOOL)shouldShowVoiceSearchBar {
1231 // On iPads, the voice search bar should only be shown for regular horizontal
1232 // size class configurations. It should always be shown for voice search
1233 // results Tabs on iPhones, including configurations with regular horizontal
1234 // size classes (i.e. landscape iPhone 6 Plus).
1235 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1236 UIUserInterfaceSizeClassCompact;
1237 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1238 (!IsIPadIdiom() || compactWidth);
1239}
1240
1241- (void)setHideStatusBar:(BOOL)hideStatusBar {
1242 if (_hideStatusBar == hideStatusBar)
1243 return;
1244 _hideStatusBar = hideStatusBar;
1245 [self setNeedsStatusBarAppearanceUpdate];
1246}
1247
Mark Cogan776e0282018-01-02 09:00:061248- (NSArray<HeaderDefinition*>*)headerViews {
1249 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
1250 if (![self isViewLoaded])
1251 return results;
1252
1253 if (!IsIPadIdiom()) {
Gauthier Ambard83207452018-01-04 07:51:391254 if (self.primaryToolbarCoordinator.toolbarViewController.view) {
Mark Cogan776e0282018-01-02 09:00:061255 [results addObject:[HeaderDefinition
Gauthier Ambard83207452018-01-04 07:51:391256 definitionWithView:self.primaryToolbarCoordinator
Mark Cogan776e0282018-01-02 09:00:061257 .toolbarViewController.view
1258 headerBehaviour:Hideable
1259 heightAdjustment:0.0
1260 inset:0.0]];
1261 }
1262 } else {
1263 if (self.tabStripView) {
1264 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
1265 headerBehaviour:Hideable
1266 heightAdjustment:0.0
1267 inset:0.0]];
1268 }
Gauthier Ambard83207452018-01-04 07:51:391269 if (self.primaryToolbarCoordinator.toolbarViewController.view) {
Mark Cogan776e0282018-01-02 09:00:061270 [results addObject:[HeaderDefinition
Gauthier Ambard83207452018-01-04 07:51:391271 definitionWithView:self.primaryToolbarCoordinator
Mark Cogan776e0282018-01-02 09:00:061272 .toolbarViewController.view
1273 headerBehaviour:Hideable
1274 heightAdjustment:0.0
1275 inset:0.0]];
1276 }
1277 if ([_findBarController view]) {
1278 [results addObject:[HeaderDefinition
1279 definitionWithView:[_findBarController view]
1280 headerBehaviour:Overlap
1281 heightAdjustment:0.0
1282 inset:kIPadFindBarOverlap]];
1283 }
1284 }
1285 return [results copy];
1286}
1287
Mark Cogan849244ee2017-12-29 15:57:191288- (CGFloat)headerOffset {
1289 if (IsIPadIdiom())
1290 return StatusBarHeight();
1291 return 0.0;
1292}
1293
1294- (CGFloat)headerHeight {
1295 return [self headerHeightForTab:[_model currentTab]];
1296}
1297
Gauthier Ambard83207452018-01-04 07:51:391298- (id<PrimaryToolbarCoordinator>)primaryToolbarCoordinator {
1299 return _toolbarCoordinator;
1300}
1301
1302- (LegacyToolbarCoordinator*)legacyToolbarCoordinator {
1303 return _toolbarCoordinator;
1304}
1305
Mark Cogan776e0282018-01-02 09:00:061306- (web::WebState*)currentWebState {
1307 return [[_model currentTab] webState];
1308}
1309
Mark Cogan5bd86ba2017-12-28 14:32:381310#pragma mark - Public methods
1311
1312- (void)setPrimary:(BOOL)primary {
1313 [_model setPrimary:primary];
1314 if (primary) {
1315 [self updateDialogPresenterActiveState];
1316 [self updateBroadcastState];
1317 } else {
1318 self.dialogPresenter.active = false;
1319 }
1320}
sdefresnee65fd872016-12-19 13:38:131321
1322- (void)shieldWasTapped:(id)sender {
Gauthier Ambard83207452018-01-04 07:51:391323 [self.primaryToolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131324}
1325
Cooper Knaakd0a974cd2017-08-10 18:05:471326- (void)userEnteredTabSwitcher {
1327 if ([self.tabTipBubblePresenter isUserEngaged]) {
1328 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1329 }
1330}
1331
Cooper Knaake963d6702017-08-11 21:03:111332- (void)presentBubblesIfEligible {
1333 [self presentNewTabTipBubbleOnInitialized];
Gregory Chatzinoff56635192017-12-06 02:11:261334 [self presentNewIncognitoTabTipBubbleOnInitialized];
Cooper Knaake963d6702017-08-11 21:03:111335}
1336
Mark Cogan5bd86ba2017-12-28 14:32:381337- (void)browserStateDestroyed {
1338 [self setActive:NO];
1339 [_paymentRequestManager close];
1340 _paymentRequestManager = nil;
Gauthier Ambard83207452018-01-04 07:51:391341 [self.legacyToolbarCoordinator browserStateDestroyed];
Mark Cogan5bd86ba2017-12-28 14:32:381342 [_model browserStateDestroyed];
1343
1344 // Disconnect child coordinators.
1345 [_activityServiceCoordinator disconnect];
1346 [_qrScannerCoordinator disconnect];
1347 [_tabHistoryCoordinator disconnect];
1348 [_pageInfoCoordinator disconnect];
1349 [_externalSearchCoordinator disconnect];
1350 [self.tabStripCoordinator stop];
1351 self.tabStripCoordinator = nil;
1352 self.tabStripView = nil;
1353
1354 _browserState = nullptr;
1355 [_dispatcher stopDispatchingToTarget:self];
1356 _dispatcher = nil;
1357}
1358
1359- (Tab*)addSelectedTabWithURL:(const GURL&)url
1360 transition:(ui::PageTransition)transition {
1361 return [self addSelectedTabWithURL:url
1362 atIndex:[_model count]
1363 transition:transition];
1364}
1365
1366- (Tab*)addSelectedTabWithURL:(const GURL&)url
1367 atIndex:(NSUInteger)position
1368 transition:(ui::PageTransition)transition {
1369 return [self addSelectedTabWithURL:url
1370 atIndex:position
1371 transition:transition
1372 tabAddedCompletion:nil];
1373}
1374
1375- (Tab*)addSelectedTabWithURL:(const GURL&)url
1376 atIndex:(NSUInteger)position
1377 transition:(ui::PageTransition)transition
1378 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
1379 return [self addSelectedTabWithURL:url
1380 postData:NULL
1381 atIndex:position
1382 transition:transition
1383 tabAddedCompletion:tabAddedCompletion];
1384}
1385
1386- (void)expectNewForegroundTab {
1387 _expectingForegroundTab = YES;
1388}
1389
1390- (void)startVoiceSearchWithOriginView:(UIView*)originView {
1391 _voiceSearchButton = originView;
1392 // Delay Voice Search until new tab animations have finished.
1393 if (self.inNewTabAnimation) {
1394 _startVoiceSearchAfterNewTabAnimation = YES;
1395 return;
1396 }
1397
1398 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
1399 // dismiss the keyboard.
1400 [self closeFindInPage];
1401 [[_model currentTab].webController dismissKeyboard];
1402
1403 // Ensure that voice search objects are created.
1404 [self ensureVoiceSearchControllerCreated];
1405 [self ensureVoiceSearchBarCreated];
1406
1407 // Present voice search.
1408 [_voiceSearchBar prepareToPresentVoiceSearch];
1409 _voiceSearchController->StartRecognition(self, [_model currentTab]);
Gauthier Ambard83207452018-01-04 07:51:391410 [self.primaryToolbarCoordinator cancelOmniboxEdit];
Mark Cogan5bd86ba2017-12-28 14:32:381411}
1412
1413- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
1414 dismissOmnibox:(BOOL)dismissOmnibox {
1415 [_activityServiceCoordinator cancelShare];
1416 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
1417 [_bookmarkInteractionController dismissSnackbar];
1418 if (dismissOmnibox) {
Gauthier Ambard83207452018-01-04 07:51:391419 [self.primaryToolbarCoordinator cancelOmniboxEdit];
Mark Cogan5bd86ba2017-12-28 14:32:381420 }
1421 [_dialogPresenter cancelAllDialogs];
1422 [self.dispatcher hidePageInfo];
1423 [self.tabTipBubblePresenter dismissAnimated:NO];
1424 [self.incognitoTabTipBubblePresenter dismissAnimated:NO];
1425 if (_voiceSearchController)
1426 _voiceSearchController->DismissMicPermissionsHelp();
1427
1428 Tab* currentTab = [_model currentTab];
1429 [currentTab dismissModals];
1430
1431 if (currentTab) {
1432 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
1433 if (findHelper) {
1434 findHelper->StopFinding(^{
1435 [self updateFindBar:NO shouldFocus:NO];
1436 });
1437 }
1438 }
1439
1440 [_paymentRequestManager cancelRequest];
1441 [_printController dismissAnimated:YES];
1442 _printController = nil;
1443 [self.dispatcher dismissToolsMenu];
1444 [_contextMenuCoordinator stop];
1445 [self dismissRateThisAppDialog];
1446
1447 if (self.presentedViewController) {
1448 // Dismisses any other modal controllers that may be present, e.g. Recent
1449 // Tabs.
1450 //
1451 // Note that currently, some controllers like the bookmark ones were already
1452 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
1453 // but are still reported as the presentedViewController. Calling
1454 // |dismissViewControllerAnimated:completion:| again would dismiss the BVC
1455 // itself, so instead check the value of |self.dismissingModal| and only
1456 // call dismiss if one of the above calls has not already triggered a
1457 // dismissal.
1458 //
1459 // To ensure the completion is called, nil is passed to the call to dismiss,
1460 // and the completion is called explicitly below.
1461 if (!TabSwitcherPresentsBVCEnabled() || !self.dismissingModal) {
1462 [self dismissViewControllerAnimated:NO completion:nil];
1463 }
1464 // Dismissed controllers will be so after a delay. Queue the completion
1465 // callback after that.
1466 if (completion) {
1467 dispatch_after(
1468 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
1469 dispatch_get_main_queue(), ^{
1470 completion();
1471 });
1472 }
1473 } else if (completion) {
1474 // If no view controllers are presented, we should be ok with dispatching
1475 // the completion block directly.
1476 dispatch_async(dispatch_get_main_queue(), completion);
1477 }
1478}
1479
1480- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
1481 return [self.tabStripCoordinator placeholderView];
1482}
1483
1484- (void)shutdown {
1485 DCHECK(!_isShutdown);
1486 _isShutdown = YES;
1487 [self.tabStripCoordinator stop];
1488 self.tabStripCoordinator = nil;
1489 [_toolbarCoordinator stop];
1490 _toolbarCoordinator = nil;
1491 self.tabStripView = nil;
1492 _infoBarContainer = nil;
1493 _readingListMenuNotifier = nil;
1494 _bookmarkModelBridge.reset();
1495 [_model removeObserver:self];
1496 [[UpgradeCenter sharedInstance] unregisterClient:self];
Mark Cogan5bd86ba2017-12-28 14:32:381497 if (_voiceSearchController)
1498 _voiceSearchController->SetDelegate(nil);
1499 [_rateThisAppDialog setDelegate:nil];
1500 [_model closeAllTabs];
1501 [_paymentRequestManager setActiveWebState:nullptr];
1502}
1503
1504#pragma mark - NSObject
1505
1506- (BOOL)accessibilityPerformEscape {
1507 [self dismissPopups];
1508 return YES;
1509}
1510
Mark Cogan776e0282018-01-02 09:00:061511#pragma mark - UIResponder
1512
1513- (NSArray*)keyCommands {
1514 if (![self shouldRegisterKeyboardCommands]) {
1515 return nil;
1516 }
1517 return [self.keyCommandsProvider
1518 keyCommandsForConsumer:self
1519 baseViewController:self
1520 dispatcher:self.dispatcher
1521 editingText:![self isFirstResponder]];
1522}
1523
1524#pragma mark - UIResponder helpers
1525
1526// Whether the BVC should declare keyboard commands.
1527- (BOOL)shouldRegisterKeyboardCommands {
1528 if ([self presentedViewController])
1529 return NO;
1530
1531 if (_voiceSearchController && _voiceSearchController->IsVisible())
1532 return NO;
1533
1534 // If there is no first responder, try to make the webview the first
1535 // responder.
1536 if (!GetFirstResponder()) {
1537 web::WebState* webState = _model.currentTab.webState;
1538 if (webState)
1539 [webState->GetWebViewProxy() becomeFirstResponder];
1540 }
1541
1542 return YES;
1543}
1544
Mark Cogan5bd86ba2017-12-28 14:32:381545#pragma mark - UIViewController
sdefresnee65fd872016-12-19 13:38:131546
1547// Perform additional set up after loading the view, typically from a nib.
1548- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091549 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281550 initialViewsRect.origin.y += StatusBarHeight();
1551 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131552 UIViewAutoresizing initialViewAutoresizing =
1553 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1554
stkhapuginf58b10d02017-04-10 13:36:171555 self.contentArea =
1556 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131557 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171558 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131559 self.typingShield.autoresizingMask = initialViewAutoresizing;
Justin Cohen41b1f382017-12-04 15:22:081560 self.typingShield.accessibilityIdentifier = @"Typing Shield";
1561 self.typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1562
sdefresnee65fd872016-12-19 13:38:131563 [self.typingShield addTarget:self
1564 action:@selector(shieldWasTapped:)
1565 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131566 self.view.autoresizingMask = initialViewAutoresizing;
1567 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1568 [self.view addSubview:self.contentArea];
1569 [self.view addSubview:self.typingShield];
1570 [super viewDidLoad];
1571
1572 // Install fake status bar for iPad iOS7
1573 [self installFakeStatusBar];
1574 [self buildToolbarAndTabStrip];
Justin Cohen4eeada32017-11-13 18:21:281575 [self setUpViewLayout:YES];
Justin Cohenba27610e2017-11-08 19:34:451576 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:501577 [self addConstraintsToToolbar];
1578 }
sdefresnee65fd872016-12-19 13:38:131579 // If the tab model and browser state are valid, finish initialization.
1580 if (_model && _browserState)
1581 [self addUIFunctionalityForModelAndBrowserState];
1582
1583 // Add a tap gesture recognizer to save the last tap location for the source
1584 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271585 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1586 initWithTarget:self
1587 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131588 [tapRecognizer setDelegate:self];
1589 [tapRecognizer setCancelsTouchesInView:NO];
1590 [_contentArea addGestureRecognizer:tapRecognizer];
1591}
1592
Justin Cohenb3170c32017-09-19 01:55:221593- (void)viewSafeAreaInsetsDidChange {
1594 [super viewSafeAreaInsetsDidChange];
1595 // Gate this behind iPhone X, since it's currently the only device that
1596 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501597 if (IsIPhoneX()) {
Justin Cohen4eeada32017-11-13 18:21:281598 [self setUpViewLayout:NO];
Jean-François Geyelined4cde72017-10-11 11:34:501599 }
Justin Cohenba27610e2017-11-08 19:34:451600 if (IsSafeAreaCompatibleToolbarEnabled()) {
Gauthier Ambard100670f72017-10-27 09:54:271601 // TODO(crbug.com/778236): Check if this call can be removed once the
1602 // Toolbar is a contained ViewController.
sczs42f7f7482017-11-08 01:13:271603 [_toolbarCoordinator.toolbarViewController viewSafeAreaInsetsDidChange];
Gauthier Ambard100670f72017-10-27 09:54:271604 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501605 }
Justin Cohenb3170c32017-09-19 01:55:221606}
1607
sdefresnee65fd872016-12-19 13:38:131608- (void)viewDidAppear:(BOOL)animated {
1609 [super viewDidAppear:animated];
1610 self.viewVisible = YES;
1611 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191612 [self updateBroadcastState];
Gregory Chatzinoff541b8642017-10-25 00:25:211613
1614 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1615 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1616 // check for |self.browserState| before calling the presenting the bubbles.
1617 if (self.browserState) {
1618 [self presentBubblesIfEligible];
1619 }
sdefresnee65fd872016-12-19 13:38:131620}
1621
1622- (void)viewWillAppear:(BOOL)animated {
1623 [super viewWillAppear:animated];
1624
sdefresnee65fd872016-12-19 13:38:131625 self.visible = YES;
1626
1627 // Restore hidden infobars.
Rohit Rao755c37b2017-11-10 14:05:521628 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131629 _infoBarContainer->RestoreInfobars();
1630 }
1631
1632 // If the controller is suspended, or has been paged out due to low memory,
1633 // updating the view will be handled when it's displayed again.
1634 if (![_model webUsageEnabled] || !self.contentArea)
1635 return;
1636 // Update the displayed tab (if any; the switcher may not have created one
1637 // yet) in case it changed while showing the switcher.
1638 Tab* currentTab = [_model currentTab];
1639 if (currentTab)
1640 [self displayTab:currentTab isNewSelection:YES];
1641}
1642
1643- (void)viewWillDisappear:(BOOL)animated {
1644 self.viewVisible = NO;
1645 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191646 [self updateBroadcastState];
sdefresnee65fd872016-12-19 13:38:131647 [[_model currentTab] wasHidden];
1648 [_bookmarkInteractionController dismissSnackbar];
Rohit Rao755c37b2017-11-10 14:05:521649 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131650 _infoBarContainer->SuspendInfobars();
1651 }
1652 [super viewWillDisappear:animated];
1653}
1654
1655- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1656 duration:(NSTimeInterval)duration {
1657 [super willRotateToInterfaceOrientation:orient duration:duration];
1658 [self dismissPopups];
1659 [self reshowFindBarIfNeededWithCoordinator:nil];
1660}
1661
1662- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1663 [super didRotateFromInterfaceOrientation:orient];
1664
1665 // This reinitializes the toolbar, including updating the Overlay View,
1666 // if there is one.
1667 [self updateToolbar];
Mark Cogan80aa28d2017-11-30 13:11:341668 [self infoBarContainerStateDidChangeAnimated:NO];
sdefresnee65fd872016-12-19 13:38:131669}
1670
1671- (BOOL)prefersStatusBarHidden {
1672 return self.hideStatusBar;
1673}
1674
1675// Called when in the foreground and the OS needs more memory. Release as much
1676// as possible.
1677- (void)didReceiveMemoryWarning {
1678 // Releases the view if it doesn't have a superview.
1679 [super didReceiveMemoryWarning];
1680
1681 // Release any cached data, images, etc that aren't in use.
1682 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1683 // but since the FaviconCache uses obj-c in the header, it can't be included
1684 // there.
1685 if (_browserState) {
1686 FaviconLoader* loader =
1687 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1688 _browserState);
1689 if (loader)
1690 loader->PurgeCache();
1691 }
1692
1693 if (![self isViewLoaded]) {
1694 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1695 // as the BrowserViewController.
1696 self.contentArea = nil;
1697 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271698 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131699 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271700 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421701 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461702 _toolbarCoordinator = nil;
Kurt Horimotoea429dd2017-11-28 02:24:301703 [_toolbarUIUpdater stopUpdating];
1704 _toolbarUIUpdater = nil;
stkhapuginc9eee7b2017-04-10 15:49:271705 _toolbarModelDelegate = nil;
1706 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031707 [self.tabStripCoordinator stop];
1708 self.tabStripCoordinator = nil;
1709 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271710 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131711 }
1712}
1713
1714- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1715 [super traitCollectionDidChange:previousTraitCollection];
1716 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1717 // because in some cases the presented view controller isn't a child of the
1718 // BVC in the view controller hierarchy (some intervening object isn't a
1719 // view controller).
1720 [self.presentedViewController
1721 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131722 // Update voice search bar visibility.
1723 [self updateVoiceSearchBarVisibilityAnimated:NO];
1724}
1725
1726- (void)viewWillTransitionToSize:(CGSize)size
1727 withTransitionCoordinator:
1728 (id<UIViewControllerTransitionCoordinator>)coordinator {
1729 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1730 [self dismissPopups];
1731 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1732}
1733
sdefresnee65fd872016-12-19 13:38:131734- (void)dismissViewControllerAnimated:(BOOL)flag
1735 completion:(void (^)())completion {
Rohit Rao685807a52017-11-10 20:50:111736 // It is an error to call this method when no VC is being presented.
1737 DCHECK(!TabSwitcherPresentsBVCEnabled() || self.presentedViewController);
1738
Rohit Raoa668c022017-11-08 00:04:441739 // Some calling code invokes |dismissViewControllerAnimated:completion:|
1740 // multiple times. When the BVC is displayed using VC containment, multiple
1741 // calls are effectively idempotent because only the first call has any effect
1742 // and subsequent calls do nothing. However, when the BVC is presented,
1743 // subsequent calls end up dismissing the BVC itself. This is never what we
Rohit Rao685807a52017-11-10 20:50:111744 // want, so check for this case and return early. It is not enough to check
1745 // |self.dismissingModal| because some dismissals do not go through
1746 // -[BrowserViewController dismissViewControllerAnimated:completion:|.
Rohit Raoa668c022017-11-08 00:04:441747 // TODO(crbug.com/782338): Fix callers and remove this early return.
Rohit Rao685807a52017-11-10 20:50:111748 if (TabSwitcherPresentsBVCEnabled() &&
1749 (self.dismissingModal || self.presentedViewController.isBeingDismissed)) {
Rohit Raoa668c022017-11-08 00:04:441750 return;
1751 }
1752
sdefresnee65fd872016-12-19 13:38:131753 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271754 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131755 [super dismissViewControllerAnimated:flag
1756 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271757 BrowserViewController* strongSelf = weakSelf;
Mark Cogan5bd86ba2017-12-28 14:32:381758 strongSelf.dismissingModal = NO;
1759 strongSelf.dialogPresenterDelegateIsPresenting =
1760 NO;
sdefresnee65fd872016-12-19 13:38:131761 if (completion)
1762 completion();
Mark Cogan5bd86ba2017-12-28 14:32:381763 [strongSelf.dialogPresenter tryToPresent];
sdefresnee65fd872016-12-19 13:38:131764 }];
1765}
1766
1767- (void)presentViewController:(UIViewController*)viewControllerToPresent
1768 animated:(BOOL)flag
1769 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271770 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131771 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1772 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1773 // presented, to show a temporary view of the launch screen and then remove it
1774 // when the controller for the FRE has been presented. This fix should be
1775 // removed when the FRE startup code is rewritten.
1776 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1777 experimental_flags::AlwaysDisplayFirstRun()) &&
1778 !tests_hook::DisableFirstRun();
1779 // These if statements check that |presentViewController| is being called for
1780 // the FRE case.
1781 if (firstRunLaunch &&
1782 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1783 UINavigationController* navController =
1784 base::mac::ObjCCastStrict<UINavigationController>(
1785 viewControllerToPresent);
1786 if ([navController.topViewController
1787 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1788 self.hideStatusBar = YES;
1789
1790 // Load view from Launch Screen and add it to window.
1791 NSBundle* mainBundle = base::mac::FrameworkBundle();
1792 NSArray* topObjects =
1793 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1794 UIViewController* launchScreenController =
1795 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1796 // |launchScreenView| is loaded as an autoreleased object, and is retained
1797 // by the |completion| block below.
1798 UIView* launchScreenView = launchScreenController.view;
1799 launchScreenView.userInteractionEnabled = NO;
1800 launchScreenView.frame = self.view.window.bounds;
1801 [self.view.window addSubview:launchScreenView];
1802
1803 // Replace the completion handler sent to the superclass with one which
1804 // removes |launchScreenView| and resets the status bar. If |completion|
1805 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271806 __weak BrowserViewController* weakSelf = self;
1807 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131808 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271809 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131810 if (completion)
1811 completion();
stkhapuginc9eee7b2017-04-10 15:49:271812 };
sdefresnee65fd872016-12-19 13:38:131813 }
1814 }
1815
Mark Cogan5bd86ba2017-12-28 14:32:381816 self.dialogPresenterDelegateIsPresenting = YES;
1817 if ([self.sideSwipeController inSwipe]) {
1818 [self.sideSwipeController resetContentView];
justincohen7e61cd92016-12-24 00:38:171819 }
sdefresnee65fd872016-12-19 13:38:131820
1821 [super presentViewController:viewControllerToPresent
1822 animated:flag
1823 completion:finalCompletionHandler];
1824}
1825
Mark Cogan80aa28d2017-11-30 13:11:341826- (BOOL)shouldAutorotate {
Kurt Horimoto3a449ce2017-12-27 18:22:141827 if (self.presentedViewController.beingPresented ||
1828 self.presentedViewController.beingDismissed) {
1829 // Don't rotate while a presentation or dismissal animation is occurring.
Mark Cogan80aa28d2017-11-30 13:11:341830 return NO;
Mark Cogan5bd86ba2017-12-28 14:32:381831 } else if (_sideSwipeController &&
1832 ![self.sideSwipeController shouldAutorotate]) {
Mark Cogan80aa28d2017-11-30 13:11:341833 // Don't auto rotate if side swipe controller view says not to.
1834 return NO;
1835 } else {
1836 return [super shouldAutorotate];
1837 }
1838}
1839
Mark Cogan849244ee2017-12-29 15:57:191840- (UIStatusBarStyle)preferredStatusBarStyle {
1841 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
1842 : UIStatusBarStyleDefault;
1843}
1844
Mark Cogan776e0282018-01-02 09:00:061845#pragma mark - ** Private BVC Methods **
1846
Mark Cogan776e0282018-01-02 09:00:061847#pragma mark - Private Methods: BVC Initialization
sdefresnee65fd872016-12-19 13:38:131848
1849- (void)updateWithTabModel:(TabModel*)model
1850 browserState:(ios::ChromeBrowserState*)browserState {
1851 DCHECK(model);
1852 DCHECK(browserState);
1853 DCHECK(!_model);
1854 DCHECK(!_browserState);
1855 _browserState = browserState;
1856 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271857 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381858
sdefresnee65fd872016-12-19 13:38:131859 [_model addObserver:self];
1860
1861 if (!_isOffTheRecord) {
1862 [DefaultIOSWebViewFactory
1863 registerWebViewFactory:[ChromeWebViewFactory class]];
1864 }
1865 NSUInteger count = [_model count];
1866 for (NSUInteger index = 0; index < count; ++index)
1867 [self installDelegatesForTab:[_model tabAtIndex:index]];
1868
gambardbdc07cc2017-02-03 16:43:111869 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121870 _browserState->GetRequestContext());
Gauthier Ambard929699412018-01-02 10:05:411871 self.imageSaver = [[ImageSaver alloc] initWithBaseViewController:self];
stkhapuginc9eee7b2017-04-10 15:49:271872 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131873
sdefresnedc432f42017-01-17 14:36:591874 // Register for bookmark changed notification (BookmarkModel may be null
1875 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131876 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591877 if (_bookmarkModel) {
Gauthier Ambard65e949b092017-11-29 08:46:201878 _bookmarkModelBridge.reset(
1879 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
sdefresnedc432f42017-01-17 14:36:591880 }
sdefresnee65fd872016-12-19 13:38:131881}
1882
sdefresnee65fd872016-12-19 13:38:131883- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221884 CGFloat statusBarHeight = StatusBarHeight();
1885 CGRect statusBarFrame =
1886 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1887 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1888 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131889 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221890 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1891 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1892 [_fakeStatusBarView layer].zPosition = 99;
1893 [[self view] addSubview:_fakeStatusBarView];
1894 } else {
1895 // Add a white bar on phone so that the status bar on the NTP is white.
1896 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1897 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131898 }
1899}
1900
1901// Create the UI elements. May or may not have valid browser state & tab model.
1902- (void)buildToolbarAndTabStrip {
1903 DCHECK([self isViewLoaded]);
1904 DCHECK(!_toolbarModelDelegate);
1905
Rohit Rao44f204302017-08-10 14:49:541906 // Initialize the prerender service before creating the toolbar controller.
1907 PrerenderService* prerenderService =
1908 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1909 if (prerenderService) {
1910 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131911 }
1912
1913 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201914 _toolbarModelDelegate.reset(
1915 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131916 _toolbarModelIOS.reset([_dependencyFactory
1917 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
Peter Laurense0b80f12017-11-21 07:52:401918 _toolbarCoordinator = [[LegacyToolbarCoordinator alloc]
1919 initWithBaseViewController:self
1920 toolsMenuConfigurationProvider:self
Kurt Horimoto975327e2017-12-20 01:38:061921 dispatcher:self.dispatcher
1922 browserState:_browserState];
Peter Laurense0b80f12017-11-21 07:52:401923
Gauthier Ambard83207452018-01-04 07:51:391924 self.sideSwipeController.toolbarInteractionHandler =
1925 self.primaryToolbarCoordinator;
Peter Laurense0b80f12017-11-21 07:52:401926
Gauthier Ambard82c8cc52017-10-26 15:59:051927 [_toolbarCoordinator
sczs32cfde82017-11-14 20:43:221928 setToolbarController:
1929 [_dependencyFactory
1930 newToolbarControllerWithDelegate:self
1931 urlLoader:self
1932 dispatcher:self.dispatcher]];
Gauthier Ambard83207452018-01-04 07:51:391933 [_dispatcher startDispatchingToTarget:self.primaryToolbarCoordinator
justincohen75011c32017-04-28 16:31:391934 forProtocol:@protocol(OmniboxFocuser)];
Gauthier Ambard83207452018-01-04 07:51:391935 [self.legacyToolbarCoordinator setTabCount:[_model count]];
Kurt Horimoto06b94252017-12-08 19:45:591936 [_toolbarCoordinator start];
Kurt Horimotoe9b6002c2017-12-04 23:19:191937 [self updateBroadcastState];
stkhapuginc9eee7b2017-04-10 15:49:271938 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461939 _voiceSearchController->SetDelegate(
Gauthier Ambard83207452018-01-04 07:51:391940 [self.primaryToolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131941
sdefresnee65fd872016-12-19 13:38:131942 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031943 self.tabStripCoordinator =
1944 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1945 self.tabStripCoordinator.browserState = _browserState;
1946 self.tabStripCoordinator.dispatcher = _dispatcher;
1947 self.tabStripCoordinator.tabModel = _model;
1948 self.tabStripCoordinator.presentationProvider = self;
1949 self.tabStripCoordinator.animationWaitDuration =
Kurt Horimoto62e97c72017-11-03 19:51:471950 kLegacyFullscreenControllerToolbarAnimationDuration;
edchinf5150c682017-09-18 02:50:031951 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131952 }
1953
1954 // Create infobar container.
1955 if (!_infoBarContainerDelegate) {
1956 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1957 _infoBarContainer.reset(
1958 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1959 }
1960}
1961
Jean-François Geyelined4cde72017-10-11 11:34:501962- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:111963 NSLayoutYAxisAnchor* topAnchor;
1964 // On iPad, the toolbar is underneath the tab strip.
1965 // On iPhone, it is underneath the top of the screen.
1966 if (IsIPadIdiom()) {
1967 topAnchor = self.tabStripView.bottomAnchor;
1968 } else {
1969 topAnchor = [self view].topAnchor;
1970 }
1971
Gauthier Ambard83207452018-01-04 07:51:391972 [self.legacyToolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501973
Gauthier Ambard83207452018-01-04 07:51:391974 self.primaryToolbarOffsetConstraint =
1975 [self.primaryToolbarCoordinator.toolbarViewController.view.topAnchor
Justin Cohen9fe9ef672017-12-01 20:37:431976 constraintEqualToAnchor:topAnchor];
Jean-François Geyelined4cde72017-10-11 11:34:501977 [NSLayoutConstraint activateConstraints:@[
Gauthier Ambard83207452018-01-04 07:51:391978 self.primaryToolbarOffsetConstraint,
1979 [self.primaryToolbarCoordinator.toolbarViewController.view.leadingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501980 constraintEqualToAnchor:[self view].leadingAnchor],
Gauthier Ambard83207452018-01-04 07:51:391981 [self.primaryToolbarCoordinator.toolbarViewController.view.trailingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501982 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501983 ]];
1984 [[self view] layoutIfNeeded];
1985}
1986
sdefresnee65fd872016-12-19 13:38:131987// Enable functionality that only makes sense if the views are loaded and
1988// both browser state and tab model are valid.
1989- (void)addUIFunctionalityForModelAndBrowserState {
1990 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051991 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131992 DCHECK(_model);
1993 DCHECK([self isViewLoaded]);
1994
1995 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1996
Rohit Raoaf46af92017-08-10 12:52:301997 infobars::InfoBarManager* infoBarManager = nullptr;
1998 if (_model.currentTab) {
1999 DCHECK(_model.currentTab.webState);
2000 infoBarManager =
2001 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
2002 }
sdefresnee65fd872016-12-19 13:38:132003 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
2004
sczsdd860eba2017-08-10 01:55:382005 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:432006 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
2007 initWithBaseViewController:self];
2008 _activityServiceCoordinator.dispatcher = _dispatcher;
2009 _activityServiceCoordinator.tabModel = _model;
2010 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:462011 _activityServiceCoordinator.positionProvider =
Gauthier Ambardc4d04f12018-01-04 16:58:242012 [self.primaryToolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:432013 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:432014
Rohit Raocda0a992017-08-16 15:37:112015 _qrScannerCoordinator =
2016 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
2017 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:052018 _qrScannerCoordinator.loadProvider =
Gauthier Ambard83207452018-01-04 07:51:392019 [self.primaryToolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:112020 _qrScannerCoordinator.presentationProvider = self;
2021
Kurt Horimoto238ae692017-12-22 23:55:472022 _tabHistoryCoordinator = [[LegacyTabHistoryCoordinator alloc]
2023 initWithBaseViewController:self
2024 browserState:_browserState];
sczsdd860eba2017-08-10 01:55:382025 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:462026 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard83207452018-01-04 07:51:392027 [self.primaryToolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:382028 _tabHistoryCoordinator.tabModel = _model;
2029 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:462030 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard83207452018-01-04 07:51:392031 [self.primaryToolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:382032
sczs6ae47ad2017-09-06 17:26:532033 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:202034 _sadTabCoordinator.baseViewController = self;
2035 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:532036
sczs281fbdc22017-12-20 20:59:062037 // If there are any existing SadTabHelpers in |_model|, update the helpers
2038 // delegate with the new |_sadTabCoordinator|.
2039 for (NSUInteger i = 0; i < _model.count; i++) {
2040 SadTabTabHelper* sadTabHelper =
2041 SadTabTabHelper::FromWebState([_model tabAtIndex:i].webState);
2042 DCHECK(sadTabHelper);
2043 if (sadTabHelper) {
2044 sadTabHelper->SetDelegate(_sadTabCoordinator);
2045 }
2046 }
2047
Kurt Horimoto084d73b2017-12-22 23:50:462048 _pageInfoCoordinator = [[PageInfoLegacyCoordinator alloc]
2049 initWithBaseViewController:self
2050 browserState:_browserState];
Gregory Chatzinoffdf93d692017-09-09 01:32:272051 _pageInfoCoordinator.dispatcher = _dispatcher;
2052 _pageInfoCoordinator.loader = self;
2053 _pageInfoCoordinator.presentationProvider = self;
2054 _pageInfoCoordinator.tabModel = _model;
2055
Louis Romerod11747a2017-10-20 20:10:352056 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
2057 _externalSearchCoordinator.dispatcher = _dispatcher;
2058
mathp9b4c11d2017-07-06 20:24:132059 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272060 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132061 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202062 browserState:_browserState
2063 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052064 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192065 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132066 }
2067}
2068
2069// Set the frame for the various views. View must be loaded.
Justin Cohen4eeada32017-11-13 18:21:282070- (void)setUpViewLayout:(BOOL)initialLayout {
sdefresnee65fd872016-12-19 13:38:132071 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132072 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:132073
Justin Cohenb3170c32017-09-19 01:55:222074 // Update the fake toolbar background height.
2075 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2076 fakeStatusBarFrame.size.height = StatusBarHeight();
2077 _fakeStatusBarView.frame = fakeStatusBarFrame;
2078
sdefresnee65fd872016-12-19 13:38:132079
2080 // Position the toolbar next, either at the top of the browser view or
2081 // directly under the tabstrip.
Justin Cohen4eeada32017-11-13 18:21:282082 if (initialLayout)
2083 [self addChildViewController:_toolbarCoordinator.toolbarViewController];
Justin Cohenba27610e2017-11-08 19:34:452084 if (!IsSafeAreaCompatibleToolbarEnabled()) {
Gauthier Ambard83207452018-01-04 07:51:392085 CGFloat minY = self.headerOffset;
2086 if (self.tabStripView) {
2087 minY += CGRectGetHeight([self.tabStripView frame]);
2088 }
2089 CGRect toolbarFrame = _toolbarCoordinator.toolbarViewController.view.frame;
2090 toolbarFrame.origin = CGPointMake(0, minY);
2091 toolbarFrame.size.width = widthOfView;
sczs42f7f7482017-11-08 01:13:272092 [_toolbarCoordinator.toolbarViewController.view setFrame:toolbarFrame];
Jean-François Geyelined4cde72017-10-11 11:34:502093 }
sdefresnee65fd872016-12-19 13:38:132094
2095 // Place the infobar container above the content area.
2096 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
Justin Cohen4eeada32017-11-13 18:21:282097 if (initialLayout)
2098 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
sdefresnee65fd872016-12-19 13:38:132099
Gauthier Ambard087e3572017-12-20 12:54:472100 // Place the toolbar controller above the infobar container and adds the
Gauthier Ambard470c50f2017-12-21 07:55:292101 // layout guides.
Gauthier Ambard087e3572017-12-20 12:54:472102 if (initialLayout) {
Justin Cohen4eeada32017-11-13 18:21:282103 [[self view] insertSubview:_toolbarCoordinator.toolbarViewController.view
2104 aboveSubview:infoBarContainerView];
Gauthier Ambard087e3572017-12-20 12:54:472105 AddNamedGuide(kOmniboxGuide, self.view);
Gauthier Ambard470c50f2017-12-21 07:55:292106 AddNamedGuide(kBackButtonGuide, self.view);
2107 AddNamedGuide(kForwardButtonGuide, self.view);
Gauthier Ambard522668e2018-01-03 14:10:072108 AddNamedGuide(kToolsMenuGuide, self.view);
2109 AddNamedGuide(kTabSwitcherGuide, self.view);
Gauthier Ambard087e3572017-12-20 12:54:472110 }
Justin Cohen4eeada32017-11-13 18:21:282111 if (initialLayout)
2112 [_toolbarCoordinator.toolbarViewController
2113 didMoveToParentViewController:self];
sdefresnee65fd872016-12-19 13:38:132114
sdefresnee65fd872016-12-19 13:38:132115 // Adjust the content area to be under the toolbar, for fullscreen or below
2116 // the toolbar is not fullscreen.
2117 CGRect contentFrame = [_contentArea frame];
2118 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222119 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2120 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132121 [_contentArea setFrame:contentFrame];
2122
2123 // Adjust the infobar container to be either at the bottom of the screen
2124 // (iPhone) or on the lower toolbar edge (iPad).
2125 CGRect infoBarFrame = contentFrame;
2126 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2127 infoBarFrame.size.height = 0;
2128 [infoBarContainerView setFrame:infoBarFrame];
2129
2130 // Attach the typing shield to the content area but have it hidden.
Mark Cogan5bd86ba2017-12-28 14:32:382131 [self.typingShield setFrame:[_contentArea frame]];
Justin Cohen41b1f382017-12-04 15:22:082132 if (initialLayout) {
Mark Cogan5bd86ba2017-12-28 14:32:382133 [[self view] insertSubview:self.typingShield aboveSubview:_contentArea];
2134 [self.typingShield setHidden:YES];
Justin Cohen41b1f382017-12-04 15:22:082135 }
sdefresnee65fd872016-12-19 13:38:132136}
2137
sdefresnee65fd872016-12-19 13:38:132138- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2139 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442140 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132141
kkhorimotoa44349c12017-04-12 23:02:122142 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132143 // Hide findbar. |updateToolbar| will restore the findbar later.
2144 [self hideFindBarWithAnimation:NO];
2145
2146 // Make new content visible, resizing it first as the orientation may
2147 // have changed from the last time it was displayed.
2148 [[tab view] setFrame:_contentArea.bounds];
2149 [_contentArea displayContentView:[tab view]];
2150 }
2151 [self updateToolbar];
2152
2153 if (newSelection)
Gauthier Ambard83207452018-01-04 07:51:392154 [self.legacyToolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132155
2156 // Notify the Tab that it was displayed.
2157 [tab wasShown];
2158}
2159
2160- (void)initializeBookmarkInteractionController {
2161 if (_bookmarkInteractionController)
2162 return;
edchinbb8ba892017-09-12 15:44:032163 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2164 initWithBrowserState:_browserState
2165 loader:self
2166 parentController:self
2167 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132168}
2169
Mark Cogan776e0282018-01-02 09:00:062170- (void)setOverScrollActionControllerToStaticNativeContent:
2171 (StaticHtmlNativeContent*)nativeContent {
2172 if (!IsIPadIdiom()) {
2173 OverscrollActionsController* controller =
2174 [[OverscrollActionsController alloc]
2175 initWithScrollView:[nativeContent scrollView]];
2176 [controller setDelegate:self];
2177 OverscrollStyle style = _isOffTheRecord
2178 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
2179 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
2180 controller.style = style;
2181 nativeContent.overscrollActionsController = controller;
2182 }
2183}
2184
2185#pragma mark - Private Methods: UI Configuration, update and Layout
2186
sdefresnee65fd872016-12-19 13:38:132187// Update the state of back and forward buttons, hiding the forward button if
2188// there is nowhere to go. Assumes the model's current tab is up to date.
2189- (void)updateToolbar {
2190 // If the BVC has been partially torn down for low memory, wait for the
2191 // view rebuild to handle toolbar updates.
2192 if (!(_toolbarModelIOS && _browserState))
2193 return;
2194
2195 Tab* tab = [_model currentTab];
2196 if (![tab navigationManager])
2197 return;
sczsf1620e52017-10-02 22:54:462198 [_toolbarCoordinator updateToolbarState];
Gauthier Ambard83207452018-01-04 07:51:392199 [self.legacyToolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132200
Sylvain Defresnef5d2d952017-11-14 11:15:312201 if (_insertedTabWasPrerenderedTab && !_toolbarModelIOS->IsLoading())
Gauthier Ambard83207452018-01-04 07:51:392202 [self.primaryToolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132203
rohitrao005a6432017-03-16 20:52:422204 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2205 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132206 [self showFindBarWithAnimation:NO
2207 selectText:YES
2208 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362209 }
sdefresnee65fd872016-12-19 13:38:132210
2211 // Hide the toolbar if displaying phone NTP.
2212 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552213 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132214 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552215 if (item) {
2216 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132217 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2218 hideToolbar = isNTP && !_isOffTheRecord &&
Gauthier Ambard83207452018-01-04 07:51:392219 ![self.primaryToolbarCoordinator isOmniboxFirstResponder] &&
2220 ![self.primaryToolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132221 }
Gauthier Ambard83207452018-01-04 07:51:392222 [self.primaryToolbarCoordinator.toolbarViewController.view
2223 setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132224 }
2225}
2226
Kurt Horimotoe9b6002c2017-12-04 23:19:192227- (void)updateBroadcastState {
2228 self.broadcasting =
Kurt Horimotoea429dd2017-11-28 02:24:302229 self.active && self.viewVisible && !self.inNewTabAnimation;
Kurt Horimotoea429dd2017-11-28 02:24:302230}
2231
sdefresnee65fd872016-12-19 13:38:132232- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122233 self.dialogPresenter.active =
2234 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132235}
2236
2237- (void)dismissPopups {
Peter Laurense0b80f12017-11-21 07:52:402238 [self.dispatcher dismissToolsMenu];
Gregory Chatzinoffdf93d692017-09-09 01:32:272239 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282240 [_tabHistoryCoordinator dismissHistoryPopup];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262241 [self.tabTipBubblePresenter dismissAnimated:NO];
2242 [self.incognitoTabTipBubblePresenter dismissAnimated:NO];
Cooper Knaak33f9f402017-08-09 18:04:382243}
2244
Mark Cogan776e0282018-01-02 09:00:062245- (BOOL)isTabScrolledToTop:(Tab*)tab {
2246 CGPoint scrollOffset =
2247 tab.webState->GetWebViewProxy().scrollViewProxy.contentOffset;
2248
2249 // If there is a native controller, use the native controller's scroll offset.
2250 id nativeController = [self nativeControllerForTab:tab];
2251 if ([nativeController conformsToProtocol:@protocol(CRWNativeContent)] &&
2252 [nativeController respondsToSelector:@selector(scrollOffset)]) {
2253 scrollOffset = [nativeController scrollOffset];
2254 }
2255 return CGPointEqualToPoint(scrollOffset, CGPointZero);
2256}
2257
2258- (UIView*)footerView {
2259 return _voiceSearchBar;
2260}
2261
2262- (CGFloat)headerHeightForTab:(Tab*)tab {
2263 id nativeController = [self nativeControllerForTab:tab];
2264 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2265 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2266 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2267 // On iPhone, don't add any header height for ToolbarOwner native
2268 // controllers when they're displaying their own toolbar.
2269 return 0;
2270 }
2271
2272 NSArray<HeaderDefinition*>* views = [self headerViews];
2273
2274 CGFloat height = self.headerOffset;
2275 for (HeaderDefinition* header in views) {
2276 if (header.view && header.behaviour == Hideable) {
2277 height += CGRectGetHeight([header.view frame]) -
2278 header.heightAdjustement - header.inset;
2279 }
2280 }
2281
2282 return height - StatusBarHeight();
2283}
2284
2285- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
2286 atOffset:(CGFloat)headerOffset {
2287 CGFloat height = self.headerOffset;
2288 for (HeaderDefinition* header in headers) {
2289 CGFloat yOrigin = height - headerOffset - header.inset;
Gauthier Ambard83207452018-01-04 07:51:392290 BOOL isPrimaryToolbar =
2291 header.view ==
2292 self.primaryToolbarCoordinator.toolbarViewController.view;
Mark Cogan776e0282018-01-02 09:00:062293 // Make sure the toolbarView's constraints are also updated. Leaving the
2294 // -setFrame call to minimize changes in this CL -- otherwise the way
2295 // toolbar_view manages it's alpha changes would also need to be updated.
2296 // TODO(crbug.com/778822): This can be cleaned up when the new fullscreen
2297 // is enabled.
Gauthier Ambard83207452018-01-04 07:51:392298 if (IsSafeAreaCompatibleToolbarEnabled() && isPrimaryToolbar &&
Mark Cogan776e0282018-01-02 09:00:062299 !IsIPadIdiom()) {
Gauthier Ambard83207452018-01-04 07:51:392300 self.primaryToolbarOffsetConstraint.constant = yOrigin;
Mark Cogan776e0282018-01-02 09:00:062301 }
2302 CGRect frame = [header.view frame];
2303 frame.origin.y = yOrigin;
2304 [header.view setFrame:frame];
2305 if (header.behaviour != Overlap)
2306 height += CGRectGetHeight(frame);
2307 }
2308}
2309
2310- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
2311 CGRect frame = [_contentArea frame];
2312 if (!fullScreen) {
2313 // Changing the origin here is unnecessary, it's set in page_animation_util.
2314 frame.size.height -= self.headerHeight;
2315 }
2316
2317 CGFloat shortAxis = frame.size.width;
2318 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
2319 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
2320 CGFloat aspectRatio = frame.size.height / frame.size.width;
2321 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
2322 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
2323 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
2324 CGRect cardFrame = {frame.origin, cardSize};
2325
2326 CardView* card =
2327 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
2328 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
2329 : CardCloseButtonSide::LEADING;
2330 [_contentArea addSubview:card];
2331 return card;
2332}
2333
2334#pragma mark - Private Methods: Showing and Dismissing Child UI
2335
2336- (void)showAllBookmarks {
2337 DCHECK(self.visible || self.dismissingModal);
2338 GURL URL(kChromeUIBookmarksURL);
2339 Tab* tab = [_model currentTab];
2340 web::NavigationManager::WebLoadParams params(URL);
2341 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
2342 [tab navigationManager]->LoadURLWithParams(params);
2343}
2344
2345- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
2346 DCHECK(self.visible || self.dismissingModal);
2347 GURL url(kChromeUINewTabURL);
2348 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
2349 if (fragment != "") {
2350 GURL::Replacements replacement;
2351 replacement.SetRefStr(fragment);
2352 url = url.ReplaceComponents(replacement);
2353 }
2354 Tab* tab = [_model currentTab];
2355 web::NavigationManager::WebLoadParams params(url);
2356 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
2357 [tab navigationManager]->LoadURLWithParams(params);
2358}
2359
2360- (void)dismissRateThisAppDialog {
2361 if (_rateThisAppDialog) {
2362 base::RecordAction(base::UserMetricsAction(
2363 "IOSRateThisAppDialogDismissedProgramatically"));
2364 [_rateThisAppDialog dismiss];
2365 _rateThisAppDialog = nil;
2366 }
2367}
2368
2369#pragma mark - Private Methods: Bubble views
2370
Cooper Knaakd0a974cd2017-08-10 18:05:472371- (BubbleViewControllerPresenter*)
2372bubblePresenterForFeature:(const base::Feature&)feature
2373 direction:(BubbleArrowDirection)direction
2374 alignment:(BubbleAlignment)alignment
2375 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212376 DCHECK(self.browserState);
2377 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382378 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472379 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382380 }
2381 // Capture |weakSelf| instead of the feature engagement tracker object
2382 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2383 // the feature engagement tracker will remain pointing to invalid memory if
2384 // its owner (the ChromeBrowserState) is deallocated.
2385 __weak BrowserViewController* weakSelf = self;
2386 void (^dismissalCallback)(void) = ^() {
2387 BrowserViewController* strongSelf = weakSelf;
2388 if (strongSelf) {
2389 feature_engagement::TrackerFactory::GetForBrowserState(
2390 strongSelf.browserState)
2391 ->Dismissed(feature);
2392 }
2393 };
2394
Cooper Knaakd0a974cd2017-08-10 18:05:472395 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382396 [[BubbleViewControllerPresenter alloc] initWithText:text
2397 arrowDirection:direction
2398 alignment:alignment
2399 dismissalCallback:dismissalCallback];
2400
Cooper Knaakd0a974cd2017-08-10 18:05:472401 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132402}
2403
Cooper Knaak120cee5e2017-08-10 20:57:002404- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212405 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002406 // If the tab tip bubble has already been presented and the user is still
2407 // considered engaged, it can't be overwritten or set to |nil| or else it will
2408 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2409 // can be safely overwritten or set to |nil|.
2410 if (!self.tabTipBubblePresenter.isUserEngaged) {
2411 __weak BrowserViewController* weakSelf = self;
2412 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2413 [weakSelf presentNewTabTipBubble];
2414 };
2415
2416 // Because the new tab tip occurs on startup, the feature engagement
2417 // tracker's database is not guaranteed to be loaded by this time. For the
2418 // bubble to appear properly, a callback is used to guarantee the event data
2419 // is loaded before the check to see if the promotion should be displayed.
2420 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2421 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2422 }
2423}
2424
2425- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212426 DCHECK(self.browserState);
Gregory Chatzinofff192d5d2017-12-09 00:26:072427 // If the BVC is not visible, do not present the bubble.
2428 if (!self.viewVisible)
2429 return;
Gregory Chatzinoff876956212017-12-18 22:33:232430 // Do not present the bubble if there is no current tab or if the current tab
2431 // is the NTP.
Gregory Chatzinofff192d5d2017-12-09 00:26:072432 Tab* currentTab = [self.tabModel currentTab];
2433 if (!currentTab)
2434 return;
2435 if (currentTab.webState->GetVisibleURL() == kChromeUINewTabURL)
2436 return;
Gregory Chatzinoffe205f44642017-12-19 17:54:032437
2438 // Do not present the bubble if the tab is not scrolled to the top.
2439 if (![self isTabScrolledToTop:currentTab])
Gregory Chatzinoff876956212017-12-18 22:33:232440 return;
Gregory Chatzinofff192d5d2017-12-09 00:26:072441
Cooper Knaak120cee5e2017-08-10 20:57:002442 NSString* text =
2443 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2444 CGPoint tabSwitcherAnchor;
2445 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032446 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002447 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032448 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002449 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2450 } else {
Gauthier Ambard522668e2018-01-03 14:10:072451 if (base::FeatureList::IsEnabled(kCleanToolbar)) {
2452 UILayoutGuide* guide = FindNamedGuide(kTabSwitcherGuide, self.view);
2453 CGPoint anchorPoint =
2454 bubble_util::AnchorPoint(guide.layoutFrame, BubbleArrowDirectionUp);
2455 tabSwitcherAnchor =
2456 [guide.owningView convertPoint:anchorPoint
2457 toView:guide.owningView.window];
2458 } else {
Gauthier Ambard6e35ffb42018-01-05 09:57:202459 DCHECK([self.legacyToolbarCoordinator
Gauthier Ambard522668e2018-01-03 14:10:072460 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
Gauthier Ambard6e35ffb42018-01-05 09:57:202461 tabSwitcherAnchor = [self.legacyToolbarCoordinator
Gauthier Ambard522668e2018-01-03 14:10:072462 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2463 }
Cooper Knaak120cee5e2017-08-10 20:57:002464 }
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262465
Cooper Knaake963d6702017-08-11 21:03:112466 // If the feature engagement tracker does not consider it valid to display
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262467 // the new tab tip, then end early to prevent the potential reassignment
2468 // of the existing |tabTipBubblePresenter| to nil.
2469 BubbleViewControllerPresenter* presenter =
Cooper Knaak120cee5e2017-08-10 20:57:002470 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2471 direction:BubbleArrowDirectionUp
2472 alignment:BubbleAlignmentTrailing
2473 text:text];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262474 if (!presenter)
2475 return;
2476
2477 self.tabTipBubblePresenter = presenter;
2478
Cooper Knaak120cee5e2017-08-10 20:57:002479 [self.tabTipBubblePresenter presentInViewController:self
2480 view:self.view
2481 anchorPoint:tabSwitcherAnchor];
2482}
2483
Helen Yang9175bd52017-08-12 00:28:402484- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212485 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402486 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2487 // user is still considered engaged.
2488 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2489 __weak BrowserViewController* weakSelf = self;
2490 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2491 [weakSelf presentNewIncognitoTabTipBubble];
2492 };
2493
2494 // Use a callback in case the new incognito tab tip should be shown on
2495 // startup. This ensures that the tracker's database will be fully loaded
2496 // before checking if the promotion should be displayed.
2497 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2498 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2499 }
2500}
2501
2502- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212503 DCHECK(self.browserState);
Gauthier Ambard6e35ffb42018-01-05 09:57:202504 DCHECK([self.legacyToolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402505 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
Gregory Chatzinofff192d5d2017-12-09 00:26:072506 // If the BVC is not visible, do not present the bubble.
2507 if (!self.viewVisible)
2508 return;
2509
Gregory Chatzinoff876956212017-12-18 22:33:232510 // Do not present the bubble if there is no current tab.
2511 Tab* currentTab = [self.tabModel currentTab];
2512 if (!currentTab)
2513 return;
Gregory Chatzinoffe205f44642017-12-19 17:54:032514
2515 // Do not present the bubble if the tab is not scrolled to the top.
2516 if (![self isTabScrolledToTop:currentTab])
Gregory Chatzinoff876956212017-12-18 22:33:232517 return;
2518
Helen Yang9175bd52017-08-12 00:28:402519 NSString* text = l10n_util::GetNSStringWithFixup(
2520 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
Gauthier Ambard522668e2018-01-03 14:10:072521 CGPoint toolsButtonAnchor;
2522 if (base::FeatureList::IsEnabled(kCleanToolbar)) {
2523 UILayoutGuide* guide = FindNamedGuide(kToolsMenuGuide, self.view);
2524 CGPoint anchorPoint =
2525 bubble_util::AnchorPoint(guide.layoutFrame, BubbleArrowDirectionUp);
2526 toolsButtonAnchor = [guide.owningView convertPoint:anchorPoint
2527 toView:guide.owningView.window];
2528 } else {
Gauthier Ambard6e35ffb42018-01-05 09:57:202529 toolsButtonAnchor = [self.legacyToolbarCoordinator
Gauthier Ambard522668e2018-01-03 14:10:072530 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2531 }
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262532
2533 // If the feature engagement tracker does not consider it valid to display
2534 // the incognito tab tip, then end early to prevent the potential reassignment
2535 // of the existing |incognitoTabTipBubblePresenter| to nil.
2536 BubbleViewControllerPresenter* presenter =
Helen Yang9175bd52017-08-12 00:28:402537 [self bubblePresenterForFeature:feature_engagement::
2538 kIPHNewIncognitoTabTipFeature
2539 direction:BubbleArrowDirectionUp
2540 alignment:BubbleAlignmentTrailing
2541 text:text];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262542 if (!presenter)
2543 return;
2544
2545 self.incognitoTabTipBubblePresenter = presenter;
2546
Helen Yang9175bd52017-08-12 00:28:402547 [self.incognitoTabTipBubblePresenter
2548 presentInViewController:self
2549 view:self.view
2550 anchorPoint:toolsButtonAnchor];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262551 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402552}
2553
Mark Cogan776e0282018-01-02 09:00:062554#pragma mark - Private Methods: Find Bar UI
Gregory Chatzinoffe205f44642017-12-19 17:54:032555
Mark Cogan776e0282018-01-02 09:00:062556- (void)hideFindBarWithAnimation:(BOOL)animate {
2557 [_findBarController hideFindBarView:animate];
Gregory Chatzinoffe205f44642017-12-19 17:54:032558}
2559
Mark Cogan776e0282018-01-02 09:00:062560- (void)showFindBarWithAnimation:(BOOL)animate
2561 selectText:(BOOL)selectText
2562 shouldFocus:(BOOL)shouldFocus {
2563 DCHECK(_findBarController);
2564 Tab* tab = [_model currentTab];
2565 DCHECK(tab);
2566 CRWWebController* webController = tab.webController;
Mark Cogan849244ee2017-12-29 15:57:192567
Mark Cogan776e0282018-01-02 09:00:062568 CGRect referenceFrame = CGRectZero;
2569 if (IsIPadIdiom()) {
2570 referenceFrame = webController.visibleFrame;
2571 referenceFrame.origin.y -= kIPadFindBarOverlap;
Mark Cogan849244ee2017-12-29 15:57:192572 } else {
Mark Cogan776e0282018-01-02 09:00:062573 referenceFrame = _contentArea.frame;
Mark Cogan849244ee2017-12-29 15:57:192574 }
2575
Gauthier Ambard6e35ffb42018-01-05 09:57:202576 CGRect omniboxFrame;
2577 if (base::FeatureList::IsEnabled(kCleanToolbar)) {
2578 omniboxFrame = FindNamedGuide(kOmniboxGuide, self.view).layoutFrame;
2579 } else {
2580 omniboxFrame = [self.legacyToolbarCoordinator visibleOmniboxFrame];
2581 }
Mark Cogan776e0282018-01-02 09:00:062582 [_findBarController addFindBarView:animate
2583 intoView:self.view
2584 withFrame:referenceFrame
2585 alignWithFrame:omniboxFrame
2586 selectText:selectText];
2587 [self updateFindBar:YES shouldFocus:shouldFocus];
Mark Cogan849244ee2017-12-29 15:57:192588}
2589
Mark Cogan776e0282018-01-02 09:00:062590- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
2591 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
2592 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
2593 // in a crash.
2594 if (![_model currentTab]) {
2595 return;
2596 }
2597 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
2598 if (helper && helper->IsFindUIActive()) {
2599 if (initialUpdate && !_isOffTheRecord) {
2600 helper->RestoreSearchTerm();
Mark Cogan849244ee2017-12-29 15:57:192601 }
Mark Cogan776e0282018-01-02 09:00:062602
2603 [self setFramesForHeaders:[self headerViews]
2604 atOffset:[self currentHeaderOffset]];
2605 [_findBarController updateView:helper->GetFindResult()
2606 initialUpdate:initialUpdate
2607 focusTextfield:shouldFocus];
2608 } else {
2609 [self hideFindBarWithAnimation:YES];
Mark Cogan849244ee2017-12-29 15:57:192610 }
2611}
2612
Mark Cogan776e0282018-01-02 09:00:062613- (void)reshowFindBarIfNeededWithCoordinator:
2614 (id<UIViewControllerTransitionCoordinator>)coordinator {
2615 if (![_findBarController isFindInPageShown])
2616 return;
2617
2618 // Record focused state.
2619 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
2620
2621 [self hideFindBarWithAnimation:NO];
2622
2623 __weak BrowserViewController* weakSelf = self;
2624 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) =
2625 ^(id<UIViewControllerTransitionCoordinatorContext> context) {
2626 BrowserViewController* strongSelf = weakSelf;
2627 if (strongSelf)
2628 [strongSelf showFindBarWithAnimation:NO
2629 selectText:NO
2630 shouldFocus:isFocusedBeforeReshow];
2631 };
2632
2633 BOOL enqueued =
2634 [coordinator animateAlongsideTransition:nil completion:completion];
2635 if (!enqueued) {
2636 completion(nil);
2637 }
2638}
2639
2640#pragma mark - Private Methods: Alerts
2641
2642- (void)showErrorAlertWithStringTitle:(NSString*)title
2643 message:(NSString*)message {
2644 // Dismiss current alert.
2645 [_alertCoordinator stop];
2646
2647 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
2648 message:message
2649 viewController:self];
2650 [_alertCoordinator start];
2651}
2652
2653- (void)showSnackbar:(NSString*)text {
2654 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
2655 message.accessibilityLabel = text;
2656 message.duration = 2.0;
2657 message.category = kBrowserViewControllerSnackbarCategory;
2658 [self.dispatcher showSnackbarMessage:message];
2659}
2660
2661#pragma mark - Private Methods: Tap handling
sdefresnee65fd872016-12-19 13:38:132662
Mark Cogandfcdea72017-07-18 13:47:382663- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032664 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2665 _lastTapPoint = CGPointZero;
2666 } else {
2667 _lastTapPoint =
2668 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132669 }
Mark Cogane01ebce2017-07-12 19:31:032670 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132671}
2672
2673- (CGPoint)lastTapPoint {
2674 if (CACurrentMediaTime() - _lastTapTime < 1) {
2675 return _lastTapPoint;
2676 }
2677 return CGPointZero;
2678}
2679
2680- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2681 UIView* view = gestureRecognizer.view;
2682 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2683 _lastTapPoint =
2684 [[view superview] convertPoint:viewCoordinate toView:self.view];
2685 _lastTapTime = CACurrentMediaTime();
2686}
2687
Mark Cogan776e0282018-01-02 09:00:062688#pragma mark - Private Methods: Tab creation and selection
sdefresnee65fd872016-12-19 13:38:132689
2690// Called when either a tab finishes loading or when a tab with finished content
2691// is added directly to the model via pre-rendering.
2692- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2693 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2694
2695 // Persist the session on a delay.
2696 [_model saveSessionImmediately:NO];
2697}
2698
2699- (Tab*)addSelectedTabWithURL:(const GURL&)url
2700 postData:(TemplateURLRef::PostContent*)postData
2701 transition:(ui::PageTransition)transition {
2702 return [self addSelectedTabWithURL:url
2703 postData:postData
2704 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492705 transition:transition
2706 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132707}
2708
sdefresnee65fd872016-12-19 13:38:132709- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2710 postData:(TemplateURLRef::PostContent*)postData
2711 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492712 transition:(ui::PageTransition)transition
2713 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132714 if (position == NSNotFound)
2715 position = [_model count];
2716 DCHECK(position <= [_model count]);
2717
2718 web::NavigationManager::WebLoadParams params(URL);
2719 params.transition_type = transition;
2720 if (postData) {
2721 // Extract the content type and post params from |postData| and add them
2722 // to the load params.
2723 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2724 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2725 length:postData->second.length()];
[email protected]52706512017-12-29 17:50:182726 params.post_data = data;
2727 params.extra_headers = @{@"Content-Type" : contentType};
sdefresnee65fd872016-12-19 13:38:132728 }
Olivier Robind508a5632017-07-19 16:29:492729
2730 if (tabAddedCompletion) {
2731 if (self.foregroundTabWasAddedCompletionBlock) {
2732 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2733 self.foregroundTabWasAddedCompletionBlock;
2734 self.foregroundTabWasAddedCompletionBlock = ^{
2735 oldForegroundTabWasAddedCompletionBlock();
2736 tabAddedCompletion();
2737 };
2738 } else {
2739 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2740 }
2741 }
2742
sdefresnea6395912017-03-01 01:14:352743 Tab* tab = [_model insertTabWithLoadParams:params
2744 opener:nil
2745 openedByDOM:NO
2746 atIndex:position
2747 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132748 return tab;
2749}
2750
sdefresnee65fd872016-12-19 13:38:132751- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322752 web::WebState* webState = tab.webState;
2753 if (!webState)
2754 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392755 web::NavigationItem* visibleItem =
2756 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322757 if (!visibleItem)
2758 return NO;
2759 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132760}
2761
sdefresnee65fd872016-12-19 13:38:132762- (UIImageView*)pageOpenCloseAnimationView {
2763 CGRect frame = [_contentArea bounds];
2764
Mark Cogan849244ee2017-12-29 15:57:192765 frame.size.height = frame.size.height - self.headerHeight;
2766 frame.origin.y = self.headerHeight;
sdefresnee65fd872016-12-19 13:38:132767
stkhapuginf58b10d02017-04-10 13:36:172768 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132769 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2770 pageView.center = center;
2771
2772 pageView.backgroundColor = [UIColor whiteColor];
2773 return pageView;
2774}
2775
2776- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142777 // Unregistration happens when the Tab is removed from the TabModel.
Sylvain Defresnef5d2d952017-11-14 11:15:312778 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
2779
2780 // There should be no pre-rendered Tabs in TabModel.
2781 PrerenderService* prerenderService =
2782 PrerenderServiceFactory::GetForBrowserState(_browserState);
2783 DCHECK(!prerenderService ||
2784 !prerenderService->IsWebStatePrerendered(tab.webState));
edchincd32fdf2017-10-25 12:45:452785
Sylvain Defresne17b8aa42017-12-21 16:17:172786 SnapshotTabHelper::FromWebState(tab.webState)->SetDelegate(tab);
2787
edchincd32fdf2017-10-25 12:45:452788 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2789 if (PasswordTabHelper* passwordTabHelper =
2790 PasswordTabHelper::FromWebState(tab.webState)) {
edchin6941b8492017-12-01 18:59:592791 passwordTabHelper->SetBaseViewController(self);
edchincd32fdf2017-10-25 12:45:452792 passwordTabHelper->SetDispatcher(self.dispatcher);
2793 passwordTabHelper->SetPasswordControllerDelegate(self);
2794 }
2795
sdefresnee65fd872016-12-19 13:38:132796 tab.dialogDelegate = self;
2797 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132798 tab.passKitDialogProvider = self;
Kurt Horimotoa5a922a2017-11-07 00:21:092799 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472800 tab.legacyFullscreenControllerDelegate = self;
Kurt Horimoto803840622017-10-28 01:20:372801 }
sdefresnee65fd872016-12-19 13:38:132802 if (!IsIPadIdiom()) {
2803 tab.overscrollActionsControllerDelegate = self;
2804 }
olivierrobin9ce77b82017-01-12 17:29:192805 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132806 tab.tabSnapshottingDelegate = self;
2807 // Install the proper CRWWebController delegates.
2808 tab.webController.nativeProvider = self;
2809 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512810 // BrowserViewController presents SKStoreKitViewController on behalf of a
2811 // tab.
2812 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2813 if (tabHelper)
2814 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132815 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532816 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292817 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532818 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292819 }
Sylvain Defresnecacc3a52017-09-12 13:51:042820 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592821 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572822 NetExportTabHelper::CreateForWebState(tab.webState, self);
Mike Dougherty4620cf8e2017-10-31 23:37:092823 CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
Eugene But49a7c572017-12-11 20:54:152824 PassKitTabHelper::CreateForWebState(tab.webState, _passKitCoordinator);
edchincd32fdf2017-10-25 12:45:452825
Mark Coganca30df62017-11-20 14:29:112826 // The language detection helper accepts a callback from the translate
2827 // client, so must be created after it.
2828 // This will explode if the webState doesn't have a JS injection manager
2829 // (this only comes up in unit tests), so check for that and bypass the
2830 // init of the translation helpers if needed.
2831 // TODO(crbug.com/785238): Remove the need for this check.
2832 if (tab.webState->GetJSInjectionReceiver()) {
2833 ChromeIOSTranslateClient::CreateForWebState(tab.webState,
2834 _languageSelectionCoordinator);
2835 language::IOSLanguageDetectionTabHelper::CreateForWebState(
2836 tab.webState,
2837 ChromeIOSTranslateClient::FromWebState(tab.webState)
2838 ->GetTranslateDriver()
2839 ->CreateLanguageDetectionCallback(),
2840 UrlLanguageHistogramFactory::GetForBrowserState(self.browserState));
2841 }
2842
edchincd32fdf2017-10-25 12:45:452843 if (AccountConsistencyService* accountConsistencyService =
2844 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2845 self.browserState)) {
2846 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552847 }
sdefresnee65fd872016-12-19 13:38:132848}
2849
sdefresne49cf2862017-03-15 13:46:142850- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112851 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
edchincd32fdf2017-10-25 12:45:452852
2853 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2854 if (PasswordTabHelper* passwordTabHelper =
Sylvain Defresne17b8aa42017-12-21 16:17:172855 PasswordTabHelper::FromWebState(tab.webState)) {
edchincd32fdf2017-10-25 12:45:452856 passwordTabHelper->SetDispatcher(nil);
Sylvain Defresne17b8aa42017-12-21 16:17:172857 }
edchincd32fdf2017-10-25 12:45:452858
sdefresne49cf2862017-03-15 13:46:142859 tab.dialogDelegate = nil;
2860 tab.snapshotOverlayProvider = nil;
2861 tab.passKitDialogProvider = nil;
Kurt Horimotoa5a922a2017-11-07 00:21:092862 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472863 tab.legacyFullscreenControllerDelegate = nil;
Kurt Horimoto803840622017-10-28 01:20:372864 }
sdefresne49cf2862017-03-15 13:46:142865 if (!IsIPadIdiom()) {
2866 tab.overscrollActionsControllerDelegate = nil;
2867 }
2868 tab.tabHeadersDelegate = nil;
2869 tab.tabSnapshottingDelegate = nil;
2870 tab.webController.nativeProvider = nil;
2871 tab.webController.swipeRecognizerProvider = nil;
2872 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2873 if (tabHelper)
2874 tabHelper->SetLauncher(nil);
2875 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452876 if (AccountConsistencyService* accountConsistencyService =
2877 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2878 self.browserState)) {
2879 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2880 }
Sylvain Defresne17b8aa42017-12-21 16:17:172881
2882 SnapshotTabHelper::FromWebState(tab.webState)->SetDelegate(nil);
sdefresne49cf2862017-03-15 13:46:142883}
2884
Gauthier Ambard64396902017-12-08 10:14:582885- (void)tabSelected:(Tab*)tab notifyToolbar:(BOOL)notifyToolbar {
sdefresnee65fd872016-12-19 13:38:132886 DCHECK(tab);
2887
2888 // Ignore changes while the tab stack view is visible (or while suspended).
2889 // The display will be refreshed when this view becomes active again.
2890 if (!self.visible || ![_model webUsageEnabled])
2891 return;
2892
Gauthier Ambard64396902017-12-08 10:14:582893 [self displayTab:tab isNewSelection:notifyToolbar];
sdefresnee65fd872016-12-19 13:38:132894
kkhorimotoa44349c12017-04-12 23:02:122895 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132896 // Now that the new tab has been displayed, return to normal. Rather than
2897 // keep a reference to the previous tab, just turn off preview mode for all
2898 // tabs (since doing so is a no-op for the tabs that don't have it set).
2899 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272900 for (Tab* tab in _model) {
Sylvain Defresne448351332017-12-27 10:38:362901 PagePlaceholderTabHelper::FromWebState(tab.webState)
2902 ->CancelPlaceholderForNextNavigation();
sdefresnee65fd872016-12-19 13:38:132903 }
2904 }
2905}
2906
Mark Cogan776e0282018-01-02 09:00:062907- (id)nativeControllerForTab:(Tab*)tab {
2908 id nativeController = tab.webController.nativeController;
2909 return nativeController ? nativeController : _temporaryNativeController;
2910}
2911
Mark Cogan776e0282018-01-02 09:00:062912#pragma mark - Private Methods: Voice Search
2913
2914- (void)ensureVoiceSearchControllerCreated {
2915 if (!_voiceSearchController) {
2916 VoiceSearchProvider* provider =
2917 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2918 if (provider) {
2919 _voiceSearchController =
2920 provider->CreateVoiceSearchController(_browserState);
2921 _voiceSearchController->SetDelegate(
Gauthier Ambard83207452018-01-04 07:51:392922 [self.primaryToolbarCoordinator voiceSearchDelegate]);
Mark Cogan776e0282018-01-02 09:00:062923 }
2924 }
2925}
2926
2927- (void)ensureVoiceSearchBarCreated {
2928 if (_voiceSearchBar)
2929 return;
2930
2931 CGFloat width = CGRectGetWidth([[self view] bounds]);
2932 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2933 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
2934 _voiceSearchBar = ios::GetChromeBrowserProvider()
2935 ->GetVoiceSearchProvider()
2936 ->BuildVoiceSearchBar(frame, self.dispatcher);
2937 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2938 [_voiceSearchBar setHidden:YES];
2939 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2940 UIViewAutoresizingFlexibleWidth];
2941 [self.view insertSubview:_voiceSearchBar
2942 belowSubview:_infoBarContainer->view()];
2943}
2944
2945- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2946 // Voice search bar exists and is shown/hidden.
2947 BOOL show = self.shouldShowVoiceSearchBar;
2948 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
2949 return;
2950
2951 // Voice search bar doesn't exist and thus is not visible.
2952 if (!_voiceSearchBar && !show)
2953 return;
2954
2955 if (animated)
2956 [_voiceSearchBar animateToBecomeVisible:show];
2957 else
2958 _voiceSearchBar.hidden = !show;
2959}
2960
2961#pragma mark - Private Methods: Reading List
2962
2963- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
2964 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
2965
2966 ReadingListModel* readingModel =
2967 ReadingListModelFactory::GetForBrowserState(_browserState);
2968 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
2969 reading_list::ADDED_VIA_CURRENT_APP);
2970
2971 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
2972 [self showSnackbar:l10n_util::GetNSString(
2973 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
2974}
2975
2976#pragma mark - ** Protocol Implementations and Helpers **
2977
sdefresnee65fd872016-12-19 13:38:132978#pragma mark - SnapshotOverlayProvider methods
2979
2980- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2981 NSMutableArray* overlays = [NSMutableArray array];
2982 if (![_model webUsageEnabled]) {
2983 return overlays;
2984 }
2985 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2986 if (voiceSearchView) {
2987 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272988 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132989 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272990 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132991 [overlays addObject:voiceSearchOverlay];
2992 }
2993 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2994 if (infoBarView) {
2995 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272996 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132997 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272998 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132999 [overlays addObject:infoBarOverlay];
3000 }
3001 return overlays;
3002}
3003
Mark Cogan849244ee2017-12-29 15:57:193004#pragma mark - SnapshotOverlayProvider helpers
sdefresnee65fd872016-12-19 13:38:133005
Mark Cogan849244ee2017-12-29 15:57:193006// Provides a view that encompasses currently displayed infobar(s) or nil
3007// if no infobar is presented.
sdefresnee65fd872016-12-19 13:38:133008- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
3009 if (IsIPadIdiom()) {
3010 // Not using overlays on iPad because the content is pushed down by
3011 // infobar and the transition between snapshot and fresh page can
3012 // cause both snapshot and real infobars to appear at the same time.
3013 return nil;
3014 }
3015 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:303016 if (currentTab && tab == currentTab) {
3017 DCHECK(currentTab.webState);
3018 infobars::InfoBarManager* infoBarManager =
3019 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:133020 if (infoBarManager->infobar_count() > 0) {
3021 DCHECK(_infoBarContainer);
3022 return _infoBarContainer->view();
3023 }
3024 }
3025 return nil;
3026}
3027
Mark Cogan849244ee2017-12-29 15:57:193028// Returns a vertical infobar offset relative to the tab content.
sdefresnee65fd872016-12-19 13:38:133029- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:273030 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:133031 // There is no UI representation for non-current tabs or there is
3032 // no _infoBarContainer instantiated yet.
3033 // Return offset outside of tab.
3034 return CGRectGetMaxY(self.view.frame);
3035 } else if (IsIPadIdiom()) {
3036 // The infobars on iPad are display at the top of a tab.
3037 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
3038 } else {
3039 // The infobars on iPhone are displayed at the bottom of a tab.
3040 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
3041 return CGRectGetMaxY(visibleFrame) -
3042 CGRectGetHeight(_infoBarContainer->view().frame);
3043 }
3044}
3045
Mark Cogan849244ee2017-12-29 15:57:193046// Provides a view that encompasses the voice search bar if it's displayed or
3047// nil if the voice search bar isn't displayed.
sdefresnee65fd872016-12-19 13:38:133048- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
3049 Tab* currentTab = [_model currentTab];
3050 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
3051 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
3052 return _voiceSearchBar;
3053 }
3054 return nil;
3055}
3056
Mark Cogan849244ee2017-12-29 15:57:193057// Returns a vertical voice search bar offset relative to the tab content.
sdefresnee65fd872016-12-19 13:38:133058- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
3059 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
3060 // There is no UI representation for non-current tabs or there is
3061 // no visible voice search. Return offset outside of tab.
3062 return CGRectGetMaxY(self.view.frame);
3063 } else {
3064 // The voice search bar on iPhone is displayed at the bottom of a tab.
3065 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
3066 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
3067 }
3068}
3069
sdefresnee65fd872016-12-19 13:38:133070#pragma mark - PassKitDialogProvider methods
3071
3072- (void)presentPassKitDialog:(NSData*)data {
3073 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:273074 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:133075 if (data)
stkhapuginc9eee7b2017-04-10 15:49:273076 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:133077 if (error || !data) {
3078 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:303079 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:133080 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:303081 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:133082 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
3083 // NULL, replace if with DCHECK).
3084 if (infoBarManager)
3085 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
3086 }
3087 } else {
3088 PKAddPassesViewController* passKitViewController =
3089 [_dependencyFactory newPassKitViewControllerForPass:pass];
3090 if (passKitViewController) {
3091 [self presentViewController:passKitViewController
3092 animated:YES
3093 completion:^{
3094 }];
3095 }
3096 }
3097}
3098
Tomasz Garbusb844e992017-09-29 12:44:553099#pragma mark - PasswordControllerDelegate methods
3100
3101- (BOOL)displaySignInNotification:(UIViewController*)viewController
3102 fromTabId:(NSString*)tabId {
3103 // Check if the call comes from currently visible tab.
3104 if ([tabId isEqual:[_model currentTab].tabId]) {
3105 [self addChildViewController:viewController];
3106 [self.view addSubview:viewController.view];
3107 [viewController didMoveToParentViewController:self];
3108 return YES;
3109 } else {
3110 return NO;
3111 }
3112}
3113
sdefresnee65fd872016-12-19 13:38:133114#pragma mark - CRWWebStateDelegate methods.
3115
eugenebut75a06fa72017-01-09 17:09:553116- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:513117 createNewWebStateForURL:(const GURL&)URL
3118 openerURL:(const GURL&)openerURL
3119 initiatedByUser:(BOOL)initiatedByUser {
3120 // Check if requested web state is a popup and block it if necessary.
3121 if (!initiatedByUser) {
3122 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
3123 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:103124 // It's possible for a page to inject a popup into a window created via
3125 // window.open before its initial load is committed. Rather than relying
3126 // on the last committed or pending NavigationItem's referrer policy, just
3127 // use ReferrerPolicyDefault.
3128 // TODO(crbug.com/719993): Update this to a more appropriate referrer
3129 // policy once referrer policies are correctly recorded in
3130 // NavigationItems.
3131 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:513132 helper->HandlePopup(URL, referrer);
3133 return nil;
3134 }
3135 }
3136
3137 // Requested web state should not be blocked from opening.
3138 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
Sylvain Defresne17b8aa42017-12-21 16:17:173139 SnapshotTabHelper::FromWebState(currentTab.webState)
3140 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
eugenebut275f5892017-03-09 22:20:513141
3142 // Tabs open by DOM are always renderer initiated.
3143 web::NavigationManager::WebLoadParams params(GURL{});
3144 params.transition_type = ui::PAGE_TRANSITION_LINK;
3145 params.is_renderer_initiated = true;
3146 Tab* childTab = [[self tabModel]
3147 insertTabWithLoadParams:params
3148 opener:currentTab
3149 openedByDOM:YES
3150 atIndex:TabModelConstants::kTabPositionAutomatically
3151 inBackground:NO];
3152 return childTab.webState;
3153}
3154
eugenebutb46b2122017-03-14 02:43:263155- (void)closeWebState:(web::WebState*)webState {
3156 // Only allow a web page to close itself if it was opened by DOM, or if there
3157 // are no navigation items.
3158 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:583159 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:263160
3161 if (![self tabModel])
3162 return;
3163
3164 NSUInteger index = [[self tabModel] indexOfTab:tab];
3165 if (index != NSNotFound)
3166 [[self tabModel] closeTabAtIndex:index];
3167}
3168
eugenebut275f5892017-03-09 22:20:513169- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:553170 openURLWithParams:(const web::WebState::OpenURLParams&)params {
3171 switch (params.disposition) {
3172 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
3173 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
3174 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:353175 insertTabWithURL:params.url
3176 referrer:params.referrer
3177 transition:params.transition
3178 opener:LegacyTabHelper::GetTabForWebState(webState)
3179 openedByDOM:NO
3180 atIndex:TabModelConstants::kTabPositionAutomatically
3181 inBackground:(params.disposition ==
3182 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:553183 return tab.webState;
3184 }
3185 case WindowOpenDisposition::CURRENT_TAB: {
3186 web::NavigationManager::WebLoadParams loadParams(params.url);
3187 loadParams.referrer = params.referrer;
3188 loadParams.transition_type = params.transition;
3189 loadParams.is_renderer_initiated = params.is_renderer_initiated;
3190 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
3191 return webState;
3192 }
eugenebutd0984e82017-02-22 23:47:513193 case WindowOpenDisposition::NEW_POPUP: {
3194 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:353195 insertTabWithURL:params.url
3196 referrer:params.referrer
3197 transition:params.transition
3198 opener:LegacyTabHelper::GetTabForWebState(webState)
3199 openedByDOM:YES
3200 atIndex:TabModelConstants::kTabPositionAutomatically
3201 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:513202 return tab.webState;
3203 }
eugenebut75a06fa72017-01-09 17:09:553204 default:
3205 NOTIMPLEMENTED();
3206 return nullptr;
3207 };
3208}
3209
Mike Dougherty4e6b3a32017-08-23 18:49:213210- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:133211 handleContextMenu:(const web::ContextMenuParams&)params {
3212 // Prevent context menu from displaying for a tab which is no longer the
3213 // current one.
3214 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:213215 return;
sdefresnee65fd872016-12-19 13:38:133216 }
3217
3218 // No custom context menu if no valid url is available in |params|.
3219 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:213220 return;
sdefresnee65fd872016-12-19 13:38:133221 }
3222
3223 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:133224
stkhapuginc9eee7b2017-04-10 15:49:273225 _contextMenuCoordinator =
3226 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
3227 params:params];
sdefresnee65fd872016-12-19 13:38:133228
3229 NSString* title = nil;
3230 ProceduralBlock action = nil;
3231
stkhapuginc9eee7b2017-04-10 15:49:273232 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:133233 GURL link = params.link_url;
3234 bool isLink = link.is_valid();
3235 GURL imageUrl = params.src_url;
3236 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:193237 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:133238
3239 if (isLink) {
3240 if (link.SchemeIs(url::kJavaScriptScheme)) {
3241 // Open
3242 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
3243 action = ^{
3244 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
3245 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
3246 };
3247 [_contextMenuCoordinator addItemWithTitle:title action:action];
3248 }
3249
3250 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193251 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133252
sdefresnee65fd872016-12-19 13:38:133253 // Open in New Tab.
3254 title = l10n_util::GetNSStringWithFixup(
3255 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
3256 action = ^{
3257 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003258 // The "New Tab" item in the context menu opens a new tab in the current
3259 // browser state. |isOffTheRecord| indicates whether or not the current
3260 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:133261 [weakSelf webPageOrderedOpen:link
3262 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003263 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:133264 inBackground:YES
3265 appendTo:kCurrentTab];
3266 };
3267 [_contextMenuCoordinator addItemWithTitle:title action:action];
3268 if (!_isOffTheRecord) {
3269 // Open in Incognito Tab.
3270 title = l10n_util::GetNSStringWithFixup(
3271 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
3272 action = ^{
3273 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
3274 [weakSelf webPageOrderedOpen:link
3275 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133276 inIncognito:YES
3277 inBackground:NO
3278 appendTo:kCurrentTab];
3279 };
3280 [_contextMenuCoordinator addItemWithTitle:title action:action];
3281 }
olivierrobin51d4cf42017-01-17 13:32:353282 }
gambard65d69152017-03-23 17:44:223283 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:353284 NSString* innerText = params.link_text;
3285 if ([innerText length] > 0) {
3286 // Add to reading list.
3287 title = l10n_util::GetNSStringWithFixup(
3288 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
3289 action = ^{
3290 Record(ACTION_READ_LATER, isImage, isLink);
3291 [weakSelf addToReadingListURL:link title:innerText];
3292 };
3293 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:533294 }
sdefresnee65fd872016-12-19 13:38:133295 }
3296 // Copy Link.
3297 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
3298 action = ^{
3299 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:283300 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:133301 };
3302 [_contextMenuCoordinator addItemWithTitle:title action:action];
3303 }
3304 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193305 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133306 // Save Image.
gambard98b4ddf2017-04-18 07:14:053307 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:133308 action = ^{
3309 Record(ACTION_SAVE_IMAGE, isImage, isLink);
3310 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
3311 };
3312 [_contextMenuCoordinator addItemWithTitle:title action:action];
3313 // Open Image.
3314 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
3315 action = ^{
3316 Record(ACTION_OPEN_IMAGE, isImage, isLink);
3317 [weakSelf loadURL:imageUrl
3318 referrer:referrer
3319 transition:ui::PAGE_TRANSITION_LINK
3320 rendererInitiated:YES];
3321 };
3322 [_contextMenuCoordinator addItemWithTitle:title action:action];
3323 // Open Image In New Tab.
3324 title = l10n_util::GetNSStringWithFixup(
3325 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
3326 action = ^{
3327 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
3328 [weakSelf webPageOrderedOpen:imageUrl
3329 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133330 inBackground:true
3331 appendTo:kCurrentTab];
3332 };
3333 [_contextMenuCoordinator addItemWithTitle:title action:action];
3334
3335 TemplateURLService* service =
3336 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103337 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133338 if (defaultURL && !defaultURL->image_url().empty() &&
3339 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3340 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3341 defaultURL->short_name());
3342 action = ^{
3343 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3344 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3345 };
3346 [_contextMenuCoordinator addItemWithTitle:title action:action];
3347 }
3348 }
3349
3350 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133351}
3352
eugenebutb739bdc2017-01-25 06:32:483353- (void)webState:(web::WebState*)webState
3354 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3355 // Display the action sheet with the arrow pointing at the top center of the
3356 // web contents.
sdefresne0452a9d2017-02-09 15:33:283357 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483358 UIView* view = webState->GetView();
3359 CGPoint dialogLocation =
3360 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283361 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033362 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173363 helper->PresentDialog(dialogLocation,
3364 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053365 handler(shouldContinue);
3366 }));
eugenebutb739bdc2017-01-25 06:32:483367}
3368
sdefresnee65fd872016-12-19 13:38:133369- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3370 (web::WebState*)webState {
3371 return _javaScriptDialogPresenter.get();
3372}
3373
eugenebut63232102017-01-19 16:19:403374- (void)webState:(web::WebState*)webState
3375 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3376 proposedCredential:(NSURLCredential*)proposedCredential
3377 completionHandler:(void (^)(NSString* username,
3378 NSString* password))handler {
3379 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3380 proposedCredential:proposedCredential
3381 webState:webState
3382 completionHandler:handler];
3383}
3384
Mark Cogan776e0282018-01-02 09:00:063385#pragma mark - CRWWebStateDelegate helpers
3386
3387// Evaluates Javascript asynchronously using the current page context.
3388- (void)openJavascript:(NSString*)javascript {
3389 DCHECK(javascript);
3390 javascript = [javascript stringByRemovingPercentEncoding];
3391 web::WebState* webState = [[_model currentTab] webState];
3392 if (webState) {
3393 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3394 }
3395}
3396
3397// Performs a search with the image at the given url. The referrer is used to
3398// download the image.
3399- (void)searchByImageAtURL:(const GURL&)url
3400 referrer:(const web::Referrer)referrer {
3401 DCHECK(url.is_valid());
3402 __weak BrowserViewController* weakSelf = self;
3403 const GURL image_source_url = url;
3404 image_fetcher::IOSImageDataFetcherCallback callback =
3405 ^(NSData* data, const image_fetcher::RequestMetadata& metadata) {
3406 DCHECK(data);
3407 dispatch_async(dispatch_get_main_queue(), ^{
3408 [weakSelf searchByImageData:data atURL:image_source_url];
3409 });
3410 };
3411 _imageFetcher->FetchImageDataWebpDecoded(
3412 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3413 web::PolicyForNavigation(url, referrer));
3414}
3415
3416// Performs a search using |data| and |imageURL| as inputs.
3417- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3418 NSData* imageData = data;
3419 UIImage* image = [UIImage imageWithData:imageData];
3420 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3421 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3422 // kSearchByImageMaxImageHeight).
3423 if (image &&
3424 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3425 (image.size.width > kSearchByImageMaxImageWidth ||
3426 image.size.height > kSearchByImageMaxImageHeight)) {
3427 CGSize newImageSize =
3428 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3429 image = [image gtm_imageByResizingToSize:newImageSize
3430 preserveAspectRatio:YES
3431 trimToFit:NO];
3432 imageData = UIImageJPEGRepresentation(image, 1.0);
3433 }
3434
3435 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3436 std::string byteString(bytes, [imageData length]);
3437
3438 TemplateURLService* templateUrlService =
3439 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
3440 const TemplateURL* defaultURL =
3441 templateUrlService->GetDefaultSearchProvider();
3442 DCHECK(!defaultURL->image_url().empty());
3443 DCHECK(defaultURL->image_url_ref().IsValid(
3444 templateUrlService->search_terms_data()));
3445 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3446 search_args.image_url = imageURL;
3447 search_args.image_thumbnail_content = byteString;
3448
3449 // Generate the URL and populate |post_content| with the content type and
3450 // HTTP body for the request.
3451 TemplateURLRef::PostContent post_content;
3452 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3453 search_args, templateUrlService->search_terms_data(), &post_content));
3454 [self addSelectedTabWithURL:result
3455 postData:&post_content
3456 transition:ui::PAGE_TRANSITION_TYPED];
3457}
3458
3459// Saves the image at the given URL on the system's album. The referrer is used
3460// to download the image.
3461- (void)saveImageAtURL:(const GURL&)url
3462 referrer:(const web::Referrer&)referrer {
3463 DCHECK(url.is_valid());
3464
Gauthier Ambard929699412018-01-02 10:05:413465 image_fetcher::IOSImageDataFetcherCallback callback =
3466 ^(NSData* data, const image_fetcher::RequestMetadata& metadata) {
3467 [self.imageSaver saveImageData:data withMetadata:metadata];
3468 };
Mark Cogan776e0282018-01-02 09:00:063469 _imageFetcher->FetchImageDataWebpDecoded(
3470 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3471 web::PolicyForNavigation(url, referrer));
3472}
3473
Kurt Horimoto62e97c72017-11-03 19:51:473474#pragma mark - LegacyFullscreenControllerDelegate methods
sdefresnee65fd872016-12-19 13:38:133475
Mark Cogan849244ee2017-12-29 15:57:193476// TODO(crbug.com/798064): Remove these methods and their helpers once the
3477// fullscreen migration is complete.
[email protected]a08e2bfb2017-11-24 19:16:453478- (void)redrawHeader {
3479 for (HeaderDefinition* header in self.headerViews) {
3480 [header.view setNeedsLayout];
3481 }
3482}
3483
Mark Cogan849244ee2017-12-29 15:57:193484- (CGFloat)headerHeightForLegacyFullscreen {
3485 return self.headerHeight;
sdefresnee65fd872016-12-19 13:38:133486}
3487
3488- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193489 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133490}
3491
3492- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453493 NSArray<HeaderDefinition*>* headers = [self headerViews];
3494 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133495 return 0.0;
3496
3497 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3498 // API documentation.
Sylvain Defresnef5d2d952017-11-14 11:15:313499 if (_insertedTabWasPrerenderedTab)
Mark Cogan849244ee2017-12-29 15:57:193500 return self.headerHeight;
sdefresnee65fd872016-12-19 13:38:133501
3502 UIView* topHeader = headers[0].view;
Mark Cogan849244ee2017-12-29 15:57:193503 return -(topHeader.frame.origin.y - self.headerOffset);
sdefresnee65fd872016-12-19 13:38:133504}
3505
Kurt Horimoto62e97c72017-11-03 19:51:473506- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133507 drawHeaderViewFromOffset:(CGFloat)headerOffset
3508 animate:(BOOL)animate {
Mark Cogan5bd86ba2017-12-28 14:32:383509 if ([self.sideSwipeController inSwipe])
sdefresnee65fd872016-12-19 13:38:133510 return;
3511
3512 CGRect footerFrame = CGRectZero;
3513 UIView* footer = nil;
3514 // Only animate the voice search bar if the tab is a voice search results tab.
3515 if ([_model currentTab].isVoiceSearchResultsTab) {
3516 footer = [self footerView];
3517 footerFrame = footer.frame;
3518 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3519 }
3520
stkhapugin952ecef2017-04-11 12:11:453521 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133522 void (^block)(void) = ^{
3523 [self setFramesForHeaders:headers atOffset:headerOffset];
3524 footer.frame = footerFrame;
3525 };
3526 void (^completion)(BOOL) = ^(BOOL finished) {
3527 [self fullScreenController:fullScreenController
3528 headerAnimationCompleted:finished
3529 offset:headerOffset];
3530 };
3531 if (animate) {
Kurt Horimoto62e97c72017-11-03 19:51:473532 [UIView
3533 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3534 delay:0.0
3535 options:UIViewAnimationOptionBeginFromCurrentState
3536 animations:block
3537 completion:completion];
sdefresnee65fd872016-12-19 13:38:133538 } else {
3539 block();
3540 completion(YES);
3541 }
3542}
3543
Kurt Horimoto62e97c72017-11-03 19:51:473544- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133545 drawHeaderViewFromOffset:(CGFloat)headerOffset
3546 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3547 changeTopContentPadding:(BOOL)changeTopContentPadding
3548 scrollingToOffset:(CGFloat)contentOffset {
3549 DCHECK(webViewProxy);
Mark Cogan5bd86ba2017-12-28 14:32:383550 if ([self.sideSwipeController inSwipe])
sdefresnee65fd872016-12-19 13:38:133551 return;
3552
3553 CGRect footerFrame;
3554 UIView* footer = nil;
3555 // Only animate the voice search bar if the tab is a voice search results tab.
3556 if ([_model currentTab].isVoiceSearchResultsTab) {
3557 footer = [self footerView];
3558 footerFrame = footer.frame;
3559 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3560 }
3561
stkhapugin952ecef2017-04-11 12:11:453562 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133563 void (^block)(void) = ^{
3564 [self setFramesForHeaders:headers atOffset:headerOffset];
3565 footer.frame = footerFrame;
3566 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3567 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3568 if (changeTopContentPadding)
3569 webViewProxy.topContentPadding = contentOffset;
3570 };
3571 void (^completion)(BOOL) = ^(BOOL finished) {
3572 [self fullScreenController:fullScreenController
3573 headerAnimationCompleted:finished
3574 offset:headerOffset];
3575 };
3576
Kurt Horimoto62e97c72017-11-03 19:51:473577 [UIView
3578 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3579 delay:0.0
3580 options:UIViewAnimationOptionBeginFromCurrentState
3581 animations:block
3582 completion:completion];
sdefresnee65fd872016-12-19 13:38:133583}
3584
Mark Cogan849244ee2017-12-29 15:57:193585#pragma mark - LegacyFullscreenControllerDelegate helpers
sdefresnee65fd872016-12-19 13:38:133586
Mark Cogan849244ee2017-12-29 15:57:193587// Returns the y coordinate for the footer's frame when animating the footer
3588// in/out of fullscreen.
3589- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3590 UIView* footer = [self footerView];
3591 CGFloat headerHeight = [self headerHeight];
3592 if (!footer || headerHeight == 0)
3593 return 0.0;
3594
3595 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3596 CGFloat offset = headerOffset * footerHeight / headerHeight;
3597 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3598}
3599
3600// Called when the animation for setting the header view's offset is finished.
3601// |completed| should indicate if the animation finished completely or was
3602// interrupted. |offset| should indicate the header offset after the animation.
3603// |dragged| should indicate if the header moved due to the user dragging.
3604- (void)fullScreenController:(LegacyFullscreenController*)controller
3605 headerAnimationCompleted:(BOOL)completed
3606 offset:(CGFloat)offset {
3607 if (completed)
3608 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133609}
3610
sdefresnee65fd872016-12-19 13:38:133611#pragma mark - OverscrollActionsControllerDelegate methods.
3612
3613- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053614 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133615 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053616 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383617 [self.dispatcher
3618 openNewTab:[OpenNewTabCommand
3619 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133620 break;
rohitrao922b7111c2017-01-03 14:31:053621 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243622 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133623 break;
Kurt Horimoto4ce19322017-11-28 19:10:473624 case OverscrollAction::REFRESH:
3625 [self reload];
sdefresnee65fd872016-12-19 13:38:133626 break;
rohitrao922b7111c2017-01-03 14:31:053627 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133628 NOTREACHED();
3629 break;
3630 }
3631}
3632
3633- (BOOL)shouldAllowOverscrollActions {
3634 return YES;
3635}
3636
3637- (UIView*)headerView {
Gauthier Ambard83207452018-01-04 07:51:393638 return self.primaryToolbarCoordinator.toolbarViewController.view;
sdefresnee65fd872016-12-19 13:38:133639}
3640
3641- (UIView*)toolbarSnapshotView {
Gauthier Ambard83207452018-01-04 07:51:393642 return [self.primaryToolbarCoordinator.toolbarViewController.view
sczs42f7f7482017-11-08 01:13:273643 snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133644}
3645
3646- (CGFloat)overscrollActionsControllerHeaderInset:
3647 (OverscrollActionsController*)controller {
3648 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
Mark Cogan849244ee2017-12-29 15:57:193649 return self.headerHeight;
sdefresnee65fd872016-12-19 13:38:133650 else
3651 return 0;
3652}
3653
3654- (CGFloat)overscrollHeaderHeight {
Mark Cogan849244ee2017-12-29 15:57:193655 return self.headerHeight + StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:133656}
3657
3658#pragma mark - TabSnapshottingDelegate methods.
3659
Sylvain Defresne0a86dd22017-12-19 14:37:503660- (UIEdgeInsets)snapshotEdgeInsetsForTab:(Tab*)tab {
sdefresnee65fd872016-12-19 13:38:133661 CGFloat headerHeight = [self headerHeightForTab:tab];
3662 id nativeController = [self nativeControllerForTab:tab];
3663 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3664 headerHeight += [nativeController toolbarHeight];
Sylvain Defresne0a86dd22017-12-19 14:37:503665 return UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
sdefresnee65fd872016-12-19 13:38:133666}
3667
Mark Cogan849244ee2017-12-29 15:57:193668#pragma mark - NewTabPageControllerObserver methods.
sdefresnee65fd872016-12-19 13:38:133669
3670- (void)selectedPanelDidChange {
3671 [self updateToolbar];
3672}
3673
3674#pragma mark - CRWNativeContentProvider methods
3675
Mark Cogan849244ee2017-12-29 15:57:193676// TODO(crbug.com/725241): This method is deprecated and should be removed by
3677// switching to DidFinishnavigation.
sdefresnee65fd872016-12-19 13:38:133678- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3679 withError:(NSError*)error
3680 isPost:(BOOL)isPost {
3681 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173682 [[ErrorPageContent alloc] initWithLoader:self
3683 browserState:self.browserState
3684 url:url
3685 error:error
3686 isPost:isPost
3687 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133688 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3689 return errorPageContent;
3690}
3691
3692- (BOOL)hasControllerForURL:(const GURL&)url {
Marti Wong64481ec2017-10-31 03:38:003693 base::StringPiece host = url.host_piece();
olivierrobin5c861c22017-04-07 15:56:453694 if (host == kChromeUIOfflineHost) {
3695 // Only allow offline URL that are fully specified.
3696 return reading_list::IsOfflineURLValid(
3697 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3698 }
sdefresnee65fd872016-12-19 13:38:133699
Justin Cohen8679e852017-08-14 16:35:253700 if (host == kChromeUIBookmarksHost) {
Marti Wong64481ec2017-10-31 03:38:003701 return IsBookmarksHostEnabled();
Justin Cohen8679e852017-08-14 16:35:253702 }
3703
3704 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133705}
3706
olivierrobind43eecb2017-01-27 20:35:263707- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3708 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133709 DCHECK(url.SchemeIs(kChromeUIScheme));
3710
3711 id<CRWNativeContent> nativeController = nil;
Marti Wong64481ec2017-10-31 03:38:003712 base::StringPiece url_host = url.host_piece();
Justin Cohen49715952017-08-22 14:12:193713 if (url_host == kChromeUINewTabHost ||
Marti Wong64481ec2017-10-31 03:38:003714 (url_host == kChromeUIBookmarksHost && IsBookmarksHostEnabled())) {
Gauthier Ambardd8890452017-09-29 12:07:463715 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3716 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3717 if (@available(iOS 11.0, *)) {
3718 safeAreaInset = self.view.safeAreaInsets;
3719 }
3720 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3721
sdefresnee65fd872016-12-19 13:38:133722 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173723 [[NewTabPageController alloc] initWithUrl:url
3724 loader:self
Gauthier Ambard83207452018-01-04 07:51:393725 focuser:self.primaryToolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173726 ntpObserver:self
3727 browserState:_browserState
3728 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463729 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453730 tabModel:_model
justincohen75011c32017-04-28 16:31:393731 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463732 dispatcher:self.dispatcher
3733 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133734 pageController.swipeRecognizerProvider = self.sideSwipeController;
3735
3736 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233737 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133738
Marti Wong64481ec2017-10-31 03:38:003739 if (IsBookmarksHostEnabled()) {
sdefresnee65fd872016-12-19 13:38:133740 // New Tab Page can have multiple panels. Each panel is addressable
3741 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3742 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3743 // the Bookmark Manager, etc.
3744 // The utility functions NewTabPage::IdentifierFromFragment() and
3745 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3746 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3747 // without changing the URL since the URL may be chrome://bookmarks/#123.
3748 // If the URL is chrome://newtab/, pre-select the panel based on the
3749 // #fragment.
3750 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233751 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133752 : NewTabPage::IdentifierFromFragment(url.ref());
3753 }
3754 [pageController selectPanel:panelType];
3755 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453756 } else if (url_host == kChromeUIOfflineHost &&
3757 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133758 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173759 [[OfflinePageNativeContent alloc] initWithLoader:self
3760 browserState:_browserState
3761 webState:webState
3762 URL:url];
sdefresnee65fd872016-12-19 13:38:133763 [self setOverScrollActionControllerToStaticNativeContent:
3764 staticNativeController];
3765 nativeController = staticNativeController;
3766 } else if (url_host == kChromeUIExternalFileHost) {
3767 // Return an instance of the |ExternalFileController| only if the file is
3768 // still in the sandbox.
3769 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3770 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173771 nativeController =
3772 [[ExternalFileController alloc] initWithURL:url
3773 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133774 }
peterlaurens44615d02017-05-23 20:23:093775 } else if (url_host == kChromeUICrashHost) {
3776 // There is no native controller for kChromeUICrashHost, it is instead
3777 // handled as any other renderer crash by the SadTabTabHelper.
3778 // nativeController must be set to nil to prevent defaulting to a
3779 // PageNotAvailableController.
3780 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133781 } else {
3782 DCHECK(![self hasControllerForURL:url]);
3783 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173784 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133785 }
3786 // If a native controller is vended before its tab is added to the tab model,
3787 // use the temporary key and add it under the new tab's tabId in the
3788 // TabModelObserver callback. This happens:
3789 // - when there is no current tab (occurs when vending the NTP controller for
3790 // the first tab that is opened),
3791 // - when the current tab's url doesn't match |url| (occurs when a native
3792 // controller is opened in a new tab)
3793 // - when the current tab's url matches |url| and there is already a native
3794 // controller of the appropriate type vended to it (occurs when a native
3795 // controller is opened in a new tab from a tab with a matching URL, e.g.
3796 // opening an NTP when an NTP is already displayed in the current tab).
3797 // For normal page loads, history navigations, tab restorations, and crash
3798 // recoveries, the tab will already exist in the tab model and the tabId can
3799 // be used as the native controller key.
3800 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3801 // that native controllers vended here always correspond to the current tab.
3802 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193803 if (!currentTab.webState ||
3804 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443805 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133806 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443807 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133808 }
sdefresnee65fd872016-12-19 13:38:133809 return nativeController;
3810}
3811
edchin2c6af4a2017-12-05 02:16:493812- (id<CRWNativeContent>)controllerForUnhandledContentAtURL:(const GURL&)URL
3813 webState:
3814 (web::WebState*)webState {
Eugene But87a09532017-12-08 20:02:053815 LegacyDownloadManagerController* downloadController =
3816 [[LegacyDownloadManagerController alloc] initWithWebState:webState
edchin2c6af4a2017-12-05 02:16:493817 downloadURL:URL
3818 baseViewController:self];
3819 [downloadController start];
3820 return downloadController;
3821}
3822
sdefresnee65fd872016-12-19 13:38:133823#pragma mark - DialogPresenterDelegate methods
3824
3825- (void)dialogPresenter:(DialogPresenter*)presenter
3826 willShowDialogForWebState:(web::WebState*)webState {
3827 for (Tab* iteratedTab in self.tabModel) {
3828 if ([iteratedTab webState] == webState) {
3829 self.tabModel.currentTab = iteratedTab;
3830 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3831 break;
3832 }
3833 }
3834}
3835
Kurt Horimoto06b94252017-12-08 19:45:593836#pragma mark - FullscreenUIElement methods
3837
3838- (void)updateForFullscreenProgress:(CGFloat)progress {
3839 [self updateHeadersForFullscreenProgress:progress];
3840 [self updateFootersForFullscreenProgress:progress];
3841 [self updateContentViewTopPaddingForFullscreenProgress:progress];
3842}
3843
3844- (void)updateForFullscreenEnabled:(BOOL)enabled {
3845 if (!enabled)
3846 [self updateForFullscreenProgress:1.0];
3847}
3848
3849- (void)finishFullscreenScrollWithAnimator:
3850 (FullscreenScrollEndAnimator*)animator {
3851 BOOL showingToolbar = animator.finalProgress > animator.startProgress;
3852 CGFloat finalProgress = animator.finalProgress;
3853 // WKWebView does not re-render its content until its model layer's bounds
3854 // have been updated at the end of the animation. If the animator is going
3855 // to hide the toolbar, update the content view's top padding early so that
3856 // content is correctly rendered behind the toolbar that's being animated
3857 // away.
3858 if (!showingToolbar)
3859 [self updateContentViewTopPaddingForFullscreenProgress:finalProgress];
3860 [animator addAnimations:^{
3861 [self updateHeadersForFullscreenProgress:finalProgress];
3862 [self updateFootersForFullscreenProgress:finalProgress];
3863 }];
3864 // If the toolbar is being animated to become visible, update the content view
3865 // top padding in the completion block so that fixed-position elements can be
3866 // properly laid out in the new viewport.
3867 if (showingToolbar) {
3868 __weak FullscreenScrollEndAnimator* weakAnimator = animator;
3869 [animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
3870 [self updateContentViewTopPaddingForFullscreenProgress:
3871 [weakAnimator progressForAnimatingPosition:finalPosition]];
3872 }];
3873 }
3874}
3875
3876#pragma mark - FullscreenUIElement helpers
3877
3878// Translates the header views up and down according to |progress|, where a
3879// progress of 1.0 fully shows the headers and a progress of 0.0 fully hides
3880// them.
3881- (void)updateHeadersForFullscreenProgress:(CGFloat)progress {
3882 [self setFramesForHeaders:[self headerViews]
3883 atOffset:(1.0 - progress) * [self toolbarHeight]];
3884}
3885
3886// Translates the footer view up and down according to |progress|, where a
3887// progress of 1.0 fully shows the footer and a progress of 0.0 fully hides it.
3888- (void)updateFootersForFullscreenProgress:(CGFloat)progress {
3889 if (![_model currentTab].isVoiceSearchResultsTab)
3890 return;
3891
3892 UIView* footerView = [self footerView];
3893 DCHECK(footerView);
3894 CGRect frame = footerView.frame;
3895 frame.origin.y = CGRectGetMaxY(footerView.superview.bounds) -
3896 progress * CGRectGetHeight(frame);
3897 footerView.frame = frame;
3898}
3899
3900// Updates the top padding of the web view proxy. This either resets the frame
3901// of the WKWebView or the contentInsets of the WKWebView's UIScrollView,
3902// depending on the the proxy's |shouldUseInsetForTopPadding| property.
3903- (void)updateContentViewTopPaddingForFullscreenProgress:(CGFloat)progress {
3904 if (self.currentWebState) {
3905 self.currentWebState->GetWebViewProxy().topContentPadding =
3906 progress * [self toolbarHeight];
3907 }
3908}
3909
sdefresnee65fd872016-12-19 13:38:133910#pragma mark - KeyCommandsPlumbing
3911
3912- (BOOL)isOffTheRecord {
3913 return _isOffTheRecord;
3914}
3915
3916- (NSUInteger)tabsCount {
3917 return [_model count];
3918}
3919
lpromero47ea8862017-01-13 17:51:063920- (BOOL)canGoBack {
3921 return [_model currentTab].canGoBack;
3922}
3923
3924- (BOOL)canGoForward {
3925 return [_model currentTab].canGoForward;
3926}
3927
sdefresnee65fd872016-12-19 13:38:133928- (void)focusTabAtIndex:(NSUInteger)index {
3929 if ([_model count] > index) {
3930 [_model setCurrentTab:[_model tabAtIndex:index]];
3931 }
3932}
3933
3934- (void)focusNextTab {
3935 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3936 NSInteger modelCount = [_model count];
3937 if (currentTabIndex < modelCount - 1) {
3938 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3939 [_model setCurrentTab:nextTab];
3940 } else {
3941 [_model setCurrentTab:[_model tabAtIndex:0]];
3942 }
3943}
3944
3945- (void)focusPreviousTab {
3946 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3947 if (currentTabIndex > 0) {
3948 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3949 [_model setCurrentTab:previousTab];
3950 } else {
3951 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3952 [_model setCurrentTab:lastTab];
3953 }
3954}
3955
3956- (void)reopenClosedTab {
3957 sessions::TabRestoreService* const tabRestoreService =
3958 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3959 if (!tabRestoreService || tabRestoreService->entries().empty())
3960 return;
3961
3962 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3963 tabRestoreService->entries().front();
3964 // Only handle the TAB type.
3965 if (entry->type != sessions::TabRestoreService::TAB)
3966 return;
3967
Mark Cogandfcdea72017-07-18 13:47:383968 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133969 TabRestoreServiceDelegateImplIOS* const delegate =
3970 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3971 _browserState);
3972 tabRestoreService->RestoreEntryById(delegate, entry->id,
3973 WindowOpenDisposition::CURRENT_TAB);
3974}
3975
Kurt Horimotoe9b6002c2017-12-04 23:19:193976#pragma mark - MainContentUI
3977
3978- (MainContentUIState*)mainContentUIState {
3979 return _mainContentUIUpdater.state;
3980}
3981
Mark Cogan776e0282018-01-02 09:00:063982#pragma mark - UrlLoader (Public)
Mark Cogan5bd86ba2017-12-28 14:32:383983
sdefresnee65fd872016-12-19 13:38:133984- (void)loadURL:(const GURL&)url
3985 referrer:(const web::Referrer&)referrer
3986 transition:(ui::PageTransition)transition
3987 rendererInitiated:(BOOL)rendererInitiated {
3988 [[OmniboxGeolocationController sharedInstance]
3989 locationBarDidSubmitURL:url
3990 transition:transition
3991 browserState:_browserState];
3992
3993 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3994 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3995 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3996 }
3997
3998 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243999 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:134000 // restart.
4001 if (url.host() == kChromeUIBrowserCrashHost) {
4002 [self induceBrowserCrash];
4003 // In debug the app can continue working even after the CHECK. Adding a
4004 // return avoids the crash url to be added to the history.
4005 return;
4006 }
4007
Danyao Wang85389a82017-10-25 18:56:274008 bool typed_or_generated_transition =
4009 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
4010 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
4011
Rohit Rao44f204302017-08-10 14:49:544012 PrerenderService* prerenderService =
4013 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4014 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:594015 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:544016 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:594017 DCHECK(newWebState);
4018
sdefresnee65fd872016-12-19 13:38:134019 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:594020 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:134021 DCHECK(oldTab);
4022 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:594023
kkhorimotod804c5732017-03-15 23:44:524024 bool canPruneItems =
4025 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:594026
Sylvain Defresne17b8aa42017-12-21 16:17:174027 if (canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:524028 [newTab navigationManager]->CopyStateFromAndPrune(
4029 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:594030
Sylvain Defresnef5d2d952017-11-14 11:15:314031 // Set _insertedTabWasPrerenderedTab to YES while the Tab is inserted
4032 // so that the correct toolbar height is used and animation are played.
4033 _insertedTabWasPrerenderedTab = YES;
sdefresne2c600c52017-04-04 16:49:594034 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
4035 std::move(newWebState));
Sylvain Defresnef5d2d952017-11-14 11:15:314036 _insertedTabWasPrerenderedTab = NO;
sdefresnee65fd872016-12-19 13:38:134037
Sylvain Defresne17b8aa42017-12-21 16:17:174038 if ([newTab loadFinished]) {
4039 // If the page has finished loading, take a snapshot. If the page is
4040 // still loading, do nothing, as the tab helper will automatically take
4041 // a snapshot once the load completes.
4042 SnapshotTabHelper::FromWebState(newTab.webState)
4043 ->UpdateSnapshot(
4044 /*with_overlays=*/true, /*visible_frame_only=*/true);
4045 }
4046
Danyao Wang85389a82017-10-25 18:56:274047 if (typed_or_generated_transition) {
4048 LoadTimingTabHelper::FromWebState(newTab.webState)
4049 ->DidPromotePrerenderTab();
4050 }
sdefresnee65fd872016-12-19 13:38:134051
sdefresne2f7781c2017-03-02 19:12:464052 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:134053 return;
4054 }
4055 }
4056
4057 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:544058 if (prerenderService) {
4059 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:134060 }
4061
sdefresnee65fd872016-12-19 13:38:134062 // Some URLs are not allowed while in incognito. If we are in incognito and
4063 // load a disallowed URL, instead create a new tab not in the incognito state.
4064 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
4065 [self webPageOrderedOpen:url
4066 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134067 inIncognito:NO
4068 inBackground:NO
4069 appendTo:kCurrentTab];
4070 return;
4071 }
4072
Danyao Wang85389a82017-10-25 18:56:274073 if (typed_or_generated_transition) {
4074 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
4075 ->DidInitiatePageLoad();
4076 }
4077
mrefaata84d5a02017-06-08 17:13:294078 // If this is a reload initiated from the omnibox.
4079 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
4080 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
4081 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
4082 [[_model currentTab] navigationManager]->Reload(
4083 web::ReloadType::NORMAL, true /* check_for_repost */);
4084 return;
4085 }
4086
sdefresnee65fd872016-12-19 13:38:134087 web::NavigationManager::WebLoadParams params(urlToLoad);
4088 params.referrer = referrer;
4089 params.transition_type = transition;
4090 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104091 Tab* currentTab = [_model currentTab];
4092 DCHECK(currentTab);
4093 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4094 currentTab.navigationManager->LoadURLWithParams(params);
4095 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4096 // to be animated on screen because the transition animator will handle the
4097 // animations. When a Tab stops being a voice search Tab, the voice search
4098 // bar should be animated away.
4099 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4100 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134101}
4102
4103- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544104 PrerenderService* prerenderService =
4105 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4106 if (prerenderService) {
4107 prerenderService->CancelPrerender();
4108 }
sdefresnee65fd872016-12-19 13:38:134109 DCHECK([_model currentTab]);
Mark Cogan776e0282018-01-02 09:00:064110 if (self.currentWebState)
4111 self.currentWebState->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134112}
4113
sdefresnee65fd872016-12-19 13:38:134114// Load a new URL on a new page/tab.
4115- (void)webPageOrderedOpen:(const GURL&)URL
4116 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134117 inBackground:(BOOL)inBackground
4118 appendTo:(OpenPosition)appendTo {
4119 Tab* adjacentTab = nil;
4120 if (appendTo == kCurrentTab)
4121 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354122 [_model insertTabWithURL:URL
4123 referrer:referrer
4124 transition:ui::PAGE_TRANSITION_LINK
4125 opener:adjacentTab
4126 openedByDOM:NO
4127 atIndex:TabModelConstants::kTabPositionAutomatically
4128 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134129}
4130
4131- (void)webPageOrderedOpen:(const GURL&)url
4132 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134133 inIncognito:(BOOL)inIncognito
4134 inBackground:(BOOL)inBackground
4135 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004136 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234137 // feature_engagement::Tracker based on |inIncognito|.
4138 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004139
sdefresnee65fd872016-12-19 13:38:134140 if (inIncognito == _isOffTheRecord) {
4141 [self webPageOrderedOpen:url
4142 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134143 inBackground:inBackground
4144 appendTo:appendTo];
4145 return;
4146 }
4147 // When sending an open command that switches modes, ensure the tab
4148 // ends up appended to the end of the model, not just next to what is
4149 // currently selected in the other mode. This is done with the |append|
4150 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274151 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134152 initWithURL:url
4153 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134154 inIncognito:inIncognito
4155 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274156 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154157 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134158}
4159
4160- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054161 WebStateList* webStateList = [_model webStateList];
4162 webStateList->ReplaceWebStateAt(
4163 webStateList->active_index(),
4164 session_util::CreateWebStateWithNavigationEntries(
4165 [_model browserState], sessionTab->current_navigation_index,
4166 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134167}
4168
Mark Cogan776e0282018-01-02 09:00:064169#pragma mark - UrlLoader helpers
4170
4171// Induce an intentional crash in the browser process.
4172- (void)induceBrowserCrash {
4173 CHECK(false);
4174 // Call another function, so that the above CHECK can't be tail-call
4175 // optimized. This ensures that this method's name will show up in the stack
4176 // for easier identification.
4177 CHECK(true);
sdefresnee65fd872016-12-19 13:38:134178}
4179
Mark Cogan776e0282018-01-02 09:00:064180#pragma mark - WebToolbarDelegate (Public)
sdefresnee65fd872016-12-19 13:38:134181
Gauthier Ambard45963ce22017-11-17 15:49:114182- (void)locationBarDidBecomeFirstResponder {
sdefresnee65fd872016-12-19 13:38:134183 if (_locationBarHasFocus)
4184 return; // TODO(crbug.com/244366): This should not be necessary.
4185 _locationBarHasFocus = YES;
4186 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524187 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134188 object:nil];
Mark Cogan5bd86ba2017-12-28 14:32:384189 [self.sideSwipeController setEnabled:NO];
sdefresnee65fd872016-12-19 13:38:134190 if ([[_model currentTab].webController wantsKeyboardShield]) {
Mark Cogan5bd86ba2017-12-28 14:32:384191 [[self view] insertSubview:self.typingShield aboveSubview:_contentArea];
4192 [self.typingShield setAlpha:0.0];
4193 [self.typingShield setHidden:NO];
sdefresnee65fd872016-12-19 13:38:134194 [UIView animateWithDuration:0.3
4195 animations:^{
Mark Cogan5bd86ba2017-12-28 14:32:384196 [self.typingShield setAlpha:1.0];
sdefresnee65fd872016-12-19 13:38:134197 }];
4198 }
4199 [[OmniboxGeolocationController sharedInstance]
4200 locationBarDidBecomeFirstResponder:_browserState];
4201}
4202
Gauthier Ambard45963ce22017-11-17 15:49:114203- (void)locationBarDidResignFirstResponder {
sdefresnee65fd872016-12-19 13:38:134204 if (!_locationBarHasFocus)
4205 return; // TODO(crbug.com/244366): This should not be necessary.
4206 _locationBarHasFocus = NO;
Mark Cogan5bd86ba2017-12-28 14:32:384207 [self.sideSwipeController setEnabled:YES];
sdefresnee65fd872016-12-19 13:38:134208 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524209 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134210 object:nil];
4211 [UIView animateWithDuration:0.3
4212 animations:^{
Mark Cogan5bd86ba2017-12-28 14:32:384213 [self.typingShield setAlpha:0.0];
sdefresnee65fd872016-12-19 13:38:134214 }
4215 completion:^(BOOL finished) {
4216 // This can happen if one quickly resigns the omnibox and then taps
4217 // on the omnibox again during this animation. If the animation is
4218 // interrupted and the toolbar controller is first responder, it's safe
Mark Cogan5bd86ba2017-12-28 14:32:384219 // to assume |self.typingShield| shouldn't be hidden here.
Gauthier Ambard83207452018-01-04 07:51:394220 if (!finished &&
4221 [self.primaryToolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134222 return;
Mark Cogan5bd86ba2017-12-28 14:32:384223 [self.typingShield setHidden:YES];
sdefresnee65fd872016-12-19 13:38:134224 }];
4225 [[OmniboxGeolocationController sharedInstance]
4226 locationBarDidResignFirstResponder:_browserState];
4227
4228 // If a load was cancelled by an omnibox edit, but nothing is loading when
4229 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4230 if (_locationBarEditCancelledLoad) {
4231 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294232
4233 web::WebState* webState = [_model currentTab].webState;
4234 if (!_toolbarModelIOS->IsLoading() && webState)
4235 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4236 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134237 }
4238}
4239
Gauthier Ambard45963ce22017-11-17 15:49:114240- (void)locationBarBeganEdit {
sdefresnee65fd872016-12-19 13:38:134241 // On handsets, if a page is currently loading it should be stopped.
4242 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354243 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134244 _locationBarEditCancelledLoad = YES;
4245 }
4246}
4247
sdefresnee65fd872016-12-19 13:38:134248- (ToolbarModelIOS*)toolbarModelIOS {
4249 return _toolbarModelIOS.get();
4250}
4251
sdefresnee65fd872016-12-19 13:38:134252
Mark Cogan849244ee2017-12-29 15:57:194253#pragma mark - ToolsMenuConfigurationProvider
Peter Laurense0b80f12017-11-21 07:52:404254
4255- (void)prepareForToolsMenuPresentationByCoordinator:
4256 (ToolsMenuCoordinator*)coordinator {
4257 DCHECK(_browserState);
4258 DCHECK(self.visible || self.dismissingModal);
4259
4260 // Dismiss the omnibox (if open).
Gauthier Ambard83207452018-01-04 07:51:394261 [self.primaryToolbarCoordinator cancelOmniboxEdit];
Peter Laurense0b80f12017-11-21 07:52:404262 // Dismiss the soft keyboard (if open).
4263 [[_model currentTab].webController dismissKeyboard];
4264 // Dismiss Find in Page focus.
4265 [self updateFindBar:NO shouldFocus:NO];
4266
4267 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4268 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4269 }
4270}
4271
4272- (ToolsMenuConfiguration*)menuConfigurationForToolsMenuCoordinator:
4273 (ToolsMenuCoordinator*)coordinator {
4274 ToolsMenuConfiguration* configuration =
4275 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4276 baseViewController:self];
4277 configuration.requestStartTime = [NSDate date].timeIntervalSinceReferenceDate;
4278
4279 if ([_model count] == 0)
4280 [configuration setNoOpenedTabs:YES];
4281
4282 if (_isOffTheRecord)
4283 [configuration setInIncognito:YES];
4284
4285 if (!_readingListMenuNotifier) {
4286 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4287 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4288 _browserState)];
4289 }
4290
4291 feature_engagement::Tracker* engagementTracker =
4292 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4293 if (engagementTracker->ShouldTriggerHelpUI(
4294 feature_engagement::kIPHBadgedReadingListFeature)) {
4295 [configuration setShowReadingListNewBadge:YES];
4296 [configuration setEngagementTracker:engagementTracker];
4297 }
4298 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4299
4300 [configuration setUserAgentType:self.userAgentType];
4301
4302 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4303 [configuration setHighlightNewIncognitoTabCell:YES];
4304 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4305 }
4306
4307 return configuration;
4308}
4309
4310- (BOOL)shouldHighlightBookmarkButtonForToolsMenuCoordinator:
4311 (ToolsMenuCoordinator*)coordinator {
4312 return [_model currentTab] ? _toolbarModelIOS->IsCurrentTabBookmarked() : NO;
4313}
4314
4315- (BOOL)shouldShowFindBarForToolsMenuCoordinator:
4316 (ToolsMenuCoordinator*)coordinator {
4317 return [_model currentTab] ? self.canShowFindBar : NO;
4318}
4319
4320- (BOOL)shouldShowShareMenuForToolsMenuCoordinator:
4321 (ToolsMenuCoordinator*)coordinator {
4322 return [_model currentTab] ? self.canShowShareMenu : NO;
4323}
4324
4325- (BOOL)isTabLoadingForToolsMenuCoordinator:(ToolsMenuCoordinator*)coordinator {
4326 return ([_model currentTab] && !IsIPadIdiom()) ? _toolbarModelIOS->IsLoading()
4327 : NO;
4328}
4329
Mark Cogan6ebbde02017-07-07 12:50:134330#pragma mark - BrowserCommands
4331
4332- (void)goBack {
4333 [[_model currentTab] goBack];
4334}
4335
4336- (void)goForward {
4337 [[_model currentTab] goForward];
4338}
4339
Mark Coganb9aac6432017-07-07 13:26:354340- (void)stopLoading {
4341 [_model currentTab].webState->Stop();
4342}
4343
4344- (void)reload {
4345 web::WebState* webState = [_model currentTab].webState;
4346 if (webState) {
4347 // |check_for_repost| is true because the reload is explicitly initiated
4348 // by the user.
4349 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4350 true /* check_for_repost */);
4351 }
4352}
4353
Mark Cogan8e791022017-07-10 09:55:354354- (void)bookmarkPage {
4355 [self initializeBookmarkInteractionController];
4356 [_bookmarkInteractionController
4357 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064358 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354359}
4360
Mark Cogandfcdea72017-07-18 13:47:384361- (void)openNewTab:(OpenNewTabCommand*)command {
4362 if (self.isOffTheRecord != command.incognito) {
edchin3ab78ff2017-11-13 19:13:144363 // Must take a snapshot of the tab before we switch the incognito mode
4364 // because the currentTab will change after the switch.
4365 Tab* currentTab = [_model currentTab];
4366 if (currentTab) {
Sylvain Defresne17b8aa42017-12-21 16:17:174367 SnapshotTabHelper::FromWebState(currentTab.webState)
4368 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
edchin3ab78ff2017-11-13 19:13:144369 }
Mark Cogandfcdea72017-07-18 13:47:384370 // Not for this browser state, send it on its way.
4371 [self.dispatcher switchModesAndOpenNewTab:command];
4372 return;
4373 }
4374
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004375 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234376 // events to the feature_engagement::Tracker based on |command.userInitiated|
4377 // and |command.incognito|.
4378 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004379
Mark Cogandfcdea72017-07-18 13:47:384380 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4381 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494382 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4383 self.foregroundTabWasAddedCompletionBlock;
Louis Romero960a12f2017-11-30 19:08:594384 __weak BrowserViewController* weakSelf = self;
Mark Cogandfcdea72017-07-18 13:47:384385 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494386 if (oldForegroundTabWasAddedCompletionBlock) {
4387 oldForegroundTabWasAddedCompletionBlock();
4388 }
Mark Cogandfcdea72017-07-18 13:47:384389 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4390 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4391 if (offTheRecord) {
4392 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4393 timeDelta);
4394 } else {
4395 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4396 }
Louis Romero960a12f2017-11-30 19:08:594397 if (command.shouldFocusOmnibox) {
Mark Cogan5bd86ba2017-12-28 14:32:384398 [weakSelf.dispatcher focusOmnibox];
Louis Romero960a12f2017-11-30 19:08:594399 }
Mark Cogandfcdea72017-07-18 13:47:384400 };
4401
4402 [self setLastTapPoint:command];
Rohit Rao2e22b8d2017-11-07 19:54:544403 // When the tab switcher presentation experiment is enabled, the new tab can
4404 // be opened before BVC has been made visible onscreen. Test for this case by
4405 // checking if the parent container VC is currently in the process of being
4406 // presented.
4407 DCHECK(self.visible || self.dismissingModal ||
4408 (TabSwitcherPresentsBVCEnabled() &&
4409 self.parentViewController.isBeingPresented));
edchin3ab78ff2017-11-13 19:13:144410
4411 // In most cases, we want to take a snapshot of the current tab before opening
4412 // a new tab. However, if the current tab is not fully visible (did not finish
4413 // |-viewDidAppear:|, then we must not take an empty snapshot, replacing an
4414 // existing snapshot for the tab. This can happen when a new regular tab is
4415 // opened from an incognito tab. A different BVC is displayed, which may not
4416 // have enough time to finish appearing before a snapshot is requested.
Mark Cogandfcdea72017-07-18 13:47:384417 Tab* currentTab = [_model currentTab];
edchin3ab78ff2017-11-13 19:13:144418 if (currentTab && self.viewVisible) {
Sylvain Defresne17b8aa42017-12-21 16:17:174419 SnapshotTabHelper::FromWebState(currentTab.webState)
4420 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
Mark Cogandfcdea72017-07-18 13:47:384421 }
4422 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4423 transition:ui::PAGE_TRANSITION_TYPED];
4424}
4425
Mark Cogan123895002017-07-20 12:54:064426- (void)printTab {
4427 Tab* currentTab = [_model currentTab];
4428 // The UI should prevent users from printing non-printable pages. However, a
4429 // redirection to an un-printable page can happen before it is reflected in
4430 // the UI.
4431 if (![currentTab viewForPrinting]) {
4432 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364433 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064434 return;
4435 }
4436 DCHECK(_browserState);
4437 if (!_printController) {
4438 _printController = [[PrintController alloc]
4439 initWithContextGetter:_browserState->GetRequestContext()];
4440 }
4441 [_printController printView:[currentTab viewForPrinting]
4442 withTitle:[currentTab title]
4443 viewController:self];
4444}
4445
Mark Coganfa25b052017-07-20 17:31:034446- (void)addToReadingList:(ReadingListAddCommand*)command {
4447 [self addToReadingListURL:[command URL] title:[command title]];
4448}
4449
sczs3a8c8602017-08-01 20:14:084450- (void)showReadingList {
4451 _readingListCoordinator = [[ReadingListCoordinator alloc]
4452 initWithBaseViewController:self
4453 browserState:self.browserState
4454 loader:self];
4455
4456 [_readingListCoordinator start];
4457}
4458
Jean-François Geyelinedef9552017-08-07 09:56:564459- (void)preloadVoiceSearch {
4460 // Preload VoiceSearchController and views and view controllers needed
4461 // for voice search.
4462 [self ensureVoiceSearchControllerCreated];
4463 _voiceSearchController->PrepareToAppear();
4464}
4465
edchinc5720722017-08-14 22:06:314466#if !defined(NDEBUG)
4467- (void)viewSource {
4468 Tab* tab = [_model currentTab];
4469 DCHECK(tab);
4470 CRWWebController* webController = tab.webController;
4471 NSString* script = @"document.documentElement.outerHTML;";
4472 __weak Tab* weakTab = tab;
4473 __weak BrowserViewController* weakSelf = self;
4474 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4475 Tab* strongTab = weakTab;
4476 if (!strongTab)
4477 return;
4478 if (![result isKindOfClass:[NSString class]])
4479 result = @"Not an HTML page";
4480 std::string base64HTML;
4481 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4482 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194483 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314484 web::ReferrerPolicyDefault);
4485
4486 [[weakSelf tabModel]
4487 insertTabWithURL:URL
4488 referrer:referrer
4489 transition:ui::PAGE_TRANSITION_LINK
4490 opener:strongTab
4491 openedByDOM:YES
4492 atIndex:TabModelConstants::kTabPositionAutomatically
4493 inBackground:NO];
4494 };
4495 [webController executeJavaScript:script
4496 completionHandler:completionHandlerBlock];
4497}
4498#endif // !defined(NDEBUG)
4499
edchin2134c042017-08-18 13:57:354500// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4501- (void)showRateThisAppDialog {
4502 DCHECK(!_rateThisAppDialog);
4503
4504 // Store the current timestamp whenever this dialog is shown.
4505 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4506 base::Time::Now().ToInternalValue());
4507
Gregory Chatzinofff39ec5162017-10-05 20:28:534508 // iOS11 no longer supports the itms link to the app store. So, use a deep
4509 // link for iOS11 and the itms link for prior versions.
4510 NSURL* storeURL;
4511 if (base::ios::IsRunningOnIOS11OrLater()) {
4512 storeURL =
4513 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4514 @"google-chrome-the-fast-and-secure-web-browser/"
4515 @"id535886823?action=write-review")];
4516 } else {
4517 storeURL = [NSURL
4518 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4519 @"MZStore.woa/wa/"
4520 @"viewContentsUserReviews?type=Purple+Software&id="
4521 @"535886823&pt=9008&ct=rating")];
4522 }
edchin2134c042017-08-18 13:57:354523
4524 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014525 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354526
4527 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4528 [_rateThisAppDialog setAppStoreURL:storeURL];
4529 [_rateThisAppDialog setDelegate:self];
4530 [_rateThisAppDialog show];
4531}
4532
Gregory Chatzinoff3f40c1542017-08-30 07:50:044533- (void)showFindInPage {
4534 if (!self.canShowFindBar)
4535 return;
4536
4537 if (!_findBarController) {
4538 _findBarController =
4539 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4540 _findBarController.dispatcher = self.dispatcher;
4541 }
4542
4543 Tab* tab = [_model currentTab];
4544 DCHECK(tab);
4545 auto* helper = FindTabHelper::FromWebState(tab.webState);
4546 DCHECK(!helper->IsFindUIActive());
4547 helper->SetFindUIActive(true);
4548 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4549}
4550
4551- (void)closeFindInPage {
4552 __weak BrowserViewController* weakSelf = self;
4553 Tab* currentTab = [_model currentTab];
4554 if (currentTab) {
4555 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4556 [weakSelf updateFindBar:NO shouldFocus:NO];
4557 });
4558 }
4559}
4560
4561- (void)searchFindInPage {
4562 DCHECK([_model currentTab]);
4563 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4564 __weak BrowserViewController* weakSelf = self;
4565 helper->StartFinding(
4566 [_findBarController searchTerm], ^(FindInPageModel* model) {
4567 BrowserViewController* strongSelf = weakSelf;
4568 if (!strongSelf) {
4569 return;
4570 }
4571 [strongSelf->_findBarController updateResultsCount:model];
4572 });
4573
4574 if (!_isOffTheRecord)
4575 helper->PersistSearchTerm();
4576}
4577
4578- (void)findNextStringInPage {
4579 Tab* currentTab = [_model currentTab];
4580 DCHECK(currentTab);
4581 // TODO(crbug.com/603524): Reshow find bar if necessary.
4582 FindTabHelper::FromWebState(currentTab.webState)
4583 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4584 [_findBarController updateResultsCount:model];
4585 });
4586}
4587
4588- (void)findPreviousStringInPage {
4589 Tab* currentTab = [_model currentTab];
4590 DCHECK(currentTab);
4591 // TODO(crbug.com/603524): Reshow find bar if necessary.
4592 FindTabHelper::FromWebState(currentTab.webState)
4593 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4594 [_findBarController updateResultsCount:model];
4595 });
4596}
4597
edchinf84b2502017-08-31 21:30:454598- (void)showHelpPage {
4599 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4600 [self webPageOrderedOpen:helpUrl
4601 referrer:web::Referrer()
4602 inBackground:NO
4603 appendTo:kCurrentTab];
4604}
4605
edchinb59b5602017-09-01 15:00:204606- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104607 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204608 [self showAllBookmarks];
4609 } else {
4610 [self initializeBookmarkInteractionController];
4611 [_bookmarkInteractionController presentBookmarks];
4612 }
4613}
4614
edchin8ee0807d2017-09-01 23:52:474615- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104616 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474617 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4618 } else {
4619 if (!self.recentTabsCoordinator) {
4620 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4621 initWithBaseViewController:self];
4622 self.recentTabsCoordinator.loader = self;
4623 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4624 self.recentTabsCoordinator.browserState = _browserState;
4625 }
4626 [self.recentTabsCoordinator start];
4627 }
4628}
4629
Mark Cogan6de7e9a2017-09-06 12:57:214630- (void)requestDesktopSite {
4631 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4632}
4633
4634- (void)requestMobileSite {
4635 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4636}
4637
sdefresnee65fd872016-12-19 13:38:134638- (void)closeCurrentTab {
4639 Tab* currentTab = [_model currentTab];
4640 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4641 if (tabIndex == NSNotFound)
4642 return;
4643
jif7fed8122017-02-08 13:15:254644 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4645 // iPad.
sdefresnee65fd872016-12-19 13:38:134646 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4647 exitingPage.image =
Sylvain Defresne17b8aa42017-12-21 16:17:174648 SnapshotTabHelper::FromWebState(currentTab.webState)
4649 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
sdefresnee65fd872016-12-19 13:38:134650
4651 // Close the actual tab, and add its image as a subview.
4652 [_model closeTabAtIndex:tabIndex];
4653
4654 // Do not animate close in iPad.
4655 if (!IsIPadIdiom()) {
4656 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524657 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134658 exitingPage, 0, YES, IsPortrait(), ^{
4659 [exitingPage removeFromSuperview];
4660 });
4661 }
4662}
4663
Mark Cogan776e0282018-01-02 09:00:064664#pragma mark - ToolbarOwner (Public)
sdefresnee65fd872016-12-19 13:38:134665
Kurt Horimotoea429dd2017-11-28 02:24:304666- (CGFloat)toolbarHeight {
Mark Cogan849244ee2017-12-29 15:57:194667 return self.headerHeight;
Kurt Horimotoea429dd2017-11-28 02:24:304668}
4669
Gauthier Ambard04ddb512017-11-07 09:14:164670- (CGRect)toolbarFrame {
sczs42f7f7482017-11-08 01:13:274671 return _toolbarCoordinator.toolbarViewController.view.frame;
Gauthier Ambard04ddb512017-11-07 09:14:164672}
4673
Gauthier Ambard996d9b12017-11-06 09:39:214674- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
4675 id<ToolbarSnapshotProviding> toolbarSnapshotProvider = nil;
sczs42f7f7482017-11-08 01:13:274676 if (_toolbarCoordinator.toolbarViewController.view.hidden) {
Gauthier Ambard996d9b12017-11-06 09:39:214677 Tab* currentTab = [_model currentTab];
4678 if (currentTab.webState &&
4679 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
4680 // Use the native content controller's toolbar when the BVC's is hidden.
4681 id nativeController = [self nativeControllerForTab:currentTab];
4682 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4683 toolbarSnapshotProvider = [nativeController toolbarSnapshotProvider];
4684 }
4685 }
4686 } else {
4687 toolbarSnapshotProvider = _toolbarCoordinator;
4688 }
4689 return toolbarSnapshotProvider;
4690}
4691
sdefresnee65fd872016-12-19 13:38:134692#pragma mark - TabModelObserver methods
4693
4694// Observer method, tab inserted.
4695- (void)tabModel:(TabModel*)model
4696 didInsertTab:(Tab*)tab
4697 atIndex:(NSUInteger)modelIndex
4698 inForeground:(BOOL)fg {
4699 DCHECK(tab);
4700 [self installDelegatesForTab:tab];
4701
4702 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194703 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134704 }
4705}
4706
4707// Observer method, active tab changed.
4708- (void)tabModel:(TabModel*)model
4709 didChangeActiveTab:(Tab*)newTab
4710 previousTab:(Tab*)previousTab
4711 atIndex:(NSUInteger)index {
4712 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4713 // Currently this observer method is always called with a non-nil |newTab|,
4714 // but that may change in the future. Remove this DCHECK when it does.
4715 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274716 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304717 DCHECK(newTab.webState);
4718 infobars::InfoBarManager* infoBarManager =
4719 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134720 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
Mark Coganb12391c62017-12-28 13:35:064721
4722 // Dismiss the language selector, if any; this is a no-op when there's
4723 // no language selector presented.
4724 [_languageSelectionCoordinator dismissLanguageSelector];
sdefresnee65fd872016-12-19 13:38:134725 }
4726 [self updateVoiceSearchBarVisibilityAnimated:NO];
4727
Mohamad Ahmadi7d09ec32017-07-11 22:32:194728 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134729
Gauthier Ambard64396902017-12-08 10:14:584730 [self tabSelected:newTab notifyToolbar:YES];
sdefresnee65fd872016-12-19 13:38:134731}
4732
Mark Cogan5b494642018-01-02 12:55:464733- (void)tabModel:(TabModel*)model willStartLoadingTab:(Tab*)tab {
4734 // Stop any Find in Page searches and close the find bar when navigating to a
4735 // new page.
4736 [self closeFindInPage];
4737}
4738
sdefresnee65fd872016-12-19 13:38:134739- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4740 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4741 if (tab == [_model currentTab]) {
4742 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134743 }
4744}
4745
Mark Cogan5b494642018-01-02 12:55:464746- (void)tabModel:(TabModel*)model didStartLoadingTab:(Tab*)tab {
4747 if (tab == [_model currentTab]) {
4748 if (![self isTabNativePage:tab]) {
Gauthier Ambard83207452018-01-04 07:51:394749 [self.legacyToolbarCoordinator currentPageLoadStarted];
Mark Cogan5b494642018-01-02 12:55:464750 }
4751 [self updateVoiceSearchBarVisibilityAnimated:NO];
4752 }
4753}
4754
4755- (void)tabModel:(TabModel*)model
4756 didFinishLoadingTab:(Tab*)tab
Mark Cogan50a4c06a2018-01-02 18:26:204757 success:(BOOL)success {
Mark Cogan5b494642018-01-02 12:55:464758 [self tabLoadComplete:tab withSuccess:success];
Mark Coganfc591c8c2018-01-02 16:07:004759 if (IsIPadIdiom()) {
4760 UIUserInterfaceSizeClass sizeClass =
4761 self.view.window.traitCollection.horizontalSizeClass;
4762 [SizeClassRecorder pageLoadedWithHorizontalSizeClass:sizeClass];
4763 }
Mark Cogan5b494642018-01-02 12:55:464764}
4765
4766- (void)tabModel:(TabModel*)model
4767 newTabWillOpen:(Tab*)tab
4768 inBackground:(BOOL)background {
4769 DCHECK(tab);
4770 _temporaryNativeController = nil;
4771
4772 // When adding new tabs, check what kind of reminder infobar should
4773 // be added to the new tab. Try to add only one of them.
4774 // This check is done when a new tab is added either through the Tools Menu
4775 // "New Tab" or through "New Tab" in Stack View Controller. This method
4776 // is called after a new tab has added and finished initial navigation.
4777 // If this is added earlier, the initial navigation may end up clearing
4778 // the infobar(s) that are just added. See http://crbug/340250 for details.
4779 web::WebState* webState = tab.webState;
4780 DCHECK(webState);
4781
4782 infobars::InfoBarManager* infoBarManager =
4783 InfoBarManagerImpl::FromWebState(webState);
4784 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
4785 forTabId:[tab tabId]];
4786 if (!ReSignInInfoBarDelegate::Create(_browserState, tab,
4787 self /* id<SigninPresenter> */)) {
4788 DisplaySyncErrors(_browserState, tab, self /* id<SyncPresenter> */);
4789 }
4790
4791 // The rest of this function initiates the new tab animation, which is
4792 // phone-specific. Call the foreground tab added completion block; for
4793 // iPhones, this will get executed after the animation has finished.
4794 if (IsIPadIdiom()) {
4795 if (self.foregroundTabWasAddedCompletionBlock) {
4796 // This callback is called before webState is activated (on
4797 // kTabModelNewTabWillOpenNotification notification). Dispatch the
4798 // callback asynchronously to be sure the activation is complete.
4799 dispatch_async(dispatch_get_main_queue(), ^() {
4800 // Test existence again as the block may have been deleted.
4801 if (self.foregroundTabWasAddedCompletionBlock) {
4802 self.foregroundTabWasAddedCompletionBlock();
4803 self.foregroundTabWasAddedCompletionBlock = nil;
4804 }
4805 });
4806 }
4807 return;
4808 }
4809
4810 // Do nothing if browsing is currently suspended. The BVC will set everything
4811 // up correctly when browsing resumes.
4812 if (!self.visible || ![_model webUsageEnabled])
4813 return;
4814
4815 // Block that starts voice search at the end of new Tab animation if
4816 // necessary.
4817 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
4818 if (_startVoiceSearchAfterNewTabAnimation) {
4819 _startVoiceSearchAfterNewTabAnimation = NO;
4820 [self startVoiceSearchWithOriginView:nil];
4821 }
4822 };
4823
4824 self.inNewTabAnimation = YES;
4825 if (!background) {
4826 UIView* animationParentView = _contentArea;
4827 // Create the new page image, and load with the new tab snapshot except if
4828 // it is the NTP.
4829 CGFloat newPageOffset = 0;
4830 UIView* newPage;
4831 CGFloat offset = 0;
4832 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
4833 !_isOffTheRecord && !IsIPadIdiom()) {
4834 offset = 0;
4835 animationParentView = self.view;
4836 newPage = tab.view;
4837 newPage.userInteractionEnabled = NO;
4838 // Compute a frame for the new page by removing the status bar height from
4839 // the bounds of |self.view|.
4840 CGRect viewBounds, remainder;
4841 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
4842 CGRectMinYEdge);
4843 newPage.frame = viewBounds;
4844 } else {
4845 UIImageView* pageScreenshot = [self pageOpenCloseAnimationView];
4846 tab.view.frame = _contentArea.bounds;
4847 pageScreenshot.image = SnapshotTabHelper::FromWebState(tab.webState)
4848 ->UpdateSnapshot(/*with_overlays=*/true,
4849 /*visible_frame_only=*/true);
4850 newPage = pageScreenshot;
4851 offset =
4852 pageScreenshot.frame.size.height - pageScreenshot.image.size.height;
4853 }
4854 newPageOffset = newPage.frame.origin.y;
4855
4856 [animationParentView addSubview:newPage];
4857 CGPoint origin = [self lastTapPoint];
4858 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
4859 newPage, -newPageOffset, offset, origin, _isOffTheRecord, NULL, ^{
4860 [tab view].frame = _contentArea.bounds;
4861 newPage.userInteractionEnabled = YES;
4862 [newPage removeFromSuperview];
4863 self.inNewTabAnimation = NO;
4864 // Use the model's currentTab here because it is possible that it can
4865 // be reset to a new value before the new Tab animation finished (e.g.
4866 // if another Tab shows a dialog via |dialogPresenter|). However, that
4867 // tab's view hasn't been displayed yet because it was in a new tab
4868 // animation.
4869 Tab* currentTab = [_model currentTab];
4870 if (currentTab) {
4871 [self tabSelected:currentTab notifyToolbar:NO];
4872 }
4873 startVoiceSearchIfNecessaryBlock();
4874
4875 if (self.foregroundTabWasAddedCompletionBlock) {
4876 self.foregroundTabWasAddedCompletionBlock();
4877 self.foregroundTabWasAddedCompletionBlock = nil;
4878 }
4879 });
4880 } else {
4881 // SnapshotTabHelper::UpdateSnapshot will force a screen redraw, so take the
4882 // snapshot before adding the views needed for the background animation.
4883 Tab* topTab = [_model currentTab];
4884 UIImage* image =
4885 SnapshotTabHelper::FromWebState(topTab.webState)
4886 ->UpdateSnapshot(/*with_overlays=*/true,
4887 /*visible_frame_only=*/self.isToolbarOnScreen);
4888
4889 // The size of the |image| above can be wrong if the snapshot fails, grab
4890 // the correct size here.
4891 CGRect imageFrame = CGRectZero;
4892 if (self.isToolbarOnScreen) {
4893 imageFrame = UIEdgeInsetsInsetRect(
4894 _contentArea.bounds, [self snapshotEdgeInsetsForTab:topTab]);
4895 } else {
4896 imageFrame = [topTab.webState->GetView() bounds];
4897 }
4898
4899 // Add three layers in order on top of the contentArea for the animation:
4900 // 1. The black "background" screen.
4901 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
4902 InstallBackgroundInView(background);
4903 [_contentArea addSubview:background];
4904
4905 // 2. A CardView displaying the data from the current tab.
4906 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
4907 NSString* title = [topTab title];
4908 if (![title length])
4909 title = [topTab urlDisplayString];
4910 [topCard setTitle:title];
4911 [topCard setImage:image];
4912 [topCard setFavicon:nil];
4913
4914 favicon::FaviconDriver* faviconDriver =
4915 favicon::WebFaviconDriver::FromWebState(topTab.webState);
4916 if (faviconDriver && faviconDriver->FaviconIsValid()) {
4917 gfx::Image favicon = faviconDriver->GetFavicon();
4918 if (!favicon.IsEmpty())
4919 [topCard setFavicon:favicon.ToUIImage()];
4920 }
4921
4922 // 3. A new, blank CardView to represent the new tab being added.
4923 // Launch the new background tab animation.
4924 page_animation_util::AnimateNewBackgroundPageWithCompletion(
4925 topCard, [_contentArea frame], imageFrame, IsPortrait(), ^{
4926 [background removeFromSuperview];
4927 [topCard removeFromSuperview];
4928 self.inNewTabAnimation = NO;
4929 // Resnapshot the top card if it has its own toolbar, as the toolbar
4930 // will be captured in the new tab animation, but isn't desired for
4931 // the stack view snapshots.
4932 id nativeController = [self nativeControllerForTab:topTab];
4933 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4934 SnapshotTabHelper::FromWebState(topTab.webState)
4935 ->UpdateSnapshot(/*with_overlays=*/true,
4936 /*visible_frame_only=*/true);
4937 }
4938 startVoiceSearchIfNecessaryBlock();
4939 });
4940 // Reset the foreground tab completion block so that it can never be
4941 // called more than once regardless of foreground/background tab
4942 // appearances.
4943 self.foregroundTabWasAddedCompletionBlock = nil;
4944 }
4945}
4946
4947- (void)tabModel:(TabModel*)model didDeselectTab:(Tab*)tab {
4948 [tab wasHidden];
4949 [self dismissPopups];
4950}
4951
sdefresne49cf2862017-03-15 13:46:144952// Observer method, tab replaced.
4953- (void)tabModel:(TabModel*)model
4954 didReplaceTab:(Tab*)oldTab
4955 withTab:(Tab*)newTab
4956 atIndex:(NSUInteger)index {
4957 [self uninstallDelegatesForTab:oldTab];
4958 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264959
michaeldo79909fb2017-05-09 23:42:504960 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304961 infobars::InfoBarManager* infoBarManager = nullptr;
4962 if (newTab) {
4963 DCHECK(newTab.webState);
4964 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4965 }
michaeldo79909fb2017-05-09 23:42:504966 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4967 }
4968
Kurt Horimoto4d1f6962017-12-19 23:38:094969 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4970 if (self.active && model.currentTab == newTab)
kkhorimotofa0844cc2017-03-20 17:01:264971 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464972
4973 if (newTab)
4974 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144975}
4976
sdefresnee65fd872016-12-19 13:38:134977// A tab has been removed, remove its views from display if necessary.
4978- (void)tabModel:(TabModel*)model
4979 didRemoveTab:(Tab*)tab
4980 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144981 [self uninstallDelegatesForTab:tab];
4982
kkhorimoto496fdd72017-06-12 19:56:314983 // Cancel dialogs for |tab|'s WebState.
4984 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4985
sdefresnee65fd872016-12-19 13:38:134986 // Ignore changes while the tab stack view is visible (or while suspended).
4987 // The display will be refreshed when this view becomes active again.
4988 if (!self.visible || !model.webUsageEnabled)
4989 return;
4990
4991 // Remove the find bar for now.
4992 [self hideFindBarWithAnimation:NO];
4993}
4994
4995- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4996 if (tab == [model currentTab]) {
4997 [_contentArea displayContentView:nil];
Gauthier Ambard83207452018-01-04 07:51:394998 [self.legacyToolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:134999 }
5000
Mohamad Ahmadi7d09ec32017-07-11 22:32:195001 [_paymentRequestManager stopTrackingWebState:tab.webState];
5002
sdefresnee65fd872016-12-19 13:38:135003 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
5004 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:195005 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:135006 }
5007}
5008
5009// Called when the number of tabs changes. Update the toolbar accordingly.
5010- (void)tabModelDidChangeTabCount:(TabModel*)model {
5011 DCHECK(model == _model);
Gauthier Ambard83207452018-01-04 07:51:395012 [self.legacyToolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:135013}
5014
Mark Cogan849244ee2017-12-29 15:57:195015#pragma mark - UpgradeCenterClient
sdefresnee65fd872016-12-19 13:38:135016
5017- (void)showUpgrade:(UpgradeCenter*)center {
5018 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:275019 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:135020 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:305021 DCHECK(tab.webState);
5022 infobars::InfoBarManager* infoBarManager =
5023 InfoBarManagerImpl::FromWebState(tab.webState);
5024 DCHECK(infoBarManager);
5025 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:135026 }
5027}
5028
Mark Cogan80aa28d2017-11-30 13:11:345029#pragma mark - InfobarContainerStateDelegate
sdefresnee65fd872016-12-19 13:38:135030
Mark Cogan80aa28d2017-11-30 13:11:345031- (void)infoBarContainerStateDidChangeAnimated:(BOOL)animated {
sdefresnee65fd872016-12-19 13:38:135032 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
5033 DCHECK(infoBarContainerView);
5034 CGRect containerFrame = infoBarContainerView.frame;
5035 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
5036 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
5037 containerFrame.size.height = height;
5038 BOOL isViewVisible = self.visible;
5039 [UIView animateWithDuration:0.1
5040 animations:^{
5041 [infoBarContainerView setFrame:containerFrame];
5042 }
5043 completion:^(BOOL finished) {
5044 if (!isViewVisible)
5045 return;
5046 UIAccessibilityPostNotification(
5047 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
5048 }];
5049}
5050
Mark Cogan80aa28d2017-11-30 13:11:345051#pragma mark - UIGestureRecognizerDelegate
sdefresnee65fd872016-12-19 13:38:135052
5053// Always return yes, as this tap should work with various recognizers,
5054// including UITextTapRecognizer, UILongPressGestureRecognizer,
5055// UIScrollViewPanGestureRecognizer and others.
5056- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
5057 shouldRecognizeSimultaneouslyWithGestureRecognizer:
5058 (UIGestureRecognizer*)otherGestureRecognizer {
5059 return YES;
5060}
5061
5062// Tap gestures should only be recognized within |_contentArea|.
5063- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
5064 CGPoint location = [gesture locationInView:self.view];
5065
5066 // Only allow touches on descendant views of |_contentArea|.
5067 UIView* hitView = [self.view hitTest:location withEvent:nil];
5068 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
5069}
5070
Mark Cogan849244ee2017-12-29 15:57:195071#pragma mark - SideSwipeControllerDelegate
sdefresnee65fd872016-12-19 13:38:135072
5073- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
5074 DCHECK(!IsIPadIdiom());
5075 // Update frame incase orientation changed while |_contentArea| was out of
5076 // the view hierarchy.
5077 [_contentArea setFrame:[sideSwipeView frame]];
5078
Justin Cohen16ad60e2017-11-10 14:56:265079 [self.view insertSubview:_contentArea aboveSubview:_fakeStatusBarView];
sdefresnee65fd872016-12-19 13:38:135080 [self updateVoiceSearchBarVisibilityAnimated:NO];
5081 [self updateToolbar];
5082
5083 // Reset horizontal stack view.
5084 [sideSwipeView removeFromSuperview];
Mark Cogan5bd86ba2017-12-28 14:32:385085 [self.sideSwipeController setInSwipe:NO];
sdefresnee65fd872016-12-19 13:38:135086 [_infoBarContainer->view() setHidden:NO];
5087}
5088
Mark Cogan849244ee2017-12-29 15:57:195089- (UIView*)sideSwipeContentView {
sdefresnee65fd872016-12-19 13:38:135090 return _contentArea;
5091}
5092
Mark Cogan849244ee2017-12-29 15:57:195093- (void)sideSwipeRedisplayTab:(Tab*)tab {
5094 [self displayTab:tab isNewSelection:YES];
5095}
5096
sdefresnee65fd872016-12-19 13:38:135097- (BOOL)preventSideSwipe {
Peter Laurense0b80f12017-11-21 07:52:405098 if ([_toolbarCoordinator isShowingToolsMenu])
sdefresnee65fd872016-12-19 13:38:135099 return YES;
5100
5101 if (_voiceSearchController && _voiceSearchController->IsVisible())
5102 return YES;
5103
sdefresnee65fd872016-12-19 13:38:135104 if (!self.active)
5105 return YES;
5106
5107 return NO;
5108}
5109
5110- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5111 if (visible) {
5112 [self updateVoiceSearchBarVisibilityAnimated:NO];
5113 [self updateToolbar];
5114 [_infoBarContainer->view() setHidden:NO];
5115 } else {
5116 // Hide UI accessories such as find bar and first visit overlays
5117 // for welcome page.
5118 [self hideFindBarWithAnimation:NO];
5119 [_infoBarContainer->view() setHidden:YES];
5120 [_voiceSearchBar setHidden:YES];
5121 }
5122}
5123
Mark Cogan849244ee2017-12-29 15:57:195124- (CGFloat)headerHeightForSideSwipe {
5125 return self.headerHeight;
5126}
5127
sdefresnee65fd872016-12-19 13:38:135128- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5129 BOOL seenToolbar = NO;
5130 BOOL seenInfoBarContainer = NO;
5131 BOOL seenContentArea = NO;
5132 for (UIView* view in views.subviews) {
sczs42f7f7482017-11-08 01:13:275133 if (view == _toolbarCoordinator.toolbarViewController.view)
sdefresnee65fd872016-12-19 13:38:135134 seenToolbar = YES;
5135 else if (view == _infoBarContainer->view())
5136 seenInfoBarContainer = YES;
5137 else if (view == _contentArea)
5138 seenContentArea = YES;
5139 if ((seenToolbar && !seenInfoBarContainer) ||
5140 (seenInfoBarContainer && !seenContentArea))
5141 return NO;
5142 }
5143 return YES;
5144}
5145
5146#pragma mark - PreloadControllerDelegate methods
5147
rohitraoeeb5293b2017-06-15 14:40:025148- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445149 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135150}
5151
rohitraoeeb5293b2017-06-15 14:40:025152- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5153 return [self hasControllerForURL:url];
5154}
5155
Gauthier Ambard65e949b092017-11-29 08:46:205156// TODO(crbug.com/788705): BVC doesn't need to implement
5157// BookmarkModelBridgeObserver once the new toolbar is turned on.
5158#pragma mark - BookmarkModelBridgeObserver
sdefresnee65fd872016-12-19 13:38:135159
5160// If an added or removed bookmark is the same as the current url, update the
5161// toolbar so the star highlight is kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205162- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
5163 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:135164}
5165
5166// If all bookmarks are removed, update the toolbar so the star highlight is
5167// kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205168- (void)bookmarkModelRemovedAllNodes {
sdefresnee65fd872016-12-19 13:38:135169 [self updateToolbar];
5170}
5171
Gauthier Ambard65e949b092017-11-29 08:46:205172// In case we are on a bookmarked page before the model is loaded.
5173- (void)bookmarkModelLoaded {
5174 [self updateToolbar];
5175}
5176
5177- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
5178 // No-op -- required by BookmarkModelBridgeObserver but not used.
5179}
5180
5181- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
5182 movedFromParent:(const BookmarkNode*)oldParent
5183 toParent:(const BookmarkNode*)newParent {
5184 // No-op -- required by BookmarkModelBridgeObserver but not used.
5185}
5186
5187- (void)bookmarkNodeDeleted:(const BookmarkNode*)node
5188 fromFolder:(const BookmarkNode*)folder {
5189 // No-op -- required by BookmarkModelBridgeObserver but not used.
5190}
5191
Mark Cogan849244ee2017-12-29 15:57:195192#pragma mark - NetExportTabHelperDelegate
sdefresnee65fd872016-12-19 13:38:135193
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575194- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5195 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135196 if (![MFMailComposeViewController canSendMail]) {
5197 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575198 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135199 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575200 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135201 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5202 return;
5203 }
stkhapuginc9eee7b2017-04-10 15:49:275204 MFMailComposeViewController* mailViewController =
5205 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135206 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575207 [mailViewController setToRecipients:[context toRecipients]];
5208 [mailViewController setSubject:[context subject]];
5209 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135210
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575211 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135212 if (!textFile.empty()) {
5213 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5214 NSData* data = [NSData dataWithContentsOfFile:filename];
5215 if (data) {
5216 NSString* displayName =
5217 base::SysUTF8ToNSString(textFile.BaseName().value());
5218 [mailViewController addAttachmentData:data
5219 mimeType:@"text/plain"
5220 fileName:displayName];
5221 }
5222 }
5223
5224 [mailViewController setMailComposeDelegate:self];
5225 [self presentViewController:mailViewController animated:YES completion:nil];
5226}
5227
5228#pragma mark - MFMailComposeViewControllerDelegate methods
5229
5230- (void)mailComposeController:(MFMailComposeViewController*)controller
5231 didFinishWithResult:(MFMailComposeResult)result
5232 error:(NSError*)error {
5233 [self dismissViewControllerAnimated:YES completion:nil];
5234}
5235
Mark Cogan849244ee2017-12-29 15:57:195236#pragma mark - SKStoreProductViewControllerDelegate
sdefresnee65fd872016-12-19 13:38:135237
5238- (void)productViewControllerDidFinish:
5239 (SKStoreProductViewController*)viewController {
5240 [self dismissViewControllerAnimated:YES completion:nil];
5241}
5242
Mark Cogan849244ee2017-12-29 15:57:195243#pragma mark - StoreKitLauncher methods
5244
sdefresnee65fd872016-12-19 13:38:135245- (void)openAppStore:(NSString*)appId {
5246 if (![appId length])
5247 return;
5248 NSDictionary* product =
5249 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275250 SKStoreProductViewController* storeViewController =
5251 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135252 [storeViewController setDelegate:self];
5253 [storeViewController loadProductWithParameters:product completionBlock:nil];
5254 [self presentViewController:storeViewController animated:YES completion:nil];
5255}
5256
5257#pragma mark - TabDialogDelegate methods
5258
sdefresnee65fd872016-12-19 13:38:135259- (void)cancelDialogForTab:(Tab*)tab {
5260 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5261}
5262
Mark Cogan849244ee2017-12-29 15:57:195263#pragma mark - AppRatingPromptDelegate
sdefresnee65fd872016-12-19 13:38:135264
5265- (void)userTappedRateApp:(UIView*)view {
5266 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275267 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135268}
5269
5270- (void)userTappedSendFeedback:(UIView*)view {
5271 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275272 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205273 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135274}
5275
5276- (void)userTappedDismiss:(UIView*)view {
5277 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275278 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135279}
5280
Mark Cogan849244ee2017-12-29 15:57:195281#pragma mark - VoiceSearchBarOwner
5282
5283- (id<VoiceSearchBar>)voiceSearchBar {
5284 return _voiceSearchBar;
5285}
5286
sdefresnee65fd872016-12-19 13:38:135287#pragma mark - VoiceSearchBarDelegate
5288
5289- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275290 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135291 [self ensureVoiceSearchControllerCreated];
5292 return _voiceSearchController->IsTextToSpeechEnabled() &&
5293 _voiceSearchController->IsTextToSpeechSupported();
5294}
5295
5296- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275297 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
Sylvain Defresne17b8aa42017-12-21 16:17:175298 SnapshotTabHelper::FromWebState(self.tabModel.currentTab.webState)
5299 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
sdefresnee65fd872016-12-19 13:38:135300}
5301
Mark Cogan776e0282018-01-02 09:00:065302#pragma mark - VoiceSearchPresenter (Public)
sdefresnee65fd872016-12-19 13:38:135303
5304- (UIView*)voiceSearchButton {
5305 return _voiceSearchButton;
5306}
5307
5308- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5309 return [self currentLogoAnimationControllerOwner];
5310}
5311
Mark Cogan849244ee2017-12-29 15:57:195312#pragma mark - VoiceSearchPresenter helpers
5313
5314// The LogoAnimationControllerOwner to be used for the next logo transition
5315// animation.
5316- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
5317 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
5318 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
5319 self.shouldShowVoiceSearchBar) {
5320 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
5321 // animations.
5322 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
5323 }
5324 id currentNativeController =
5325 [self nativeControllerForTab:self.tabModel.currentTab];
5326 Protocol* possibleOwnerProtocol =
5327 @protocol(LogoAnimationControllerOwnerOwner);
5328 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
5329 [currentNativeController logoAnimationControllerOwner]) {
5330 // If the current native controller is showing a GLIF view (e.g. the NTP
5331 // when there is no doodle), use that GLIFControllerOwner.
5332 return [currentNativeController logoAnimationControllerOwner];
5333 }
5334 return nil;
5335}
5336
5337#pragma mark - ActivityServicePresentation
Rohit Rao01e0e002017-08-14 20:49:435338
5339- (void)presentActivityServiceViewController:(UIViewController*)controller {
5340 [self presentViewController:controller animated:YES completion:nil];
5341}
5342
5343- (void)activityServiceDidEndPresenting {
Mark Cogan5bd86ba2017-12-28 14:32:385344 self.dialogPresenterDelegateIsPresenting = NO;
Rohit Rao01e0e002017-08-14 20:49:435345 [self.dialogPresenter tryToPresent];
5346}
5347
Mark Cogan849244ee2017-12-29 15:57:195348- (void)showActivityServiceErrorAlertWithStringTitle:(NSString*)title
5349 message:(NSString*)message {
5350 [self showErrorAlertWithStringTitle:title message:message];
5351}
5352
5353#pragma mark - QRScannerPresenting
Rohit Raocda0a992017-08-16 15:37:115354
5355- (void)presentQRScannerViewController:(UIViewController*)controller {
5356 [self presentViewController:controller animated:YES completion:nil];
5357}
5358
5359- (void)dismissQRScannerViewController:(UIViewController*)controller
5360 completion:(void (^)(void))completion {
5361 DCHECK_EQ(controller, self.presentedViewController);
5362 [self dismissViewControllerAnimated:YES completion:completion];
5363}
5364
Mark Cogan849244ee2017-12-29 15:57:195365#pragma mark - TabHeadersDelegate
5366
5367- (CGFloat)tabHeaderHeightForTab:(Tab*)tab {
5368 return [self headerHeightForTab:tab];
5369}
5370
5371#pragma mark - TabHistoryPresentation
sczsdd860eba2017-08-10 01:55:385372
sczs0a726d22017-08-21 22:40:135373- (UIView*)viewForTabHistoryPresentation {
5374 return self.view;
5375}
5376
sczsdd860eba2017-08-10 01:55:385377- (void)prepareForTabHistoryPresentation {
5378 DCHECK(self.visible || self.dismissingModal);
5379 [[self.tabModel currentTab].webController dismissKeyboard];
Gauthier Ambard83207452018-01-04 07:51:395380 [self.primaryToolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385381}
5382
Mike Doughertya1ec26402017-08-23 19:46:315383#pragma mark - CaptivePortalDetectorTabHelperDelegate
5384
Mike Dougherty4620cf8e2017-10-31 23:37:095385- (void)captivePortalDetectorTabHelper:
5386 (CaptivePortalDetectorTabHelper*)tabHelper
5387 connectWithLandingURL:(const GURL&)landingURL {
Mike Dougherty66e58812017-11-03 06:54:285388 [self addSelectedTabWithURL:landingURL transition:ui::PAGE_TRANSITION_TYPED];
Mike Doughertya1ec26402017-08-23 19:46:315389}
5390
Gregory Chatzinoffdf93d692017-09-09 01:32:275391#pragma mark - PageInfoPresentation
5392
Gregory Chatzinoffb6a01f72017-09-20 20:06:395393- (void)presentPageInfoView:(UIView*)pageInfoView {
5394 [pageInfoView setFrame:self.view.bounds];
5395 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275396}
5397
5398- (void)prepareForPageInfoPresentation {
5399 // Dismiss the omnibox (if open).
Gauthier Ambard83207452018-01-04 07:51:395400 [self.primaryToolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275401}
5402
Gregory Chatzinoffb6a01f72017-09-20 20:06:395403- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5404 return [self.view convertPoint:origin fromView:nil];
5405}
5406
Sylvain Defresnecacc3a52017-09-12 13:51:045407#pragma mark - WebStatePrinter
5408
5409- (void)printWebState:(web::WebState*)webState {
5410 if (webState == [_model currentTab].webState)
Mark Cogan849244ee2017-12-29 15:57:195411 [self.dispatcher printTab];
Sylvain Defresnecacc3a52017-09-12 13:51:045412}
5413
Eugene But35ded552017-09-13 23:31:595414#pragma mark - RepostFormTabHelperDelegate
5415
5416- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
Sylvain Defresnee3c698122017-11-17 11:16:325417 presentRepostFormDialogForWebState:(web::WebState*)webState
5418 dialogAtPoint:(CGPoint)location
5419 completionHandler:(void (^)(BOOL))completion {
5420 _repostFormCoordinator =
5421 [[RepostFormCoordinator alloc] initWithBaseViewController:self
5422 dialogLocation:location
5423 webState:webState
5424 completionHandler:completion];
Eugene But35ded552017-09-13 23:31:595425 [_repostFormCoordinator start];
5426}
5427
5428- (void)repostFormTabHelperDismissRepostFormDialog:
5429 (RepostFormTabHelper*)helper {
5430 _repostFormCoordinator = nil;
5431}
5432
edchinf5150c682017-09-18 02:50:035433#pragma mark - TabStripPresentation
5434
5435- (BOOL)isTabStripFullyVisible {
5436 return ([self currentHeaderOffset] == 0.0f);
5437}
5438
5439- (void)showTabStripView:(UIView*)tabStripView {
5440 DCHECK([self isViewLoaded]);
5441 DCHECK(tabStripView);
5442 self.tabStripView = tabStripView;
5443 CGRect tabStripFrame = [self.tabStripView frame];
5444 tabStripFrame.origin = CGPointZero;
5445 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5446 // because the CGPointZero above will break reset the offset, but it's not
5447 // clear what removing that will do.
Mark Cogan849244ee2017-12-29 15:57:195448 tabStripFrame.origin.y = self.headerOffset;
edchinf5150c682017-09-18 02:50:035449 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5450 [self.tabStripView setFrame:tabStripFrame];
5451 [[self view] addSubview:tabStripView];
5452}
5453
edchincd32fdf2017-10-25 12:45:455454#pragma mark - ManageAccountsDelegate
5455
5456- (void)onManageAccounts {
5457 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5458 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5459 ->GetState());
edchin5b8aa052017-10-30 23:27:285460 [self.dispatcher showAccountsSettingsFromViewController:self];
edchincd32fdf2017-10-25 12:45:455461}
5462
5463- (void)onAddAccount {
5464 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5465 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5466 ->GetState());
edchinb082b2982017-11-03 19:41:215467 [self.dispatcher showAddAccountFromViewController:self];
edchincd32fdf2017-10-25 12:45:455468}
5469
5470- (void)onGoIncognito:(const GURL&)url {
5471 // The user taps on go incognito from the mobile U-turn webpage (the web page
5472 // that displays all users accounts available in the content area). As the
5473 // user chooses to go to incognito, the mobile U-turn page is no longer
5474 // neeeded. The current solution is to go back in history. This has the
5475 // advantage of keeping the current browsing session and give a good user
5476 // experience when the user comes back from incognito.
5477 [self.tabModel.currentTab goBack];
5478
5479 if (url.is_valid()) {
5480 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5481 initWithURL:url
5482 referrer:web::Referrer() // Strip referrer when switching modes.
5483 inIncognito:YES
5484 inBackground:NO
5485 appendTo:kLastTab];
5486 [self.dispatcher openURL:command];
5487 } else {
5488 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5489 }
5490}
5491
Mark Cogan776e0282018-01-02 09:00:065492#pragma mark - SyncPresenter (Public)
edchin95c927072017-11-04 00:35:075493
5494- (void)showReauthenticateSignin {
5495 [self.dispatcher
edchin3b46e8d2017-11-07 22:48:125496 showSignin:
5497 [[ShowSigninCommand alloc]
5498 initWithOperation:AUTHENTICATION_OPERATION_REAUTHENTICATE
5499 accessPoint:signin_metrics::AccessPoint::
5500 ACCESS_POINT_UNKNOWN]
5501 baseViewController:self];
edchin95c927072017-11-04 00:35:075502}
5503
5504- (void)showSyncSettings {
edchina14d7182017-11-06 18:37:505505 [self.dispatcher showSyncSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075506}
5507
5508- (void)showSyncPassphraseSettings {
edchinec723062017-11-06 20:03:545509 [self.dispatcher showSyncPassphraseSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075510}
5511
edchin9e7a1112017-11-07 18:28:035512#pragma mark - SigninPresenter
5513
5514- (void)showSignin:(ShowSigninCommand*)command {
edchin3b46e8d2017-11-07 22:48:125515 [self.dispatcher showSignin:command baseViewController:self];
edchin9e7a1112017-11-07 18:28:035516}
5517
sdefresnee65fd872016-12-19 13:38:135518@end