blob: 33d43f6e5d930965c1f4fc19f99a90f1c1d095c8 [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"
asvitkinef1899e32017-01-27 16:30:2928#include "base/metrics/histogram_macros.h"
sdefresnee65fd872016-12-19 13:38:1329#include "base/metrics/user_metrics.h"
30#include "base/metrics/user_metrics_action.h"
sdefresnee65fd872016-12-19 13:38:1331#include "base/strings/sys_string_conversions.h"
rohitraocd324eb72017-04-04 15:36:3932#include "base/strings/utf_string_conversions.h"
Sylvain Defresnefd3ecf22017-07-12 18:47:2433#include "base/task_scheduler/post_task.h"
sdefresnee65fd872016-12-19 13:38:1334#include "components/bookmarks/browser/base_bookmark_model_observer.h"
35#include "components/bookmarks/browser/bookmark_model.h"
Sylvain Defresne7178d4c2017-09-14 13:22:3736#include "components/favicon/ios/web_favicon_driver.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2337#include "components/feature_engagement/public/event_constants.h"
Cooper Knaake4f495cf2017-07-27 23:30:0338#include "components/feature_engagement/public/feature_constants.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2339#include "components/feature_engagement/public/tracker.h"
gambardbdc07cc2017-02-03 16:43:1140#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
sdefresnee65fd872016-12-19 13:38:1341#include "components/infobars/core/infobar_manager.h"
Mark Coganca30df62017-11-20 14:29:1142#import "components/language/ios/browser/ios_language_detection_tab_helper.h"
mathp9b4c11d2017-07-06 20:24:1343#include "components/payments/core/features.h"
sdefresnee65fd872016-12-19 13:38:1344#include "components/prefs/pref_service.h"
olivierrobin52b6cd6ec2017-03-23 13:55:5445#include "components/reading_list/core/reading_list_model.h"
sdefresnee65fd872016-12-19 13:38:1346#include "components/search_engines/search_engines_pref_names.h"
47#include "components/search_engines/template_url_service.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0548#include "components/sessions/core/session_types.h"
sdefresnee65fd872016-12-19 13:38:1349#include "components/sessions/core/tab_restore_service_helper.h"
edchincd32fdf2017-10-25 12:45:4550#include "components/signin/core/browser/account_reconcilor.h"
51#include "components/signin/core/browser/signin_metrics.h"
52#import "components/signin/ios/browser/account_consistency_service.h"
Eugene Butc90499d52017-09-22 16:02:0953#include "components/signin/ios/browser/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:1354#include "components/strings/grit/components_strings.h"
55#include "components/toolbar/toolbar_model_impl.h"
56#include "ios/chrome/app/tests_hook.h"
edchin7af0f132018-01-19 01:20:2557#import "ios/chrome/browser/app_launcher/app_launcher_tab_helper.h"
edchin01c88912018-01-26 16:45:0158#import "ios/chrome/browser/autofill/autofill_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1359#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"
Sylvain Defresne599df792018-01-11 13:14:5899#import "ios/chrome/browser/snapshots/snapshot_generator_delegate.h"
sdefresnee65fd872016-12-19 13:38:13100#import "ios/chrome/browser/snapshots/snapshot_overlay.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"
Mark Coganca30df62017-11-20 14:29:11112#import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
113#import "ios/chrome/browser/translate/language_selection_handler.h"
Rohit Rao01e0e002017-08-14 20:49:43114#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
115#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
sdefresnee65fd872016-12-19 13:38:13116#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
Eugene But35ded552017-09-13 23:31:59117#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
edchin7af0f132018-01-19 01:20:25118#import "ios/chrome/browser/ui/app_launcher/app_launcher_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"
gambarda675a2d2018-01-09 17:12:12137#import "ios/chrome/browser/ui/commands/toolbar_commands.h"
Gauthier Ambardf520c022017-08-29 07:42:23138#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13139#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13140#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
141#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
Eugene But87a09532017-12-08 20:02:05142#import "ios/chrome/browser/ui/download/legacy_download_manager_controller.h"
Eugene But49a7c572017-12-11 20:54:15143#import "ios/chrome/browser/ui/download/pass_kit_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13144#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
145#import "ios/chrome/browser/ui/external_file_controller.h"
Louis Romerod11747a2017-10-20 20:10:35146#import "ios/chrome/browser/ui/external_search/external_search_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13147#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
148#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19149#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
150#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_factory.h"
Kurt Horimoto803840622017-10-28 01:20:37151#import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
Kurt Horimoto06b94252017-12-08 19:45:59152#import "ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.h"
153#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h"
154#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h"
Kurt Horimoto62e97c72017-11-03 19:51:47155#import "ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38156#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13157#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
Gauthier Ambard929699412018-01-02 10:05:41158#import "ios/chrome/browser/ui/image_util/image_saver.h"
sdefresnee65fd872016-12-19 13:38:13159#import "ios/chrome/browser/ui/key_commands_provider.h"
Kurt Horimoto1945ef42017-10-26 03:57:26160#import "ios/chrome/browser/ui/location_bar_notification_names.h"
Rohit Rao9a8ad772017-10-30 22:35:59161#import "ios/chrome/browser/ui/main/main_feature_flags.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19162#import "ios/chrome/browser/ui/main_content/main_content_ui.h"
163#import "ios/chrome/browser/ui/main_content/main_content_ui_broadcasting_util.h"
164#import "ios/chrome/browser/ui/main_content/main_content_ui_state.h"
165#import "ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.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 Ambarddf60b46ef2018-01-17 15:30:43193#import "ios/chrome/browser/ui/toolbar/adaptive/adaptive_toolbar_coordinator.h"
194#import "ios/chrome/browser/ui/toolbar/adaptive/adaptive_toolbar_view_controller.h"
Gauthier Ambard3aa4f742018-01-10 14:19:48195#import "ios/chrome/browser/ui/toolbar/adaptive/primary_toolbar_coordinator.h"
Gauthier Ambard0e6da242018-01-10 15:27:03196#import "ios/chrome/browser/ui/toolbar/adaptive/secondary_toolbar_coordinator.h"
Gauthier Ambard4d485dd2018-01-11 12:43:44197#import "ios/chrome/browser/ui/toolbar/adaptive/toolbar_coordinator_adaptor.h"
Gauthier Ambard76d826ac2017-11-16 16:27:34198#include "ios/chrome/browser/ui/toolbar/legacy_toolbar_coordinator.h"
Kurt Horimotoea429dd2017-11-28 02:24:30199#import "ios/chrome/browser/ui/toolbar/legacy_toolbar_ui_updater.h"
Gauthier Ambard83207452018-01-04 07:51:39200#import "ios/chrome/browser/ui/toolbar/public/primary_toolbar_coordinator.h"
Gauthier Ambard522668e2018-01-03 14:10:07201#import "ios/chrome/browser/ui/toolbar/public/toolbar_controller_base_feature.h"
sdefresnee65fd872016-12-19 13:38:13202#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
203#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
Gauthier Ambard29939db12017-10-30 16:47:31204#import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
Kurt Horimotoea429dd2017-11-28 02:24:30205#import "ios/chrome/browser/ui/toolbar/toolbar_ui.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19206#import "ios/chrome/browser/ui/toolbar/toolbar_ui_broadcasting_util.h"
sczsc2b4f152017-10-11 01:44:24207#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
Peter Laurense0b80f12017-11-21 07:52:40208#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_configuration_provider.h"
209#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_presentation_provider.h"
sczsbbad1632017-07-29 03:48:00210#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13211#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
Mark Coganca30df62017-11-20 14:29:11212#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13213#include "ios/chrome/browser/ui/ui_util.h"
214#import "ios/chrome/browser/ui/uikit_ui_util.h"
Gauthier Ambard087e3572017-12-20 12:54:47215#import "ios/chrome/browser/ui/util/named_guide.h"
gambard6a138362017-02-06 17:19:28216#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13217#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
218#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51219#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13220#import "ios/chrome/browser/web/error_page_content.h"
edchin7af0f132018-01-19 01:20:25221#import "ios/chrome/browser/web/external_apps_launch_policy_decider.h"
Danyao Wang85389a82017-10-25 18:56:27222#import "ios/chrome/browser/web/load_timing_tab_helper.h"
Sylvain Defresne448351332017-12-27 10:38:36223#import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13224#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04225#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05226#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59227#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53228#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresne5b1174cb2018-01-16 15:47:58229#import "ios/chrome/browser/web/tab_id_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04230#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05231#import "ios/chrome/browser/web_state_list/web_state_list.h"
232#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57233#import "ios/chrome/browser/webui/net_export_tab_helper.h"
234#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
235#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13236#include "ios/chrome/grit/ios_chromium_strings.h"
237#include "ios/chrome/grit/ios_strings.h"
238#import "ios/net/request_tracker.h"
239#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
240#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
241#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
242#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
243#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
244#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
245#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
246#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
edchineeb4d422017-10-02 17:39:36247#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
sdefresnee65fd872016-12-19 13:38:13248#include "ios/web/public/navigation_item.h"
249#import "ios/web/public/navigation_manager.h"
250#include "ios/web/public/referrer_util.h"
251#include "ios/web/public/ssl_status.h"
252#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39253#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13254#include "ios/web/public/web_client.h"
255#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13256#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29257#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
Sylvain Defresnee7f2c8a2017-10-17 02:39:19258#import "ios/web/public/web_state/web_state.h"
sdefresnee65fd872016-12-19 13:38:13259#import "ios/web/public/web_state/web_state_delegate_bridge.h"
260#include "ios/web/public/web_thread.h"
261#import "ios/web/web_state/ui/crw_web_controller.h"
262#import "net/base/mac/url_conversions.h"
263#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
264#include "net/ssl/ssl_info.h"
265#include "net/url_request/url_request_context_getter.h"
266#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
267#include "ui/base/l10n/l10n_util.h"
268#include "ui/base/l10n/l10n_util_mac.h"
269#include "ui/base/page_transition_types.h"
270#include "url/gurl.h"
271
stkhapuginf58b10d02017-04-10 13:36:17272#if !defined(__has_feature) || !__has_feature(objc_arc)
273#error "This file requires ARC support."
274#endif
275
sdefresnee65fd872016-12-19 13:38:13276using base::UserMetricsAction;
277using bookmarks::BookmarkNode;
278
sdefresnee65fd872016-12-19 13:38:13279class InfoBarContainerDelegateIOS;
280
sdefresnee65fd872016-12-19 13:38:13281namespace {
282
283typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
284 // Note: these values must match the ContextMenuOption enum in histograms.xml.
285 ACTION_OPEN_IN_NEW_TAB = 0,
286 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
287 ACTION_COPY_LINK_ADDRESS = 2,
288 ACTION_SAVE_IMAGE = 6,
289 ACTION_OPEN_IMAGE = 7,
290 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
291 ACTION_SEARCH_BY_IMAGE = 11,
292 ACTION_OPEN_JAVASCRIPT = 21,
293 ACTION_READ_LATER = 22,
294 NUM_ACTIONS = 23,
295};
296
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28297void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13298 if (is_image) {
299 if (is_link) {
300 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
301 NUM_ACTIONS);
302 } else {
303 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
304 NUM_ACTIONS);
305 }
306 } else {
307 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
308 NUM_ACTIONS);
309 }
310}
311
edchinf5150c682017-09-18 02:50:03312// Returns the status bar background color.
313UIColor* StatusBarBackgroundColor() {
314 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
315}
316
317// Duration of the toolbar animation.
Kurt Horimoto62e97c72017-11-03 19:51:47318const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3;
edchinf5150c682017-09-18 02:50:03319
sdefresnee65fd872016-12-19 13:38:13320const CGFloat kVoiceSearchBarHeight = 59.0;
321
322// Dimensions to use when downsizing an image for search-by-image.
323const CGFloat kSearchByImageMaxImageArea = 90000.0;
324const CGFloat kSearchByImageMaxImageWidth = 600.0;
325const CGFloat kSearchByImageMaxImageHeight = 400.0;
326
sdefresnee65fd872016-12-19 13:38:13327enum HeaderBehaviour {
328 // The header moves completely out of the screen.
329 Hideable = 0,
330 // This header stays on screen and doesn't overlap with the content.
331 Visible,
332 // This header stay on screen and covers part of the content.
333 Overlap
334};
335
sdefresnee65fd872016-12-19 13:38:13336const CGFloat kIPadFindBarOverlap = 11;
337
338bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24339 // Most URLs are allowed in incognito; the following is an exception.
340 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13341}
342
edchineeb4d422017-10-02 17:39:36343// Snackbar category for browser view controller.
344NSString* const kBrowserViewControllerSnackbarCategory =
345 @"BrowserViewControllerSnackbarCategory";
346
rohitrao005a6432017-03-16 20:52:42347} // namespace
sdefresnee65fd872016-12-19 13:38:13348
stkhapugin952ecef2017-04-11 12:11:45349#pragma mark - HeaderDefinition helper
350
351@interface HeaderDefinition : NSObject
352
353// The header view.
354@property(nonatomic, strong) UIView* view;
355// How to place the view, and its behaviour when the headers move.
356@property(nonatomic, assign) HeaderBehaviour behaviour;
357// Reduces the height of a header to adjust for shadows.
358@property(nonatomic, assign) CGFloat heightAdjustement;
359// Nudges that particular header up by this number of points.
360@property(nonatomic, assign) CGFloat inset;
361
362- (instancetype)initWithView:(UIView*)view
363 headerBehaviour:(HeaderBehaviour)behaviour
364 heightAdjustment:(CGFloat)heightAdjustment
365 inset:(CGFloat)inset;
366
367+ (instancetype)definitionWithView:(UIView*)view
368 headerBehaviour:(HeaderBehaviour)behaviour
369 heightAdjustment:(CGFloat)heightAdjustment
370 inset:(CGFloat)inset;
371
372@end
373
374@implementation HeaderDefinition
375@synthesize view = _view;
376@synthesize behaviour = _behaviour;
377@synthesize heightAdjustement = _heightAdjustement;
378@synthesize inset = _inset;
379
380+ (instancetype)definitionWithView:(UIView*)view
381 headerBehaviour:(HeaderBehaviour)behaviour
382 heightAdjustment:(CGFloat)heightAdjustment
383 inset:(CGFloat)inset {
384 return [[self alloc] initWithView:view
385 headerBehaviour:behaviour
386 heightAdjustment:heightAdjustment
387 inset:inset];
388}
389
390- (instancetype)initWithView:(UIView*)view
391 headerBehaviour:(HeaderBehaviour)behaviour
392 heightAdjustment:(CGFloat)heightAdjustment
393 inset:(CGFloat)inset {
394 self = [super init];
395 if (self) {
396 _view = view;
397 _behaviour = behaviour;
398 _heightAdjustement = heightAdjustment;
399 _inset = inset;
400 }
401 return self;
402}
403
404@end
405
406#pragma mark - BVC
407
Rohit Rao01e0e002017-08-14 20:49:43408@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43409 AppRatingPromptDelegate,
Gauthier Ambard65e949b092017-11-29 08:46:20410 BookmarkModelBridgeObserver,
Mike Dougherty4620cf8e2017-10-31 23:37:09411 CaptivePortalDetectorTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13412 CRWNativeContentProvider,
413 CRWWebStateDelegate,
414 DialogPresenterDelegate,
Kurt Horimoto06b94252017-12-08 19:45:59415 FullscreenUIElement,
Kurt Horimoto62e97c72017-11-03 19:51:47416 LegacyFullscreenControllerDelegate,
Mark Cogan80aa28d2017-11-30 13:11:34417 InfobarContainerStateDelegate,
sdefresnee65fd872016-12-19 13:38:13418 KeyCommandsPlumbing,
Kurt Horimotoe9b6002c2017-12-04 23:19:19419 MainContentUI,
edchincd32fdf2017-10-25 12:45:45420 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13421 MFMailComposeViewControllerDelegate,
Mark Cogan849244ee2017-12-29 15:57:19422 NetExportTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13423 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27424 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13425 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55426 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13427 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11428 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59429 RepostFormTabHelperDelegate,
Gauthier Ambard29939db12017-10-30 16:47:31430 SideSwipeControllerDelegate,
Mark Cogan849244ee2017-12-29 15:57:19431 SigninPresenter,
sdefresnee65fd872016-12-19 13:38:13432 SKStoreProductViewControllerDelegate,
Sylvain Defresne599df792018-01-11 13:14:58433 SnapshotGeneratorDelegate,
sdefresnee65fd872016-12-19 13:38:13434 StoreKitLauncher,
435 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19436 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38437 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13438 TabModelObserver,
edchinf5150c682017-09-18 02:50:03439 TabStripPresentation,
Peter Laurense0b80f12017-11-21 07:52:40440 ToolsMenuConfigurationProvider,
sdefresnee65fd872016-12-19 13:38:13441 UIGestureRecognizerDelegate,
Mark Cogan849244ee2017-12-29 15:57:19442 UpgradeCenterClient,
sdefresnee65fd872016-12-19 13:38:13443 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04444 VoiceSearchBarOwner,
445 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13446 // The dependency factory passed on initialization. Used to vend objects used
447 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27448 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13449
450 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27451 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13452
sczsf1620e52017-10-02 22:54:46453 // Facade objects used by |_toolbarCoordinator|.
454 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13455 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
456 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
457
sdefresnee65fd872016-12-19 13:38:13458 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27459 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13460
461 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27462 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13463
464 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27465 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13466
467 // Handles presentation of JavaScript dialogs.
468 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
469
justincohen75011c32017-04-28 16:31:39470 // Handles command dispatching.
471 CommandDispatcher* _dispatcher;
472
sdefresnee65fd872016-12-19 13:38:13473 // Keyboard commands provider. It offloads most of the keyboard commands
474 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27475 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13476
sdefresnee65fd872016-12-19 13:38:13477 // Used to inject Javascript implementing the PaymentRequest API and to
478 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27479 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13480
sdefresnee65fd872016-12-19 13:38:13481 // Used to display the Voice Search UI. Nil if not visible.
482 scoped_refptr<VoiceSearchController> _voiceSearchController;
483
gambard6299cc1d2017-02-21 13:06:03484 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27485 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03486
sdefresnee65fd872016-12-19 13:38:13487 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27488 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13489
sdefresnee65fd872016-12-19 13:38:13490 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27491 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13492
sdefresnee65fd872016-12-19 13:38:13493 // Adapter to let BVC be the delegate for WebState.
494 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
495
496 // YES if new tab is animating in.
497 BOOL _inNewTabAnimation;
498
499 // YES if Voice Search should be started when the new tab animation is
500 // finished.
501 BOOL _startVoiceSearchAfterNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13502 // YES if a load was cancelled due to typing in the location bar.
503 BOOL _locationBarEditCancelledLoad;
504 // YES if waiting for a foreground tab due to expectNewForegroundTab.
505 BOOL _expectingForegroundTab;
506
Sylvain Defresne41170aa2017-06-15 10:25:20507 // Whether or not -shutdown has been called.
508 BOOL _isShutdown;
509
sdefresnee65fd872016-12-19 13:38:13510 // The ChromeBrowserState associated with this BVC.
511 ios::ChromeBrowserState* _browserState; // weak
512
513 // Whether or not Incognito* is enabled.
514 BOOL _isOffTheRecord;
515
516 // The last point within |_contentArea| that's received a touch.
517 CGPoint _lastTapPoint;
518
519 // The time at which |_lastTapPoint| was most recently set.
520 CFTimeInterval _lastTapTime;
521
522 // A single infobar container handles all infobars in all tabs. It keeps
523 // track of infobars for current tab (accessed via infobar helper of
524 // the current tab).
525 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
526
527 // Bridge class to deliver container change notifications to BVC.
528 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
529
530 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37531 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27532 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13533
534 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11535 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13536
sdefresnee65fd872016-12-19 13:38:13537 // Bridge to register for bookmark changes.
Gauthier Ambard65e949b092017-11-29 08:46:20538 std::unique_ptr<bookmarks::BookmarkModelBridge> _bookmarkModelBridge;
sdefresnee65fd872016-12-19 13:38:13539
540 // Cached pointer to the bookmarks model.
541 bookmarks::BookmarkModel* _bookmarkModel; // weak
542
543 // The controller that shows the bookmarking UI after the user taps the star
544 // button.
stkhapuginc9eee7b2017-04-10 15:49:27545 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13546
sdefresnee65fd872016-12-19 13:38:13547 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27548 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13549
Eugene But56efc322017-08-11 14:03:44550 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28551 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13552
553 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27554 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13555
Jean-François Geyelin3d47c212017-08-03 09:24:09556 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27557 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13558
Rohit Rao01e0e002017-08-14 20:49:43559 // Coordinator for the share menu (Activity Services).
560 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
561
sdefresnee65fd872016-12-19 13:38:13562 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27563 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38564
Rohit Raocda0a992017-08-16 15:37:11565 // Coordinator for the QR scanner.
566 QRScannerLegacyCoordinator* _qrScannerCoordinator;
567
sczsdd860eba2017-08-10 01:55:38568 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13569 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53570
571 // Coordinator for displaying Sad Tab.
572 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27573
574 // Coordinator for Page Info UI.
575 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59576
577 // Coordinator for displaying Repost Form dialog.
578 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22579
edchin7f210cd2017-09-28 08:03:53580 // Coordinator for displaying snackbars.
581 SnackbarCoordinator* _snackbarCoordinator;
582
sczsf1620e52017-10-02 22:54:46583 // Coordinator for the toolbar.
584 LegacyToolbarCoordinator* _toolbarCoordinator;
585
Kurt Horimotoea429dd2017-11-28 02:24:30586 // The toolbar UI updater for the toolbar managed by |_toolbarCoordinator|.
587 LegacyToolbarUIUpdater* _toolbarUIUpdater;
588
Kurt Horimotoe9b6002c2017-12-04 23:19:19589 // The main content UI updater for the content displayed by this BVC.
590 MainContentUIStateUpdater* _mainContentUIUpdater;
591
592 // The forwarder for web scroll view interation events.
593 WebScrollViewMainContentUIForwarder* _webMainContentUIForwarder;
594
Kurt Horimoto06b94252017-12-08 19:45:59595 // The updater that adjusts the toolbar's layout for fullscreen events.
596 std::unique_ptr<FullscreenUIUpdater> _fullscreenUIUpdater;
597
Louis Romerod11747a2017-10-20 20:10:35598 // Coordinator for the External Search UI.
599 ExternalSearchCoordinator* _externalSearchCoordinator;
600
Mark Coganca30df62017-11-20 14:29:11601 // Coordinator for the language selection UI.
602 LanguageSelectionCoordinator* _languageSelectionCoordinator;
603
Eugene But49a7c572017-12-11 20:54:15604 // Coordinator for the PassKit UI presentation.
605 PassKitCoordinator* _passKitCoordinator;
606
edchin7af0f132018-01-19 01:20:25607 // Coordinator for UI related to launching external apps.
608 AppLauncherCoordinator* _appLauncherCoordinator;
609
Justin Cohenb3170c32017-09-19 01:55:22610 // Fake status bar view used to blend the toolbar into the status bar.
611 UIView* _fakeStatusBarView;
Sylvain Defresnef5d2d952017-11-14 11:15:31612
613 // Stores whether the Tab currently inserted was a pre-rendered Tab. This
614 // is used to determine whether the pre-rendering animation should be played
615 // or not.
616 BOOL _insertedTabWasPrerenderedTab;
sdefresnee65fd872016-12-19 13:38:13617}
618
619// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17620@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13621// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17622@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13623// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17624@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13625// Whether the current tab can enable the request desktop menu item.
626@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
627// Whether the sharing menu should be enabled.
628@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
629// Helper method to check web controller canShowFindBar method.
630@property(nonatomic, assign, readonly) BOOL canShowFindBar;
631// Whether the controller's view is currently available.
632// YES from viewWillAppear to viewWillDisappear.
633@property(nonatomic, assign, getter=isVisible) BOOL visible;
634// Whether the controller's view is currently visible.
635// YES from viewDidAppear to viewWillDisappear.
636@property(nonatomic, assign) BOOL viewVisible;
Kurt Horimotoe9b6002c2017-12-04 23:19:19637// Whether the controller should broadcast its UI.
638@property(nonatomic, assign, getter=isBroadcasting) BOOL broadcasting;
sdefresnee65fd872016-12-19 13:38:13639// Whether the controller is currently dismissing a presented view controller.
640@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
641// Returns YES if the toolbar has not been scrolled out by fullscreen.
642@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
643 BOOL toolbarOnScreen;
644// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12645@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13646// Whether BVC prefers to hide the status bar. This value is used to determine
647// the response from the |prefersStatusBarHidden| method.
648@property(nonatomic, assign) BOOL hideStatusBar;
649// Whether the VoiceSearchBar should be displayed.
650@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
651// Coordinator for displaying a modal overlay with activity indicator to prevent
652// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17653@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13654 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39655// A block to be run when the |tabWasAdded:| method completes the animation
656// for the presentation of a new tab. Can be used to record performance metrics.
657@property(nonatomic, strong, nullable)
658 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42659// Coordinator for Recent Tabs.
660@property(nonatomic, strong)
661 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03662// Coordinator for tablet tab strip.
663@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
664// A weak reference to the view of the tab strip on tablet.
665@property(nonatomic, weak) UIView* tabStripView;
Gauthier Ambard929699412018-01-02 10:05:41666// Helper for saving images.
667@property(nonatomic, strong) ImageSaver* imageSaver;
sdefresnee65fd872016-12-19 13:38:13668
Gauthier Ambard929699412018-01-02 10:05:41669// The user agent type used to load the currently visible page. User agent
670// type is NONE if there is no visible page or visible page is a native
671// page.
liaoyukeea9f3ee62017-03-07 22:05:39672@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
673
stkhapugin952ecef2017-04-11 12:11:45674// Returns the header views, all the chrome on top of the page, including the
675// ones that cannot be scrolled off screen by full screen.
676@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
677
Cooper Knaakd0a974cd2017-08-10 18:05:47678// Used to display the new tab tip in-product help promotion bubble. |nil| if
679// the new tab tip bubble has not yet been presented. Once the bubble is
680// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38681@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47682 BubbleViewControllerPresenter* tabTipBubblePresenter;
Helen Yang9175bd52017-08-12 00:28:40683// Used to display the new incognito tab tip in-product help promotion bubble.
684@property(nonatomic, strong)
685 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
686
Gauthier Ambard83207452018-01-04 07:51:39687// Primary toolbar.
Gauthier Ambard4d485dd2018-01-11 12:43:44688@property(nonatomic, strong) id<PrimaryToolbarCoordinator>
Gauthier Ambard83207452018-01-04 07:51:39689 primaryToolbarCoordinator;
Gauthier Ambard0e6da242018-01-10 15:27:03690// Secondary toolbar.
Gauthier Ambard4d485dd2018-01-11 12:43:44691@property(nonatomic, strong)
Gauthier Ambarddf60b46ef2018-01-17 15:30:43692 AdaptiveToolbarCoordinator* secondaryToolbarCoordinator;
Gauthier Ambard4d485dd2018-01-11 12:43:44693// Interface object with the toolbars.
694@property(nonatomic, strong)
695 id<ToolbarCoordinating, ToolsMenuPresentationStateProvider>
696 toolbarInterface;
Gauthier Ambard83207452018-01-04 07:51:39697// TODO(crbug.com/788705): Removes this property and associated calls.
698// Returns the LegacyToolbarCoordinator. This property is here to separate
699// methods which will be removed during cleanup to other methods. Uses this
700// property only for deprecated methods.
Gauthier Ambardc4d04f12018-01-04 16:58:24701@property(nonatomic, readonly) id<LegacyToolbarCoordinator>
702 legacyToolbarCoordinator;
Gauthier Ambard83207452018-01-04 07:51:39703
Justin Cohen9fe9ef672017-12-01 20:37:43704// Vertical offset for fullscreen toolbar.
Gauthier Ambard83207452018-01-04 07:51:39705@property(nonatomic, strong) NSLayoutConstraint* primaryToolbarOffsetConstraint;
Mark Cogan849244ee2017-12-29 15:57:19706// Y-dimension offset for placement of the header.
707@property(nonatomic, readonly) CGFloat headerOffset;
Mark Cogan849244ee2017-12-29 15:57:19708// Height of the header view for the tab model's current tab.
709@property(nonatomic, readonly) CGFloat headerHeight;
710
Mark Cogan776e0282018-01-02 09:00:06711// The webState of the active tab.
712@property(nonatomic, readonly) web::WebState* currentWebState;
sdefresnee65fd872016-12-19 13:38:13713
Mark Cogan776e0282018-01-02 09:00:06714// BVC initialization
715// ------------------
716// If the BVC is initialized with a valid browser state & tab model immediately,
717// the path is straightforward: functionality is enabled, and the UI is built
718// when -viewDidLoad is called.
719// If the BVC is initialized without a browser state or tab model, the tab model
720// and browser state may or may not be provided before -viewDidLoad is called.
721// In most cases, they will not, to improve startup performance.
722// In order to handle this, initialization of various aspects of BVC have been
723// broken out into the following functions, which have expectations (enforced
724// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
725
sdefresnee65fd872016-12-19 13:38:13726// Updates non-view-related functionality with the given browser state and tab
727// model.
728// Does not matter whether or not the view has been loaded.
729- (void)updateWithTabModel:(TabModel*)model
730 browserState:(ios::ChromeBrowserState*)browserState;
731// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
732// the status bar to mimic this layout.
733- (void)installFakeStatusBar;
734// Builds the UI parts of tab strip and the toolbar. Does not matter whether
735// or not browser state and tab model are valid.
736- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50737// Sets up the constraints on the toolbar.
738- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13739// Updates view-related functionality with the given tab model and browser
740// state. The view must have been loaded. Uses |_browserState| and |_model|.
741- (void)addUIFunctionalityForModelAndBrowserState;
Justin Cohen4eeada32017-11-13 18:21:28742// Sets the correct frame and hierarchy for subviews and helper views. Only
743// insert views on |initialLayout|.
744- (void)setUpViewLayout:(BOOL)initialLayout;
sdefresnee65fd872016-12-19 13:38:13745// Makes |tab| the currently visible tab, displaying its view. Calls
746// -selectedTabChanged on the toolbar only if |newSelection| is YES.
747- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
748// Initializes the bookmark interaction controller if not already initialized.
749- (void)initializeBookmarkInteractionController;
Mark Cogan776e0282018-01-02 09:00:06750// Installs the BVC as overscroll actions controller of |nativeContent| if
751// needed. Sets the style of the overscroll actions toolbar.
752- (void)setOverScrollActionControllerToStaticNativeContent:
753 (StaticHtmlNativeContent*)nativeContent;
754
755// UI Configuration, update and Layout
756// -----------------------------------
sdefresnee65fd872016-12-19 13:38:13757// Updates the toolbar display based on the current tab.
758- (void)updateToolbar;
Kurt Horimotoe9b6002c2017-12-04 23:19:19759// Starts or stops broadcasting the toolbar UI and main content UI depending on
760// whether the BVC is visible and active.
761- (void)updateBroadcastState;
sdefresnee65fd872016-12-19 13:38:13762// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12763// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13764- (void)updateDialogPresenterActiveState;
765// Dismisses popups and modal dialogs that are displayed above the BVC upon size
766// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
767// is performed.
768// TODO(crbug.com/522721): Support size changes for all popups and modal
769// dialogs.
770- (void)dismissPopups;
Mark Cogan776e0282018-01-02 09:00:06771// Returns whether |tab| is scrolled to the top.
772- (BOOL)isTabScrolledToTop:(Tab*)tab;
773// Returns the footer view if one exists (e.g. the voice search bar).
774- (UIView*)footerView;
775// Returns the header height needed for |tab|.
776- (CGFloat)headerHeightForTab:(Tab*)tab;
777// Sets the frame for the headers.
778- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
779 atOffset:(CGFloat)headerOffset;
780// Adds a CardView on top of the contentArea either taking the size of the full
781// screen or just the size of the space under the header.
782// Returns the CardView that was added.
783- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
Cooper Knaakd0a974cd2017-08-10 18:05:47784
Mark Cogan776e0282018-01-02 09:00:06785// Showing and Dismissing child UI
786// -------------------------------
Mark Cogan776e0282018-01-02 09:00:06787// Dismisses the "rate this app" dialog.
788- (void)dismissRateThisAppDialog;
789
790// Bubble Views
791// ------------
Cooper Knaakd0a974cd2017-08-10 18:05:47792// Returns a bubble associated with an in-product help promotion if
793// it is valid to show the promotion and |nil| otherwise. |feature| is the
794// base::Feature object associated with the given promotion. |direction| is the
795// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21796// arrow on the button. |text| is the text displayed by the bubble. This method
797// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47798- (BubbleViewControllerPresenter*)
799bubblePresenterForFeature:(const base::Feature&)feature
800 direction:(BubbleArrowDirection)direction
801 alignment:(BubbleAlignment)alignment
802 text:(NSString*)text;
803
Cooper Knaak120cee5e2017-08-10 20:57:00804// Waits to present a bubble associated with the new tab tip in-product help
805// promotion until the feature engagement tracker database is fully initialized.
806// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
807// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11808// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21809// determines it is not valid to present it. This method requires that
810// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00811- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11812// Optionally presents a bubble associated with the new tab tip in-product help
813// promotion. If the feature engagement tracker determines it is valid to show
814// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
815// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21816// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
817// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00818- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40819// Waits to present a bubble associated with the new incognito tab tip
820// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21821// fully initialized. This method requires that |self.browserState| is
822// not NULL.
Helen Yang9175bd52017-08-12 00:28:40823- (void)presentNewIncognitoTabTipBubbleOnInitialized;
824// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21825// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40826- (void)presentNewIncognitoTabTipBubble;
Cooper Knaak120cee5e2017-08-10 20:57:00827
Mark Cogan776e0282018-01-02 09:00:06828// Find Bar UI
829// -----------
sdefresnee65fd872016-12-19 13:38:13830// Update find bar with model data. If |shouldFocus| is set to YES, the text
831// field will become first responder.
832- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13833// Hide find bar.
834- (void)hideFindBarWithAnimation:(BOOL)animate;
835// Shows find bar. If |selectText| is YES, all text inside the Find Bar
836// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
837// set to be first responder.
838- (void)showFindBarWithAnimation:(BOOL)animate
839 selectText:(BOOL)selectText
840 shouldFocus:(BOOL)shouldFocus;
Mark Cogan776e0282018-01-02 09:00:06841// Redisplays the find bar if necessary furing a view controller size change,
842// using the transition coordinator |coordinator|.
843- (void)reshowFindBarIfNeededWithCoordinator:
844 (id<UIViewControllerTransitionCoordinator>)coordinator;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36845
Mark Cogan776e0282018-01-02 09:00:06846// Alerts
847// ------
848// Shows a self-dismissing snackbar displaying |message|.
849- (void)showSnackbar:(NSString*)message;
850// Shows an alert dialog with |title| and |message|.
851- (void)showErrorAlertWithStringTitle:(NSString*)title
852 message:(NSString*)message;
853
854// Tap Handling
855// ------------
856// Record the last tap point based on the |originPoint| (if any) passed in
857// |command|.
858- (void)setLastTapPoint:(OpenNewTabCommand*)command;
859// Returns the last stored |_lastTapPoint| if it's been set within the past
860// second.
861- (CGPoint)lastTapPoint;
862// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
863- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
864
865// Tab creation and selection
866// --------------------------
sdefresnee65fd872016-12-19 13:38:13867// Called when either a tab finishes loading or when a tab with finished content
868// is added directly to the model via pre-rendering. The tab must be non-nil and
869// must be a member of the tab model controlled by this BrowserViewController.
870- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
Mark Cogan776e0282018-01-02 09:00:06871// Adds a new tab with |url| and |postData| at the end of the model, and make it
872// the selected tab and return it.
873- (Tab*)addSelectedTabWithURL:(const GURL&)url
874 postData:(TemplateURLRef::PostContent*)postData
875 transition:(ui::PageTransition)transition;
876// Internal method that all of the similar public and private methods call.
877// Adds a new tab with |url| and |postData| (if not null) at |position| in the
878// tab model (or at the end if |position is NSNotFound|, with |transition| as
879// the page transition type. If |tabAddedCompletion| is nonnull, it's called
880// synchronously after the tab is added.
881- (Tab*)addSelectedTabWithURL:(const GURL&)url
882 postData:(TemplateURLRef::PostContent*)postData
883 atIndex:(NSUInteger)position
884 transition:(ui::PageTransition)transition
885 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion;
886// Whether the given tab's URL is an application specific URL.
887- (BOOL)isTabNativePage:(Tab*)tab;
888// Returns the view to use when animating a page in or out, positioning it to
889// fill the content area but not actually adding it to the view hierarchy.
890- (UIImageView*)pageOpenCloseAnimationView;
891// Add all delegates to the provided |tab|.
892- (void)installDelegatesForTab:(Tab*)tab;
893// Remove delegates from the provided |tab|.
894- (void)uninstallDelegatesForTab:(Tab*)tab;
895// Called when a tab is selected in the model. Make any required view changes.
896// The notification will not be sent when the tab is already the selected tab.
897// |notifyToolbar| indicates whether the toolbar is notified that the tab has
898// changed.
899- (void)tabSelected:(Tab*)tab notifyToolbar:(BOOL)notifyToolbar;
900// Returns the native controller being used by |tab|'s web controller.
901- (id)nativeControllerForTab:(Tab*)tab;
902
Mark Cogan776e0282018-01-02 09:00:06903// Voice Search
904// ------------
sdefresnee65fd872016-12-19 13:38:13905// Lazily instantiates |_voiceSearchController|.
906- (void)ensureVoiceSearchControllerCreated;
907// Lazily instantiates |_voiceSearchBar| and adds it to the view.
908- (void)ensureVoiceSearchBarCreated;
909// Shows/hides the voice search bar.
910- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
sdefresnee65fd872016-12-19 13:38:13911
Mark Cogan776e0282018-01-02 09:00:06912// Reading List
913// ------------
sdefresnee65fd872016-12-19 13:38:13914// Adds the given url to the reading list.
915- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
Mark Cogan776e0282018-01-02 09:00:06916
sdefresnee65fd872016-12-19 13:38:13917@end
918
sdefresnee65fd872016-12-19 13:38:13919@implementation BrowserViewController
Mark Cogan5bd86ba2017-12-28 14:32:38920// Public synthesized propeties.
sdefresnee65fd872016-12-19 13:38:13921@synthesize contentArea = _contentArea;
922@synthesize typingShield = _typingShield;
923@synthesize active = _active;
Mark Cogan5bd86ba2017-12-28 14:32:38924// Private synthesized properties
sdefresnee65fd872016-12-19 13:38:13925@synthesize visible = _visible;
926@synthesize viewVisible = _viewVisible;
Kurt Horimotoe9b6002c2017-12-04 23:19:19927@synthesize broadcasting = _broadcasting;
sdefresnee65fd872016-12-19 13:38:13928@synthesize dismissingModal = _dismissingModal;
929@synthesize hideStatusBar = _hideStatusBar;
930@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39931@synthesize foregroundTabWasAddedCompletionBlock =
932 _foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42933@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03934@synthesize tabStripCoordinator = _tabStripCoordinator;
935@synthesize tabStripView = _tabStripView;
Mark Cogan5bd86ba2017-12-28 14:32:38936@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
937@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambard4d485dd2018-01-11 12:43:44938@synthesize primaryToolbarCoordinator = _primaryToolbarCoordinator;
939@synthesize secondaryToolbarCoordinator = _secondaryToolbarCoordinator;
Gauthier Ambard83207452018-01-04 07:51:39940@synthesize primaryToolbarOffsetConstraint = _primaryToolbarOffsetConstraint;
Gauthier Ambard4d485dd2018-01-11 12:43:44941@synthesize toolbarInterface = _toolbarInterface;
Gauthier Ambard929699412018-01-02 10:05:41942@synthesize imageSaver = _imageSaver;
Mark Cogan5bd86ba2017-12-28 14:32:38943// DialogPresenterDelegate property
944@synthesize dialogPresenterDelegateIsPresenting =
945 _dialogPresenterDelegateIsPresenting;
sdefresnee65fd872016-12-19 13:38:13946
947#pragma mark - Object lifecycle
948
Mark Cogan5e3da152017-07-11 15:57:30949- (instancetype)
950 initWithTabModel:(TabModel*)model
951 browserState:(ios::ChromeBrowserState*)browserState
952 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
953applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:13954 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
955 if (self) {
956 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17957
stkhapuginc9eee7b2017-04-10 15:49:27958 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:27959 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
960 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39961 _dispatcher = [[CommandDispatcher alloc] init];
962 [_dispatcher startDispatchingToTarget:self
963 forProtocol:@protocol(UrlLoader)];
964 [_dispatcher startDispatchingToTarget:self
965 forProtocol:@protocol(WebToolbarDelegate)];
966 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:24967 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:30968 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
969 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:32970 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
971 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
972 // dispatched to the endpoint as well. Since this is potentially
973 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
974 DCHECK(!applicationCommandEndpoint ||
975 [applicationCommandEndpoint
976 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
977 [_dispatcher
978 startDispatchingToTarget:applicationCommandEndpoint
979 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:39980
edchin7f210cd2017-09-28 08:03:53981 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
982 _snackbarCoordinator.dispatcher = _dispatcher;
983 [_snackbarCoordinator start];
984
Mark Coganca30df62017-11-20 14:29:11985 _languageSelectionCoordinator =
986 [[LanguageSelectionCoordinator alloc] initWithBaseViewController:self];
987 _languageSelectionCoordinator.presenter =
988 [[VerticalAnimationContainer alloc] init];
989
Eugene But49a7c572017-12-11 20:54:15990 _passKitCoordinator =
991 [[PassKitCoordinator alloc] initWithBaseViewController:self];
992
edchin7af0f132018-01-19 01:20:25993 _appLauncherCoordinator =
994 [[AppLauncherCoordinator alloc] initWithBaseViewController:self];
995
sdefresnee65fd872016-12-19 13:38:13996 _javaScriptDialogPresenter.reset(
997 new JavaScriptDialogPresenterImpl(_dialogPresenter));
998 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
999 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:151000 [[UpgradeCenter sharedInstance] registerClient:self
1001 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131002 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131003 if (model && browserState)
1004 [self updateWithTabModel:model browserState:browserState];
sdefresnee65fd872016-12-19 13:38:131005 }
1006 return self;
1007}
1008
1009- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1010 bundle:(NSBundle*)nibBundleOrNil {
1011 NOTREACHED();
1012 return nil;
1013}
1014
1015- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1016 NOTREACHED();
1017 return nil;
1018}
1019
1020- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201021 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131022}
1023
Mark Cogan5bd86ba2017-12-28 14:32:381024#pragma mark - Public Properties
sdefresnee65fd872016-12-19 13:38:131025
edchin3365c7d2017-09-01 22:20:371026- (id<ApplicationCommands,
1027 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371028 OmniboxFocuser,
[email protected]2f95cbe2018-01-15 14:24:571029 FakeboxFocuser,
edchin7f210cd2017-09-28 08:03:531030 SnackbarCommands,
gambarda675a2d2018-01-09 17:12:121031 ToolbarCommands,
edchin3365c7d2017-09-01 22:20:371032 UrlLoader,
1033 WebToolbarDelegate>)dispatcher {
gambarda675a2d2018-01-09 17:12:121034 return static_cast<
[email protected]2f95cbe2018-01-15 14:24:571035 id<ApplicationCommands, BrowserCommands, OmniboxFocuser, FakeboxFocuser,
1036 SnackbarCommands, ToolbarCommands, UrlLoader, WebToolbarDelegate>>(
1037 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241038}
1039
sdefresnee65fd872016-12-19 13:38:131040- (void)setActive:(BOOL)active {
1041 if (_active == active) {
1042 return;
1043 }
1044 _active = active;
1045
1046 // If not active, display an activity indicator overlay over the view to
1047 // prevent interaction with the web page.
1048 // TODO(crbug.com/637093): This coordinator should be managed by the
1049 // coordinator used to present BrowserViewController, when implemented.
1050 if (active) {
1051 [self.activityOverlayCoordinator stop];
1052 self.activityOverlayCoordinator = nil;
1053 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171054 self.activityOverlayCoordinator =
1055 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131056 [self.activityOverlayCoordinator start];
1057 }
1058
1059 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091060 ActiveStateManager* active_state_manager =
1061 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131062 active_state_manager->SetActive(active);
1063 }
1064
1065 [_model setWebUsageEnabled:active];
1066 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191067 [self updateBroadcastState];
sdefresnee65fd872016-12-19 13:38:131068
1069 if (active) {
1070 // Make sure the tab (if any; it's possible to get here without a current
1071 // tab if the caller is about to create one) ends up on screen completely.
1072 Tab* currentTab = [_model currentTab];
1073 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441074 [self loadViewIfNeeded];
Sylvain Defresne448351332017-12-27 10:38:361075 if (_expectingForegroundTab) {
1076 PagePlaceholderTabHelper::FromWebState(currentTab.webState)
1077 ->AddPlaceholderForNextNavigation();
1078 }
sdefresnee65fd872016-12-19 13:38:131079 if (currentTab)
1080 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341081 } else {
1082 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131083 }
sdefresnee65fd872016-12-19 13:38:131084 [_paymentRequestManager enablePaymentRequest:active];
1085
1086 [self setNeedsStatusBarAppearanceUpdate];
1087}
1088
sdefresnee65fd872016-12-19 13:38:131089- (BOOL)isPlayingTTS {
1090 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1091}
1092
Mark Cogan5bd86ba2017-12-28 14:32:381093- (TabModel*)tabModel {
1094 return _model;
1095}
1096
sdefresne6165c8742017-01-16 15:42:021097- (ios::ChromeBrowserState*)browserState {
1098 return _browserState;
1099}
1100
Mark Cogan5bd86ba2017-12-28 14:32:381101#pragma mark - Private Properties
sdefresne6165c8742017-01-16 15:42:021102
sdefresnee65fd872016-12-19 13:38:131103- (SideSwipeController*)sideSwipeController {
1104 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271105 _sideSwipeController =
1106 [[SideSwipeController alloc] initWithTabModel:_model
1107 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131108 [_sideSwipeController setSnapshotDelegate:self];
Gauthier Ambard8fd74b72018-01-25 18:02:141109 _sideSwipeController.primaryToolbarInteractionHandler =
Gauthier Ambard83207452018-01-04 07:51:391110 self.primaryToolbarCoordinator;
Gauthier Ambard8fd74b72018-01-25 18:02:141111 _sideSwipeController.secondaryToolbarSnapshotProvider =
1112 self.secondaryToolbarCoordinator;
sdefresnee65fd872016-12-19 13:38:131113 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031114 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131115 }
1116 return _sideSwipeController;
1117}
1118
sdefresnee65fd872016-12-19 13:38:131119- (DialogPresenter*)dialogPresenter {
1120 return _dialogPresenter;
1121}
1122
Mark Cogan776e0282018-01-02 09:00:061123- (KeyCommandsProvider*)keyCommandsProvider {
1124 if (!_keyCommandsProvider) {
1125 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
1126 }
1127 return _keyCommandsProvider;
1128}
1129
sdefresnee65fd872016-12-19 13:38:131130- (BOOL)canUseDesktopUserAgent {
1131 Tab* tab = [_model currentTab];
1132 if ([self isTabNativePage:tab])
1133 return NO;
1134
1135 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441136 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131137}
1138
1139// Whether the sharing menu should be shown.
1140- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191141 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251142 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131143}
1144
1145- (BOOL)canShowFindBar {
1146 // Make sure web controller can handle find in page.
1147 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421148 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131149 return NO;
rohitrao005a6432017-03-16 20:52:421150 }
sdefresnee65fd872016-12-19 13:38:131151
rohitrao005a6432017-03-16 20:52:421152 auto* helper = FindTabHelper::FromWebState(tab.webState);
1153 return (helper && helper->CurrentPageSupportsFindInPage() &&
1154 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131155}
1156
liaoyukeea9f3ee62017-03-07 22:05:391157- (web::UserAgentType)userAgentType {
1158 web::WebState* webState = [_model currentTab].webState;
1159 if (!webState)
1160 return web::UserAgentType::NONE;
1161 web::NavigationItem* visibleItem =
1162 webState->GetNavigationManager()->GetVisibleItem();
1163 if (!visibleItem)
1164 return web::UserAgentType::NONE;
1165
1166 return visibleItem->GetUserAgentType();
1167}
1168
sdefresnee65fd872016-12-19 13:38:131169- (void)setVisible:(BOOL)visible {
1170 if (_visible == visible)
1171 return;
Peter Laurense0b80f12017-11-21 07:52:401172
sdefresnee65fd872016-12-19 13:38:131173 _visible = visible;
1174}
1175
1176- (void)setViewVisible:(BOOL)viewVisible {
1177 if (_viewVisible == viewVisible)
1178 return;
1179 _viewVisible = viewVisible;
1180 self.visible = viewVisible;
1181 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191182 [self updateBroadcastState];
1183}
1184
1185- (void)setBroadcasting:(BOOL)broadcasting {
1186 if (_broadcasting == broadcasting)
1187 return;
1188 _broadcasting = broadcasting;
1189 if (base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
1190 // TODO(crbug.com/790886): Use the Browser's broadcaster once Browsers are
1191 // supported.
Kurt Horimoto06b94252017-12-08 19:45:591192 FullscreenController* fullscreenController =
1193 FullscreenControllerFactory::GetInstance()->GetForBrowserState(
1194 _browserState);
1195 ChromeBroadcaster* broadcaster = fullscreenController->broadcaster();
Kurt Horimotoe9b6002c2017-12-04 23:19:191196 if (_broadcasting) {
1197 _toolbarUIUpdater = [[LegacyToolbarUIUpdater alloc]
1198 initWithToolbarUI:[[ToolbarUIState alloc] init]
1199 toolbarOwner:self
1200 webStateList:[_model webStateList]];
1201 [_toolbarUIUpdater startUpdating];
1202 StartBroadcastingToolbarUI(_toolbarUIUpdater.toolbarUI, broadcaster);
Kurt Horimoto06b94252017-12-08 19:45:591203
Kurt Horimotoe9b6002c2017-12-04 23:19:191204 _mainContentUIUpdater = [[MainContentUIStateUpdater alloc]
1205 initWithState:[[MainContentUIState alloc] init]];
1206 _webMainContentUIForwarder = [[WebScrollViewMainContentUIForwarder alloc]
1207 initWithUpdater:_mainContentUIUpdater
1208 webStateList:[_model webStateList]];
1209 StartBroadcastingMainContentUI(self, broadcaster);
Kurt Horimoto06b94252017-12-08 19:45:591210
Jinho Bangb940d6f42018-01-18 22:39:041211 _fullscreenUIUpdater = std::make_unique<FullscreenUIUpdater>(self);
Kurt Horimoto06b94252017-12-08 19:45:591212 fullscreenController->AddObserver(_fullscreenUIUpdater.get());
1213
1214 fullscreenController->SetWebStateList([_model webStateList]);
Kurt Horimotoe9b6002c2017-12-04 23:19:191215 } else {
1216 StopBroadcastingToolbarUI(broadcaster);
1217 StopBroadcastingMainContentUI(broadcaster);
1218 [_toolbarUIUpdater stopUpdating];
1219 _toolbarUIUpdater = nil;
1220 _mainContentUIUpdater = nil;
1221 [_webMainContentUIForwarder disconnect];
1222 _webMainContentUIForwarder = nil;
Kurt Horimoto06b94252017-12-08 19:45:591223 fullscreenController->RemoveObserver(_fullscreenUIUpdater.get());
1224 _fullscreenUIUpdater = nullptr;
1225 fullscreenController->SetWebStateList(nullptr);
Kurt Horimotoe9b6002c2017-12-04 23:19:191226 }
1227 }
sdefresnee65fd872016-12-19 13:38:131228}
1229
1230- (BOOL)isToolbarOnScreen {
Mark Cogan849244ee2017-12-29 15:57:191231 return self.headerHeight - [self currentHeaderOffset] > 0;
sdefresnee65fd872016-12-19 13:38:131232}
1233
kkhorimotoa44349c12017-04-12 23:02:121234- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1235 if (_inNewTabAnimation == inNewTabAnimation)
1236 return;
1237 _inNewTabAnimation = inNewTabAnimation;
1238 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191239 [self updateBroadcastState];
kkhorimotoa44349c12017-04-12 23:02:121240}
1241
sdefresnee65fd872016-12-19 13:38:131242- (BOOL)isInNewTabAnimation {
1243 return _inNewTabAnimation;
1244}
1245
1246- (BOOL)shouldShowVoiceSearchBar {
1247 // On iPads, the voice search bar should only be shown for regular horizontal
1248 // size class configurations. It should always be shown for voice search
1249 // results Tabs on iPhones, including configurations with regular horizontal
1250 // size classes (i.e. landscape iPhone 6 Plus).
1251 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1252 UIUserInterfaceSizeClassCompact;
1253 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1254 (!IsIPadIdiom() || compactWidth);
1255}
1256
1257- (void)setHideStatusBar:(BOOL)hideStatusBar {
1258 if (_hideStatusBar == hideStatusBar)
1259 return;
1260 _hideStatusBar = hideStatusBar;
1261 [self setNeedsStatusBarAppearanceUpdate];
1262}
1263
Mark Cogan776e0282018-01-02 09:00:061264- (NSArray<HeaderDefinition*>*)headerViews {
1265 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
1266 if (![self isViewLoaded])
1267 return results;
1268
1269 if (!IsIPadIdiom()) {
Gauthier Ambard964f1b52018-01-10 14:00:091270 if (self.primaryToolbarCoordinator.viewController.view) {
Mark Cogan776e0282018-01-02 09:00:061271 [results addObject:[HeaderDefinition
Gauthier Ambard83207452018-01-04 07:51:391272 definitionWithView:self.primaryToolbarCoordinator
Gauthier Ambard964f1b52018-01-10 14:00:091273 .viewController.view
Mark Cogan776e0282018-01-02 09:00:061274 headerBehaviour:Hideable
1275 heightAdjustment:0.0
1276 inset:0.0]];
1277 }
1278 } else {
1279 if (self.tabStripView) {
1280 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
1281 headerBehaviour:Hideable
1282 heightAdjustment:0.0
1283 inset:0.0]];
1284 }
Gauthier Ambard964f1b52018-01-10 14:00:091285 if (self.primaryToolbarCoordinator.viewController.view) {
Mark Cogan776e0282018-01-02 09:00:061286 [results addObject:[HeaderDefinition
Gauthier Ambard83207452018-01-04 07:51:391287 definitionWithView:self.primaryToolbarCoordinator
Gauthier Ambard964f1b52018-01-10 14:00:091288 .viewController.view
Mark Cogan776e0282018-01-02 09:00:061289 headerBehaviour:Hideable
1290 heightAdjustment:0.0
1291 inset:0.0]];
1292 }
1293 if ([_findBarController view]) {
1294 [results addObject:[HeaderDefinition
1295 definitionWithView:[_findBarController view]
1296 headerBehaviour:Overlap
1297 heightAdjustment:0.0
1298 inset:kIPadFindBarOverlap]];
1299 }
1300 }
1301 return [results copy];
1302}
1303
Mark Cogan849244ee2017-12-29 15:57:191304- (CGFloat)headerOffset {
1305 if (IsIPadIdiom())
1306 return StatusBarHeight();
1307 return 0.0;
1308}
1309
1310- (CGFloat)headerHeight {
1311 return [self headerHeightForTab:[_model currentTab]];
1312}
1313
Gauthier Ambard83207452018-01-04 07:51:391314- (LegacyToolbarCoordinator*)legacyToolbarCoordinator {
1315 return _toolbarCoordinator;
1316}
1317
Mark Cogan776e0282018-01-02 09:00:061318- (web::WebState*)currentWebState {
1319 return [[_model currentTab] webState];
1320}
1321
Mark Cogan5bd86ba2017-12-28 14:32:381322#pragma mark - Public methods
1323
1324- (void)setPrimary:(BOOL)primary {
1325 [_model setPrimary:primary];
1326 if (primary) {
1327 [self updateDialogPresenterActiveState];
1328 [self updateBroadcastState];
1329 } else {
1330 self.dialogPresenter.active = false;
1331 }
1332}
sdefresnee65fd872016-12-19 13:38:131333
1334- (void)shieldWasTapped:(id)sender {
[email protected]76545762018-01-24 18:30:531335 [self.dispatcher cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131336}
1337
Cooper Knaakd0a974cd2017-08-10 18:05:471338- (void)userEnteredTabSwitcher {
1339 if ([self.tabTipBubblePresenter isUserEngaged]) {
1340 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1341 }
1342}
1343
Cooper Knaake963d6702017-08-11 21:03:111344- (void)presentBubblesIfEligible {
1345 [self presentNewTabTipBubbleOnInitialized];
Gregory Chatzinoff56635192017-12-06 02:11:261346 [self presentNewIncognitoTabTipBubbleOnInitialized];
Cooper Knaake963d6702017-08-11 21:03:111347}
1348
Mark Cogan5bd86ba2017-12-28 14:32:381349- (void)browserStateDestroyed {
1350 [self setActive:NO];
1351 [_paymentRequestManager close];
1352 _paymentRequestManager = nil;
Gauthier Ambard83207452018-01-04 07:51:391353 [self.legacyToolbarCoordinator browserStateDestroyed];
Mark Cogan5bd86ba2017-12-28 14:32:381354 [_model browserStateDestroyed];
1355
1356 // Disconnect child coordinators.
1357 [_activityServiceCoordinator disconnect];
1358 [_qrScannerCoordinator disconnect];
1359 [_tabHistoryCoordinator disconnect];
1360 [_pageInfoCoordinator disconnect];
1361 [_externalSearchCoordinator disconnect];
1362 [self.tabStripCoordinator stop];
1363 self.tabStripCoordinator = nil;
1364 self.tabStripView = nil;
1365
1366 _browserState = nullptr;
1367 [_dispatcher stopDispatchingToTarget:self];
1368 _dispatcher = nil;
1369}
1370
1371- (Tab*)addSelectedTabWithURL:(const GURL&)url
1372 transition:(ui::PageTransition)transition {
1373 return [self addSelectedTabWithURL:url
1374 atIndex:[_model count]
1375 transition:transition];
1376}
1377
1378- (Tab*)addSelectedTabWithURL:(const GURL&)url
1379 atIndex:(NSUInteger)position
1380 transition:(ui::PageTransition)transition {
1381 return [self addSelectedTabWithURL:url
1382 atIndex:position
1383 transition:transition
1384 tabAddedCompletion:nil];
1385}
1386
1387- (Tab*)addSelectedTabWithURL:(const GURL&)url
1388 atIndex:(NSUInteger)position
1389 transition:(ui::PageTransition)transition
1390 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
1391 return [self addSelectedTabWithURL:url
1392 postData:NULL
1393 atIndex:position
1394 transition:transition
1395 tabAddedCompletion:tabAddedCompletion];
1396}
1397
1398- (void)expectNewForegroundTab {
1399 _expectingForegroundTab = YES;
1400}
1401
1402- (void)startVoiceSearchWithOriginView:(UIView*)originView {
1403 _voiceSearchButton = originView;
1404 // Delay Voice Search until new tab animations have finished.
1405 if (self.inNewTabAnimation) {
1406 _startVoiceSearchAfterNewTabAnimation = YES;
1407 return;
1408 }
1409
1410 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
1411 // dismiss the keyboard.
1412 [self closeFindInPage];
1413 [[_model currentTab].webController dismissKeyboard];
1414
1415 // Ensure that voice search objects are created.
1416 [self ensureVoiceSearchControllerCreated];
1417 [self ensureVoiceSearchBarCreated];
1418
1419 // Present voice search.
1420 [_voiceSearchBar prepareToPresentVoiceSearch];
1421 _voiceSearchController->StartRecognition(self, [_model currentTab]);
[email protected]76545762018-01-24 18:30:531422 [self.dispatcher cancelOmniboxEdit];
Mark Cogan5bd86ba2017-12-28 14:32:381423}
1424
1425- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
1426 dismissOmnibox:(BOOL)dismissOmnibox {
1427 [_activityServiceCoordinator cancelShare];
1428 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
1429 [_bookmarkInteractionController dismissSnackbar];
1430 if (dismissOmnibox) {
[email protected]76545762018-01-24 18:30:531431 [self.dispatcher cancelOmniboxEdit];
Mark Cogan5bd86ba2017-12-28 14:32:381432 }
1433 [_dialogPresenter cancelAllDialogs];
1434 [self.dispatcher hidePageInfo];
1435 [self.tabTipBubblePresenter dismissAnimated:NO];
1436 [self.incognitoTabTipBubblePresenter dismissAnimated:NO];
1437 if (_voiceSearchController)
1438 _voiceSearchController->DismissMicPermissionsHelp();
1439
1440 Tab* currentTab = [_model currentTab];
1441 [currentTab dismissModals];
1442
1443 if (currentTab) {
1444 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
1445 if (findHelper) {
1446 findHelper->StopFinding(^{
1447 [self updateFindBar:NO shouldFocus:NO];
1448 });
1449 }
1450 }
1451
1452 [_paymentRequestManager cancelRequest];
1453 [_printController dismissAnimated:YES];
1454 _printController = nil;
1455 [self.dispatcher dismissToolsMenu];
1456 [_contextMenuCoordinator stop];
1457 [self dismissRateThisAppDialog];
1458
1459 if (self.presentedViewController) {
1460 // Dismisses any other modal controllers that may be present, e.g. Recent
1461 // Tabs.
1462 //
1463 // Note that currently, some controllers like the bookmark ones were already
1464 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
1465 // but are still reported as the presentedViewController. Calling
1466 // |dismissViewControllerAnimated:completion:| again would dismiss the BVC
1467 // itself, so instead check the value of |self.dismissingModal| and only
1468 // call dismiss if one of the above calls has not already triggered a
1469 // dismissal.
1470 //
1471 // To ensure the completion is called, nil is passed to the call to dismiss,
1472 // and the completion is called explicitly below.
1473 if (!TabSwitcherPresentsBVCEnabled() || !self.dismissingModal) {
1474 [self dismissViewControllerAnimated:NO completion:nil];
1475 }
1476 // Dismissed controllers will be so after a delay. Queue the completion
1477 // callback after that.
1478 if (completion) {
1479 dispatch_after(
1480 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
1481 dispatch_get_main_queue(), ^{
1482 completion();
1483 });
1484 }
1485 } else if (completion) {
1486 // If no view controllers are presented, we should be ok with dispatching
1487 // the completion block directly.
1488 dispatch_async(dispatch_get_main_queue(), completion);
1489 }
1490}
1491
1492- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
1493 return [self.tabStripCoordinator placeholderView];
1494}
1495
1496- (void)shutdown {
1497 DCHECK(!_isShutdown);
1498 _isShutdown = YES;
1499 [self.tabStripCoordinator stop];
1500 self.tabStripCoordinator = nil;
1501 [_toolbarCoordinator stop];
1502 _toolbarCoordinator = nil;
Gauthier Ambard4d485dd2018-01-11 12:43:441503 [self.primaryToolbarCoordinator stop];
1504 self.primaryToolbarCoordinator = nil;
1505 [self.secondaryToolbarCoordinator stop];
1506 self.secondaryToolbarCoordinator = nil;
1507 self.toolbarInterface = nil;
Mark Cogan5bd86ba2017-12-28 14:32:381508 self.tabStripView = nil;
1509 _infoBarContainer = nil;
1510 _readingListMenuNotifier = nil;
1511 _bookmarkModelBridge.reset();
1512 [_model removeObserver:self];
1513 [[UpgradeCenter sharedInstance] unregisterClient:self];
Mark Cogan5bd86ba2017-12-28 14:32:381514 if (_voiceSearchController)
1515 _voiceSearchController->SetDelegate(nil);
1516 [_rateThisAppDialog setDelegate:nil];
1517 [_model closeAllTabs];
1518 [_paymentRequestManager setActiveWebState:nullptr];
1519}
1520
1521#pragma mark - NSObject
1522
1523- (BOOL)accessibilityPerformEscape {
1524 [self dismissPopups];
1525 return YES;
1526}
1527
Mark Cogan776e0282018-01-02 09:00:061528#pragma mark - UIResponder
1529
1530- (NSArray*)keyCommands {
1531 if (![self shouldRegisterKeyboardCommands]) {
1532 return nil;
1533 }
1534 return [self.keyCommandsProvider
1535 keyCommandsForConsumer:self
1536 baseViewController:self
1537 dispatcher:self.dispatcher
1538 editingText:![self isFirstResponder]];
1539}
1540
1541#pragma mark - UIResponder helpers
1542
1543// Whether the BVC should declare keyboard commands.
1544- (BOOL)shouldRegisterKeyboardCommands {
1545 if ([self presentedViewController])
1546 return NO;
1547
1548 if (_voiceSearchController && _voiceSearchController->IsVisible())
1549 return NO;
1550
1551 // If there is no first responder, try to make the webview the first
1552 // responder.
1553 if (!GetFirstResponder()) {
1554 web::WebState* webState = _model.currentTab.webState;
1555 if (webState)
1556 [webState->GetWebViewProxy() becomeFirstResponder];
1557 }
1558
1559 return YES;
1560}
1561
Mark Cogan5bd86ba2017-12-28 14:32:381562#pragma mark - UIViewController
sdefresnee65fd872016-12-19 13:38:131563
1564// Perform additional set up after loading the view, typically from a nib.
1565- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091566 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281567 initialViewsRect.origin.y += StatusBarHeight();
1568 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131569 UIViewAutoresizing initialViewAutoresizing =
1570 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1571
stkhapuginf58b10d02017-04-10 13:36:171572 self.contentArea =
1573 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131574 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171575 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131576 self.typingShield.autoresizingMask = initialViewAutoresizing;
Justin Cohen41b1f382017-12-04 15:22:081577 self.typingShield.accessibilityIdentifier = @"Typing Shield";
1578 self.typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1579
sdefresnee65fd872016-12-19 13:38:131580 [self.typingShield addTarget:self
1581 action:@selector(shieldWasTapped:)
1582 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131583 self.view.autoresizingMask = initialViewAutoresizing;
1584 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1585 [self.view addSubview:self.contentArea];
1586 [self.view addSubview:self.typingShield];
1587 [super viewDidLoad];
1588
1589 // Install fake status bar for iPad iOS7
1590 [self installFakeStatusBar];
1591 [self buildToolbarAndTabStrip];
Justin Cohen4eeada32017-11-13 18:21:281592 [self setUpViewLayout:YES];
Justin Cohenba27610e2017-11-08 19:34:451593 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:501594 [self addConstraintsToToolbar];
1595 }
sdefresnee65fd872016-12-19 13:38:131596 // If the tab model and browser state are valid, finish initialization.
1597 if (_model && _browserState)
1598 [self addUIFunctionalityForModelAndBrowserState];
1599
1600 // Add a tap gesture recognizer to save the last tap location for the source
1601 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271602 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1603 initWithTarget:self
1604 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131605 [tapRecognizer setDelegate:self];
1606 [tapRecognizer setCancelsTouchesInView:NO];
1607 [_contentArea addGestureRecognizer:tapRecognizer];
1608}
1609
Justin Cohenb3170c32017-09-19 01:55:221610- (void)viewSafeAreaInsetsDidChange {
1611 [super viewSafeAreaInsetsDidChange];
1612 // Gate this behind iPhone X, since it's currently the only device that
1613 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501614 if (IsIPhoneX()) {
Justin Cohen4eeada32017-11-13 18:21:281615 [self setUpViewLayout:NO];
Jean-François Geyelined4cde72017-10-11 11:34:501616 }
Justin Cohenb3170c32017-09-19 01:55:221617}
1618
sdefresnee65fd872016-12-19 13:38:131619- (void)viewDidAppear:(BOOL)animated {
1620 [super viewDidAppear:animated];
1621 self.viewVisible = YES;
1622 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191623 [self updateBroadcastState];
Gregory Chatzinoff541b8642017-10-25 00:25:211624
1625 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1626 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1627 // check for |self.browserState| before calling the presenting the bubbles.
1628 if (self.browserState) {
1629 [self presentBubblesIfEligible];
1630 }
sdefresnee65fd872016-12-19 13:38:131631}
1632
1633- (void)viewWillAppear:(BOOL)animated {
1634 [super viewWillAppear:animated];
1635
sdefresnee65fd872016-12-19 13:38:131636 self.visible = YES;
1637
1638 // Restore hidden infobars.
Rohit Rao755c37b2017-11-10 14:05:521639 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131640 _infoBarContainer->RestoreInfobars();
1641 }
1642
1643 // If the controller is suspended, or has been paged out due to low memory,
1644 // updating the view will be handled when it's displayed again.
1645 if (![_model webUsageEnabled] || !self.contentArea)
1646 return;
1647 // Update the displayed tab (if any; the switcher may not have created one
1648 // yet) in case it changed while showing the switcher.
1649 Tab* currentTab = [_model currentTab];
1650 if (currentTab)
1651 [self displayTab:currentTab isNewSelection:YES];
1652}
1653
1654- (void)viewWillDisappear:(BOOL)animated {
1655 self.viewVisible = NO;
1656 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191657 [self updateBroadcastState];
sdefresnee65fd872016-12-19 13:38:131658 [[_model currentTab] wasHidden];
1659 [_bookmarkInteractionController dismissSnackbar];
Rohit Rao755c37b2017-11-10 14:05:521660 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131661 _infoBarContainer->SuspendInfobars();
1662 }
1663 [super viewWillDisappear:animated];
1664}
1665
1666- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1667 duration:(NSTimeInterval)duration {
1668 [super willRotateToInterfaceOrientation:orient duration:duration];
1669 [self dismissPopups];
1670 [self reshowFindBarIfNeededWithCoordinator:nil];
1671}
1672
1673- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1674 [super didRotateFromInterfaceOrientation:orient];
1675
1676 // This reinitializes the toolbar, including updating the Overlay View,
1677 // if there is one.
1678 [self updateToolbar];
Mark Cogan80aa28d2017-11-30 13:11:341679 [self infoBarContainerStateDidChangeAnimated:NO];
sdefresnee65fd872016-12-19 13:38:131680}
1681
1682- (BOOL)prefersStatusBarHidden {
Gauthier Ambarda5c13ad2018-01-17 16:00:481683 BOOL defaultValue = NO;
Justin Cohen2d81c582018-01-22 14:46:441684 if (IsUIRefreshPhase1Enabled()) {
Gauthier Ambarda5c13ad2018-01-17 16:00:481685 defaultValue = [super prefersStatusBarHidden];
1686 }
1687 return self.hideStatusBar || defaultValue;
sdefresnee65fd872016-12-19 13:38:131688}
1689
1690// Called when in the foreground and the OS needs more memory. Release as much
1691// as possible.
1692- (void)didReceiveMemoryWarning {
1693 // Releases the view if it doesn't have a superview.
1694 [super didReceiveMemoryWarning];
1695
1696 // Release any cached data, images, etc that aren't in use.
1697 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1698 // but since the FaviconCache uses obj-c in the header, it can't be included
1699 // there.
1700 if (_browserState) {
1701 FaviconLoader* loader =
1702 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1703 _browserState);
1704 if (loader)
1705 loader->PurgeCache();
1706 }
1707
1708 if (![self isViewLoaded]) {
1709 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1710 // as the BrowserViewController.
1711 self.contentArea = nil;
1712 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271713 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131714 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271715 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421716 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461717 _toolbarCoordinator = nil;
Gauthier Ambard4d485dd2018-01-11 12:43:441718 self.primaryToolbarCoordinator = nil;
1719 self.secondaryToolbarCoordinator = nil;
1720 self.toolbarInterface = nil;
Kurt Horimotoea429dd2017-11-28 02:24:301721 [_toolbarUIUpdater stopUpdating];
1722 _toolbarUIUpdater = nil;
stkhapuginc9eee7b2017-04-10 15:49:271723 _toolbarModelDelegate = nil;
1724 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031725 [self.tabStripCoordinator stop];
1726 self.tabStripCoordinator = nil;
1727 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271728 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131729 }
1730}
1731
1732- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1733 [super traitCollectionDidChange:previousTraitCollection];
1734 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1735 // because in some cases the presented view controller isn't a child of the
1736 // BVC in the view controller hierarchy (some intervening object isn't a
1737 // view controller).
1738 [self.presentedViewController
1739 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131740 // Update voice search bar visibility.
1741 [self updateVoiceSearchBarVisibilityAnimated:NO];
1742}
1743
1744- (void)viewWillTransitionToSize:(CGSize)size
1745 withTransitionCoordinator:
1746 (id<UIViewControllerTransitionCoordinator>)coordinator {
1747 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1748 [self dismissPopups];
1749 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1750}
1751
sdefresnee65fd872016-12-19 13:38:131752- (void)dismissViewControllerAnimated:(BOOL)flag
1753 completion:(void (^)())completion {
Rohit Rao685807a52017-11-10 20:50:111754 // It is an error to call this method when no VC is being presented.
1755 DCHECK(!TabSwitcherPresentsBVCEnabled() || self.presentedViewController);
1756
Rohit Raoa668c022017-11-08 00:04:441757 // Some calling code invokes |dismissViewControllerAnimated:completion:|
1758 // multiple times. When the BVC is displayed using VC containment, multiple
1759 // calls are effectively idempotent because only the first call has any effect
1760 // and subsequent calls do nothing. However, when the BVC is presented,
1761 // subsequent calls end up dismissing the BVC itself. This is never what we
Rohit Rao685807a52017-11-10 20:50:111762 // want, so check for this case and return early. It is not enough to check
1763 // |self.dismissingModal| because some dismissals do not go through
1764 // -[BrowserViewController dismissViewControllerAnimated:completion:|.
Rohit Raoa668c022017-11-08 00:04:441765 // TODO(crbug.com/782338): Fix callers and remove this early return.
Rohit Rao685807a52017-11-10 20:50:111766 if (TabSwitcherPresentsBVCEnabled() &&
1767 (self.dismissingModal || self.presentedViewController.isBeingDismissed)) {
Rohit Raoa668c022017-11-08 00:04:441768 return;
1769 }
1770
sdefresnee65fd872016-12-19 13:38:131771 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271772 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131773 [super dismissViewControllerAnimated:flag
1774 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271775 BrowserViewController* strongSelf = weakSelf;
Mark Cogan5bd86ba2017-12-28 14:32:381776 strongSelf.dismissingModal = NO;
1777 strongSelf.dialogPresenterDelegateIsPresenting =
1778 NO;
sdefresnee65fd872016-12-19 13:38:131779 if (completion)
1780 completion();
Mark Cogan5bd86ba2017-12-28 14:32:381781 [strongSelf.dialogPresenter tryToPresent];
sdefresnee65fd872016-12-19 13:38:131782 }];
1783}
1784
1785- (void)presentViewController:(UIViewController*)viewControllerToPresent
1786 animated:(BOOL)flag
1787 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271788 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131789 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1790 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1791 // presented, to show a temporary view of the launch screen and then remove it
1792 // when the controller for the FRE has been presented. This fix should be
1793 // removed when the FRE startup code is rewritten.
1794 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1795 experimental_flags::AlwaysDisplayFirstRun()) &&
1796 !tests_hook::DisableFirstRun();
1797 // These if statements check that |presentViewController| is being called for
1798 // the FRE case.
1799 if (firstRunLaunch &&
1800 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1801 UINavigationController* navController =
1802 base::mac::ObjCCastStrict<UINavigationController>(
1803 viewControllerToPresent);
1804 if ([navController.topViewController
1805 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1806 self.hideStatusBar = YES;
1807
1808 // Load view from Launch Screen and add it to window.
1809 NSBundle* mainBundle = base::mac::FrameworkBundle();
1810 NSArray* topObjects =
1811 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1812 UIViewController* launchScreenController =
1813 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1814 // |launchScreenView| is loaded as an autoreleased object, and is retained
1815 // by the |completion| block below.
1816 UIView* launchScreenView = launchScreenController.view;
1817 launchScreenView.userInteractionEnabled = NO;
1818 launchScreenView.frame = self.view.window.bounds;
1819 [self.view.window addSubview:launchScreenView];
1820
1821 // Replace the completion handler sent to the superclass with one which
1822 // removes |launchScreenView| and resets the status bar. If |completion|
1823 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271824 __weak BrowserViewController* weakSelf = self;
1825 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131826 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271827 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131828 if (completion)
1829 completion();
stkhapuginc9eee7b2017-04-10 15:49:271830 };
sdefresnee65fd872016-12-19 13:38:131831 }
1832 }
1833
Mark Cogan5bd86ba2017-12-28 14:32:381834 self.dialogPresenterDelegateIsPresenting = YES;
1835 if ([self.sideSwipeController inSwipe]) {
1836 [self.sideSwipeController resetContentView];
justincohen7e61cd92016-12-24 00:38:171837 }
sdefresnee65fd872016-12-19 13:38:131838
1839 [super presentViewController:viewControllerToPresent
1840 animated:flag
1841 completion:finalCompletionHandler];
1842}
1843
Mark Cogan80aa28d2017-11-30 13:11:341844- (BOOL)shouldAutorotate {
Kurt Horimoto3a449ce2017-12-27 18:22:141845 if (self.presentedViewController.beingPresented ||
1846 self.presentedViewController.beingDismissed) {
1847 // Don't rotate while a presentation or dismissal animation is occurring.
Mark Cogan80aa28d2017-11-30 13:11:341848 return NO;
Mark Cogan5bd86ba2017-12-28 14:32:381849 } else if (_sideSwipeController &&
1850 ![self.sideSwipeController shouldAutorotate]) {
Mark Cogan80aa28d2017-11-30 13:11:341851 // Don't auto rotate if side swipe controller view says not to.
1852 return NO;
1853 } else {
1854 return [super shouldAutorotate];
1855 }
1856}
1857
Mark Cogan849244ee2017-12-29 15:57:191858- (UIStatusBarStyle)preferredStatusBarStyle {
1859 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
1860 : UIStatusBarStyleDefault;
1861}
1862
Mark Cogan776e0282018-01-02 09:00:061863#pragma mark - ** Private BVC Methods **
1864
Mark Cogan776e0282018-01-02 09:00:061865#pragma mark - Private Methods: BVC Initialization
sdefresnee65fd872016-12-19 13:38:131866
1867- (void)updateWithTabModel:(TabModel*)model
1868 browserState:(ios::ChromeBrowserState*)browserState {
1869 DCHECK(model);
1870 DCHECK(browserState);
1871 DCHECK(!_model);
1872 DCHECK(!_browserState);
1873 _browserState = browserState;
1874 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271875 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381876
sdefresnee65fd872016-12-19 13:38:131877 [_model addObserver:self];
1878
1879 if (!_isOffTheRecord) {
1880 [DefaultIOSWebViewFactory
1881 registerWebViewFactory:[ChromeWebViewFactory class]];
1882 }
1883 NSUInteger count = [_model count];
1884 for (NSUInteger index = 0; index < count; ++index)
1885 [self installDelegatesForTab:[_model tabAtIndex:index]];
1886
Jinho Bangb940d6f42018-01-18 22:39:041887 _imageFetcher = std::make_unique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121888 _browserState->GetRequestContext());
Gauthier Ambard929699412018-01-02 10:05:411889 self.imageSaver = [[ImageSaver alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131890
sdefresnedc432f42017-01-17 14:36:591891 // Register for bookmark changed notification (BookmarkModel may be null
1892 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131893 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591894 if (_bookmarkModel) {
Gauthier Ambard65e949b092017-11-29 08:46:201895 _bookmarkModelBridge.reset(
1896 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
sdefresnedc432f42017-01-17 14:36:591897 }
sdefresnee65fd872016-12-19 13:38:131898}
1899
sdefresnee65fd872016-12-19 13:38:131900- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221901 CGFloat statusBarHeight = StatusBarHeight();
1902 CGRect statusBarFrame =
1903 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1904 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1905 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131906 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221907 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1908 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1909 [_fakeStatusBarView layer].zPosition = 99;
1910 [[self view] addSubview:_fakeStatusBarView];
1911 } else {
1912 // Add a white bar on phone so that the status bar on the NTP is white.
1913 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1914 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131915 }
1916}
1917
1918// Create the UI elements. May or may not have valid browser state & tab model.
1919- (void)buildToolbarAndTabStrip {
1920 DCHECK([self isViewLoaded]);
1921 DCHECK(!_toolbarModelDelegate);
1922
Rohit Rao44f204302017-08-10 14:49:541923 // Initialize the prerender service before creating the toolbar controller.
1924 PrerenderService* prerenderService =
1925 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1926 if (prerenderService) {
1927 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131928 }
1929
1930 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201931 _toolbarModelDelegate.reset(
1932 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131933 _toolbarModelIOS.reset([_dependencyFactory
1934 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
Gauthier Ambard3aa4f742018-01-10 14:19:481935
Justin Cohen2d81c582018-01-22 14:46:441936 if (IsUIRefreshPhase1Enabled()) {
Gauthier Ambard4d485dd2018-01-11 12:43:441937 PrimaryToolbarCoordinator* topToolbarCoordinator =
Gauthier Ambard948048c2018-01-11 12:38:261938 [[PrimaryToolbarCoordinator alloc] initWithBrowserState:_browserState];
Gauthier Ambard4d485dd2018-01-11 12:43:441939 self.primaryToolbarCoordinator = topToolbarCoordinator;
1940 topToolbarCoordinator.delegate = self;
1941 topToolbarCoordinator.URLLoader = self;
1942 topToolbarCoordinator.webStateList = [_model webStateList];
1943 topToolbarCoordinator.dispatcher = self.dispatcher;
1944 [topToolbarCoordinator start];
1945
Gauthier Ambarddf60b46ef2018-01-17 15:30:431946 SecondaryToolbarCoordinator* bottomToolbarCoordinator = [
1947 [SecondaryToolbarCoordinator alloc] initWithBrowserState:_browserState];
Gauthier Ambard4d485dd2018-01-11 12:43:441948 self.secondaryToolbarCoordinator = bottomToolbarCoordinator;
Gauthier Ambarddf60b46ef2018-01-17 15:30:431949 bottomToolbarCoordinator.webStateList = [_model webStateList];
Gauthier Ambard4d485dd2018-01-11 12:43:441950 bottomToolbarCoordinator.dispatcher = self.dispatcher;
1951 [bottomToolbarCoordinator start];
1952
1953 ToolbarCoordinatorAdaptor* adaptor = [[ToolbarCoordinatorAdaptor alloc]
1954 initWithToolsMenuConfigurationProvider:self
1955 dispatcher:self.dispatcher];
1956 self.toolbarInterface = adaptor;
1957 [adaptor addToolbarCoordinator:topToolbarCoordinator];
1958 // TODO(crbug.com/800330): Add secondary toolbar.
Gauthier Ambard3aa4f742018-01-10 14:19:481959 } else {
1960 _toolbarCoordinator = [[LegacyToolbarCoordinator alloc]
1961 initWithBaseViewController:self
1962 toolsMenuConfigurationProvider:self
1963 dispatcher:self.dispatcher
1964 browserState:_browserState];
Gauthier Ambard4d485dd2018-01-11 12:43:441965 self.primaryToolbarCoordinator = _toolbarCoordinator;
1966 self.toolbarInterface = _toolbarCoordinator;
Gauthier Ambard3aa4f742018-01-10 14:19:481967 [_toolbarCoordinator
1968 setToolbarController:
1969 [_dependencyFactory
1970 newToolbarControllerWithDelegate:self
1971 urlLoader:self
1972 dispatcher:self.dispatcher]];
1973
1974 [_toolbarCoordinator start];
1975 }
Peter Laurense0b80f12017-11-21 07:52:401976
Gauthier Ambard8fd74b72018-01-25 18:02:141977 self.sideSwipeController.primaryToolbarInteractionHandler =
Gauthier Ambard83207452018-01-04 07:51:391978 self.primaryToolbarCoordinator;
Gauthier Ambard8fd74b72018-01-25 18:02:141979 self.sideSwipeController.secondaryToolbarSnapshotProvider =
1980 self.secondaryToolbarCoordinator;
Peter Laurense0b80f12017-11-21 07:52:401981
[email protected]76545762018-01-24 18:30:531982 [_dispatcher
1983 startDispatchingToTarget:self.primaryToolbarCoordinator.omniboxFocuser
1984 forProtocol:@protocol(OmniboxFocuser)];
[email protected]2f95cbe2018-01-15 14:24:571985 [_dispatcher startDispatchingToTarget:self.primaryToolbarCoordinator
1986 forProtocol:@protocol(FakeboxFocuser)];
Gauthier Ambard83207452018-01-04 07:51:391987 [self.legacyToolbarCoordinator setTabCount:[_model count]];
Kurt Horimotoe9b6002c2017-12-04 23:19:191988 [self updateBroadcastState];
stkhapuginc9eee7b2017-04-10 15:49:271989 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461990 _voiceSearchController->SetDelegate(
Gauthier Ambard83207452018-01-04 07:51:391991 [self.primaryToolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131992
sdefresnee65fd872016-12-19 13:38:131993 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031994 self.tabStripCoordinator =
1995 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1996 self.tabStripCoordinator.browserState = _browserState;
1997 self.tabStripCoordinator.dispatcher = _dispatcher;
1998 self.tabStripCoordinator.tabModel = _model;
1999 self.tabStripCoordinator.presentationProvider = self;
2000 self.tabStripCoordinator.animationWaitDuration =
Kurt Horimoto62e97c72017-11-03 19:51:472001 kLegacyFullscreenControllerToolbarAnimationDuration;
edchinf5150c682017-09-18 02:50:032002 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:132003 }
2004
2005 // Create infobar container.
2006 if (!_infoBarContainerDelegate) {
2007 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
2008 _infoBarContainer.reset(
2009 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
2010 }
2011}
2012
Jean-François Geyelined4cde72017-10-11 11:34:502013- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:112014 NSLayoutYAxisAnchor* topAnchor;
2015 // On iPad, the toolbar is underneath the tab strip.
2016 // On iPhone, it is underneath the top of the screen.
2017 if (IsIPadIdiom()) {
2018 topAnchor = self.tabStripView.bottomAnchor;
2019 } else {
2020 topAnchor = [self view].topAnchor;
2021 }
2022
Gauthier Ambard83207452018-01-04 07:51:392023 [self.legacyToolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:502024
Gauthier Ambard83207452018-01-04 07:51:392025 self.primaryToolbarOffsetConstraint =
Gauthier Ambard964f1b52018-01-10 14:00:092026 [self.primaryToolbarCoordinator.viewController.view.topAnchor
Justin Cohen9fe9ef672017-12-01 20:37:432027 constraintEqualToAnchor:topAnchor];
Jean-François Geyelined4cde72017-10-11 11:34:502028 [NSLayoutConstraint activateConstraints:@[
Gauthier Ambard83207452018-01-04 07:51:392029 self.primaryToolbarOffsetConstraint,
Gauthier Ambard964f1b52018-01-10 14:00:092030 [self.primaryToolbarCoordinator.viewController.view.leadingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:502031 constraintEqualToAnchor:[self view].leadingAnchor],
Gauthier Ambard964f1b52018-01-10 14:00:092032 [self.primaryToolbarCoordinator.viewController.view.trailingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:502033 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:502034 ]];
Gauthier Ambard0e6da242018-01-10 15:27:032035 if (self.secondaryToolbarCoordinator) {
2036 [NSLayoutConstraint activateConstraints:@[
2037 [self.secondaryToolbarCoordinator.viewController.view.leadingAnchor
2038 constraintEqualToAnchor:[self view].leadingAnchor],
2039 [self.secondaryToolbarCoordinator.viewController.view.trailingAnchor
2040 constraintEqualToAnchor:[self view].trailingAnchor],
2041 [self.secondaryToolbarCoordinator.viewController.view.bottomAnchor
2042 constraintEqualToAnchor:[self view].bottomAnchor],
2043 ]];
2044 }
Jean-François Geyelined4cde72017-10-11 11:34:502045 [[self view] layoutIfNeeded];
2046}
2047
sdefresnee65fd872016-12-19 13:38:132048// Enable functionality that only makes sense if the views are loaded and
2049// both browser state and tab model are valid.
2050- (void)addUIFunctionalityForModelAndBrowserState {
2051 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:052052 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:132053 DCHECK(_model);
2054 DCHECK([self isViewLoaded]);
2055
2056 [self.sideSwipeController addHorizontalGesturesToView:self.view];
2057
Rohit Raoaf46af92017-08-10 12:52:302058 infobars::InfoBarManager* infoBarManager = nullptr;
2059 if (_model.currentTab) {
2060 DCHECK(_model.currentTab.webState);
2061 infoBarManager =
2062 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
2063 }
sdefresnee65fd872016-12-19 13:38:132064 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
2065
sczsdd860eba2017-08-10 01:55:382066 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:432067 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
2068 initWithBaseViewController:self];
2069 _activityServiceCoordinator.dispatcher = _dispatcher;
2070 _activityServiceCoordinator.tabModel = _model;
2071 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:462072 _activityServiceCoordinator.positionProvider =
Gauthier Ambardc4d04f12018-01-04 16:58:242073 [self.primaryToolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:432074 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:432075
Rohit Raocda0a992017-08-16 15:37:112076 _qrScannerCoordinator =
2077 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
2078 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:052079 _qrScannerCoordinator.loadProvider =
Gauthier Ambard83207452018-01-04 07:51:392080 [self.primaryToolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:112081 _qrScannerCoordinator.presentationProvider = self;
2082
Kurt Horimoto238ae692017-12-22 23:55:472083 _tabHistoryCoordinator = [[LegacyTabHistoryCoordinator alloc]
2084 initWithBaseViewController:self
2085 browserState:_browserState];
sczsdd860eba2017-08-10 01:55:382086 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:462087 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard5e6d17ed2018-01-16 08:08:102088 [self.legacyToolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:382089 _tabHistoryCoordinator.tabModel = _model;
2090 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:462091 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard83207452018-01-04 07:51:392092 [self.primaryToolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:382093
sczs6ae47ad2017-09-06 17:26:532094 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:202095 _sadTabCoordinator.baseViewController = self;
2096 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:532097
sczs281fbdc22017-12-20 20:59:062098 // If there are any existing SadTabHelpers in |_model|, update the helpers
2099 // delegate with the new |_sadTabCoordinator|.
2100 for (NSUInteger i = 0; i < _model.count; i++) {
2101 SadTabTabHelper* sadTabHelper =
2102 SadTabTabHelper::FromWebState([_model tabAtIndex:i].webState);
2103 DCHECK(sadTabHelper);
2104 if (sadTabHelper) {
2105 sadTabHelper->SetDelegate(_sadTabCoordinator);
2106 }
2107 }
2108
Kurt Horimoto084d73b2017-12-22 23:50:462109 _pageInfoCoordinator = [[PageInfoLegacyCoordinator alloc]
2110 initWithBaseViewController:self
2111 browserState:_browserState];
Gregory Chatzinoffdf93d692017-09-09 01:32:272112 _pageInfoCoordinator.dispatcher = _dispatcher;
2113 _pageInfoCoordinator.loader = self;
2114 _pageInfoCoordinator.presentationProvider = self;
2115 _pageInfoCoordinator.tabModel = _model;
2116
Louis Romerod11747a2017-10-20 20:10:352117 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
2118 _externalSearchCoordinator.dispatcher = _dispatcher;
2119
mathp9b4c11d2017-07-06 20:24:132120 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272121 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132122 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202123 browserState:_browserState
2124 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052125 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192126 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132127 }
2128}
2129
2130// Set the frame for the various views. View must be loaded.
Justin Cohen4eeada32017-11-13 18:21:282131- (void)setUpViewLayout:(BOOL)initialLayout {
sdefresnee65fd872016-12-19 13:38:132132 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132133 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:132134
Justin Cohenb3170c32017-09-19 01:55:222135 // Update the fake toolbar background height.
2136 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2137 fakeStatusBarFrame.size.height = StatusBarHeight();
2138 _fakeStatusBarView.frame = fakeStatusBarFrame;
2139
sdefresnee65fd872016-12-19 13:38:132140
2141 // Position the toolbar next, either at the top of the browser view or
2142 // directly under the tabstrip.
Gauthier Ambard0e6da242018-01-10 15:27:032143 if (initialLayout) {
Gauthier Ambard3aa4f742018-01-10 14:19:482144 [self addChildViewController:self.primaryToolbarCoordinator.viewController];
Gauthier Ambard0e6da242018-01-10 15:27:032145 if (self.secondaryToolbarCoordinator)
2146 [self addChildViewController:self.secondaryToolbarCoordinator
2147 .viewController];
2148 }
Justin Cohenba27610e2017-11-08 19:34:452149 if (!IsSafeAreaCompatibleToolbarEnabled()) {
Gauthier Ambard83207452018-01-04 07:51:392150 CGFloat minY = self.headerOffset;
2151 if (self.tabStripView) {
2152 minY += CGRectGetHeight([self.tabStripView frame]);
2153 }
Gauthier Ambard964f1b52018-01-10 14:00:092154 CGRect toolbarFrame = _toolbarCoordinator.viewController.view.frame;
Gauthier Ambard83207452018-01-04 07:51:392155 toolbarFrame.origin = CGPointMake(0, minY);
2156 toolbarFrame.size.width = widthOfView;
Gauthier Ambard964f1b52018-01-10 14:00:092157 [_toolbarCoordinator.viewController.view setFrame:toolbarFrame];
Jean-François Geyelined4cde72017-10-11 11:34:502158 }
sdefresnee65fd872016-12-19 13:38:132159
2160 // Place the infobar container above the content area.
2161 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
Justin Cohen4eeada32017-11-13 18:21:282162 if (initialLayout)
2163 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
sdefresnee65fd872016-12-19 13:38:132164
Gauthier Ambard087e3572017-12-20 12:54:472165 // Place the toolbar controller above the infobar container and adds the
Gauthier Ambard470c50f2017-12-21 07:55:292166 // layout guides.
Gauthier Ambard087e3572017-12-20 12:54:472167 if (initialLayout) {
Gauthier Ambard3aa4f742018-01-10 14:19:482168 [[self view]
2169 insertSubview:self.primaryToolbarCoordinator.viewController.view
2170 aboveSubview:infoBarContainerView];
Gauthier Ambard0e6da242018-01-10 15:27:032171 if (self.secondaryToolbarCoordinator) {
2172 [[self view]
2173 insertSubview:self.secondaryToolbarCoordinator.viewController.view
Gauthier Ambard8fd74b72018-01-25 18:02:142174 aboveSubview:self.primaryToolbarCoordinator.viewController.view];
Gauthier Ambard0e6da242018-01-10 15:27:032175 }
Gauthier Ambard087e3572017-12-20 12:54:472176 AddNamedGuide(kOmniboxGuide, self.view);
Gauthier Ambard470c50f2017-12-21 07:55:292177 AddNamedGuide(kBackButtonGuide, self.view);
2178 AddNamedGuide(kForwardButtonGuide, self.view);
Gauthier Ambard522668e2018-01-03 14:10:072179 AddNamedGuide(kToolsMenuGuide, self.view);
2180 AddNamedGuide(kTabSwitcherGuide, self.view);
Gauthier Ambard087e3572017-12-20 12:54:472181 }
Gauthier Ambard0e6da242018-01-10 15:27:032182 if (initialLayout) {
Gauthier Ambard3aa4f742018-01-10 14:19:482183 [self.primaryToolbarCoordinator.viewController
2184 didMoveToParentViewController:self];
Gauthier Ambard0e6da242018-01-10 15:27:032185 if (self.secondaryToolbarCoordinator) {
2186 [self.secondaryToolbarCoordinator.viewController
2187 didMoveToParentViewController:self];
2188 }
2189 }
sdefresnee65fd872016-12-19 13:38:132190
sdefresnee65fd872016-12-19 13:38:132191 // Adjust the content area to be under the toolbar, for fullscreen or below
2192 // the toolbar is not fullscreen.
2193 CGRect contentFrame = [_contentArea frame];
2194 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222195 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2196 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132197 [_contentArea setFrame:contentFrame];
2198
edchincfe94ea2018-01-24 17:23:542199 if (initialLayout) {
2200 // Adjust the infobar container to be either at the bottom of the screen
2201 // (iPhone) or on the lower toolbar edge (iPad).
2202 CGRect infoBarFrame = contentFrame;
2203 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2204 infoBarFrame.size.height = 0;
2205 [infoBarContainerView setFrame:infoBarFrame];
2206 }
sdefresnee65fd872016-12-19 13:38:132207
2208 // Attach the typing shield to the content area but have it hidden.
Mark Cogan5bd86ba2017-12-28 14:32:382209 [self.typingShield setFrame:[_contentArea frame]];
Justin Cohen41b1f382017-12-04 15:22:082210 if (initialLayout) {
Mark Cogan5bd86ba2017-12-28 14:32:382211 [[self view] insertSubview:self.typingShield aboveSubview:_contentArea];
2212 [self.typingShield setHidden:YES];
Justin Cohen41b1f382017-12-04 15:22:082213 }
sdefresnee65fd872016-12-19 13:38:132214}
2215
sdefresnee65fd872016-12-19 13:38:132216- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2217 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442218 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132219
kkhorimotoa44349c12017-04-12 23:02:122220 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132221 // Hide findbar. |updateToolbar| will restore the findbar later.
2222 [self hideFindBarWithAnimation:NO];
2223
2224 // Make new content visible, resizing it first as the orientation may
2225 // have changed from the last time it was displayed.
2226 [[tab view] setFrame:_contentArea.bounds];
2227 [_contentArea displayContentView:[tab view]];
2228 }
2229 [self updateToolbar];
2230
2231 if (newSelection)
Gauthier Ambard83207452018-01-04 07:51:392232 [self.legacyToolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132233
2234 // Notify the Tab that it was displayed.
2235 [tab wasShown];
2236}
2237
2238- (void)initializeBookmarkInteractionController {
2239 if (_bookmarkInteractionController)
2240 return;
edchinbb8ba892017-09-12 15:44:032241 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2242 initWithBrowserState:_browserState
2243 loader:self
2244 parentController:self
2245 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132246}
2247
Mark Cogan776e0282018-01-02 09:00:062248- (void)setOverScrollActionControllerToStaticNativeContent:
2249 (StaticHtmlNativeContent*)nativeContent {
2250 if (!IsIPadIdiom()) {
2251 OverscrollActionsController* controller =
2252 [[OverscrollActionsController alloc]
2253 initWithScrollView:[nativeContent scrollView]];
2254 [controller setDelegate:self];
2255 OverscrollStyle style = _isOffTheRecord
2256 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
2257 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
2258 controller.style = style;
2259 nativeContent.overscrollActionsController = controller;
2260 }
2261}
2262
2263#pragma mark - Private Methods: UI Configuration, update and Layout
2264
sdefresnee65fd872016-12-19 13:38:132265// Update the state of back and forward buttons, hiding the forward button if
2266// there is nowhere to go. Assumes the model's current tab is up to date.
2267- (void)updateToolbar {
2268 // If the BVC has been partially torn down for low memory, wait for the
2269 // view rebuild to handle toolbar updates.
2270 if (!(_toolbarModelIOS && _browserState))
2271 return;
2272
2273 Tab* tab = [_model currentTab];
2274 if (![tab navigationManager])
2275 return;
Gauthier Ambard642e8a62018-01-25 14:52:422276 [self.legacyToolbarCoordinator updateToolbarState];
Gauthier Ambard83207452018-01-04 07:51:392277 [self.legacyToolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132278
Sylvain Defresnef5d2d952017-11-14 11:15:312279 if (_insertedTabWasPrerenderedTab && !_toolbarModelIOS->IsLoading())
Gauthier Ambard83207452018-01-04 07:51:392280 [self.primaryToolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132281
rohitrao005a6432017-03-16 20:52:422282 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2283 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132284 [self showFindBarWithAnimation:NO
2285 selectText:YES
2286 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362287 }
sdefresnee65fd872016-12-19 13:38:132288
2289 // Hide the toolbar if displaying phone NTP.
2290 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552291 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132292 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552293 if (item) {
2294 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132295 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2296 hideToolbar = isNTP && !_isOffTheRecord &&
Gauthier Ambard83207452018-01-04 07:51:392297 ![self.primaryToolbarCoordinator isOmniboxFirstResponder] &&
2298 ![self.primaryToolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132299 }
Gauthier Ambard964f1b52018-01-10 14:00:092300 [self.primaryToolbarCoordinator.viewController.view setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132301 }
2302}
2303
Kurt Horimotoe9b6002c2017-12-04 23:19:192304- (void)updateBroadcastState {
2305 self.broadcasting =
Kurt Horimotoea429dd2017-11-28 02:24:302306 self.active && self.viewVisible && !self.inNewTabAnimation;
Kurt Horimotoea429dd2017-11-28 02:24:302307}
2308
sdefresnee65fd872016-12-19 13:38:132309- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122310 self.dialogPresenter.active =
2311 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132312}
2313
2314- (void)dismissPopups {
Peter Laurense0b80f12017-11-21 07:52:402315 [self.dispatcher dismissToolsMenu];
Gregory Chatzinoffdf93d692017-09-09 01:32:272316 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282317 [_tabHistoryCoordinator dismissHistoryPopup];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262318 [self.tabTipBubblePresenter dismissAnimated:NO];
2319 [self.incognitoTabTipBubblePresenter dismissAnimated:NO];
Cooper Knaak33f9f402017-08-09 18:04:382320}
2321
Mark Cogan776e0282018-01-02 09:00:062322- (BOOL)isTabScrolledToTop:(Tab*)tab {
2323 CGPoint scrollOffset =
2324 tab.webState->GetWebViewProxy().scrollViewProxy.contentOffset;
2325
2326 // If there is a native controller, use the native controller's scroll offset.
2327 id nativeController = [self nativeControllerForTab:tab];
2328 if ([nativeController conformsToProtocol:@protocol(CRWNativeContent)] &&
2329 [nativeController respondsToSelector:@selector(scrollOffset)]) {
2330 scrollOffset = [nativeController scrollOffset];
2331 }
2332 return CGPointEqualToPoint(scrollOffset, CGPointZero);
2333}
2334
2335- (UIView*)footerView {
2336 return _voiceSearchBar;
2337}
2338
2339- (CGFloat)headerHeightForTab:(Tab*)tab {
2340 id nativeController = [self nativeControllerForTab:tab];
2341 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2342 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2343 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2344 // On iPhone, don't add any header height for ToolbarOwner native
2345 // controllers when they're displaying their own toolbar.
2346 return 0;
2347 }
2348
2349 NSArray<HeaderDefinition*>* views = [self headerViews];
2350
2351 CGFloat height = self.headerOffset;
2352 for (HeaderDefinition* header in views) {
2353 if (header.view && header.behaviour == Hideable) {
2354 height += CGRectGetHeight([header.view frame]) -
2355 header.heightAdjustement - header.inset;
2356 }
2357 }
2358
2359 return height - StatusBarHeight();
2360}
2361
2362- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
2363 atOffset:(CGFloat)headerOffset {
2364 CGFloat height = self.headerOffset;
2365 for (HeaderDefinition* header in headers) {
2366 CGFloat yOrigin = height - headerOffset - header.inset;
Gauthier Ambard83207452018-01-04 07:51:392367 BOOL isPrimaryToolbar =
Gauthier Ambard964f1b52018-01-10 14:00:092368 header.view == self.primaryToolbarCoordinator.viewController.view;
Mark Cogan776e0282018-01-02 09:00:062369 // Make sure the toolbarView's constraints are also updated. Leaving the
2370 // -setFrame call to minimize changes in this CL -- otherwise the way
2371 // toolbar_view manages it's alpha changes would also need to be updated.
2372 // TODO(crbug.com/778822): This can be cleaned up when the new fullscreen
2373 // is enabled.
Gauthier Ambard83207452018-01-04 07:51:392374 if (IsSafeAreaCompatibleToolbarEnabled() && isPrimaryToolbar &&
Mark Cogan776e0282018-01-02 09:00:062375 !IsIPadIdiom()) {
Gauthier Ambard83207452018-01-04 07:51:392376 self.primaryToolbarOffsetConstraint.constant = yOrigin;
Mark Cogan776e0282018-01-02 09:00:062377 }
2378 CGRect frame = [header.view frame];
2379 frame.origin.y = yOrigin;
2380 [header.view setFrame:frame];
2381 if (header.behaviour != Overlap)
2382 height += CGRectGetHeight(frame);
2383 }
2384}
2385
2386- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
2387 CGRect frame = [_contentArea frame];
2388 if (!fullScreen) {
2389 // Changing the origin here is unnecessary, it's set in page_animation_util.
2390 frame.size.height -= self.headerHeight;
2391 }
2392
2393 CGFloat shortAxis = frame.size.width;
2394 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
2395 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
2396 CGFloat aspectRatio = frame.size.height / frame.size.width;
2397 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
2398 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
2399 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
2400 CGRect cardFrame = {frame.origin, cardSize};
2401
2402 CardView* card =
2403 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
2404 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
2405 : CardCloseButtonSide::LEADING;
2406 [_contentArea addSubview:card];
2407 return card;
2408}
2409
2410#pragma mark - Private Methods: Showing and Dismissing Child UI
2411
Mark Cogan776e0282018-01-02 09:00:062412- (void)dismissRateThisAppDialog {
2413 if (_rateThisAppDialog) {
2414 base::RecordAction(base::UserMetricsAction(
2415 "IOSRateThisAppDialogDismissedProgramatically"));
2416 [_rateThisAppDialog dismiss];
2417 _rateThisAppDialog = nil;
2418 }
2419}
2420
2421#pragma mark - Private Methods: Bubble views
2422
Cooper Knaakd0a974cd2017-08-10 18:05:472423- (BubbleViewControllerPresenter*)
2424bubblePresenterForFeature:(const base::Feature&)feature
2425 direction:(BubbleArrowDirection)direction
2426 alignment:(BubbleAlignment)alignment
2427 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212428 DCHECK(self.browserState);
2429 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382430 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472431 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382432 }
2433 // Capture |weakSelf| instead of the feature engagement tracker object
2434 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2435 // the feature engagement tracker will remain pointing to invalid memory if
2436 // its owner (the ChromeBrowserState) is deallocated.
2437 __weak BrowserViewController* weakSelf = self;
2438 void (^dismissalCallback)(void) = ^() {
2439 BrowserViewController* strongSelf = weakSelf;
2440 if (strongSelf) {
2441 feature_engagement::TrackerFactory::GetForBrowserState(
2442 strongSelf.browserState)
2443 ->Dismissed(feature);
2444 }
2445 };
2446
Cooper Knaakd0a974cd2017-08-10 18:05:472447 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382448 [[BubbleViewControllerPresenter alloc] initWithText:text
2449 arrowDirection:direction
2450 alignment:alignment
2451 dismissalCallback:dismissalCallback];
2452
Cooper Knaakd0a974cd2017-08-10 18:05:472453 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132454}
2455
Cooper Knaak120cee5e2017-08-10 20:57:002456- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212457 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002458 // If the tab tip bubble has already been presented and the user is still
2459 // considered engaged, it can't be overwritten or set to |nil| or else it will
2460 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2461 // can be safely overwritten or set to |nil|.
2462 if (!self.tabTipBubblePresenter.isUserEngaged) {
2463 __weak BrowserViewController* weakSelf = self;
2464 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2465 [weakSelf presentNewTabTipBubble];
2466 };
2467
2468 // Because the new tab tip occurs on startup, the feature engagement
2469 // tracker's database is not guaranteed to be loaded by this time. For the
2470 // bubble to appear properly, a callback is used to guarantee the event data
2471 // is loaded before the check to see if the promotion should be displayed.
2472 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2473 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2474 }
2475}
2476
2477- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212478 DCHECK(self.browserState);
Gregory Chatzinofff192d5d2017-12-09 00:26:072479 // If the BVC is not visible, do not present the bubble.
2480 if (!self.viewVisible)
2481 return;
Gregory Chatzinoff876956212017-12-18 22:33:232482 // Do not present the bubble if there is no current tab or if the current tab
2483 // is the NTP.
Gregory Chatzinofff192d5d2017-12-09 00:26:072484 Tab* currentTab = [self.tabModel currentTab];
2485 if (!currentTab)
2486 return;
2487 if (currentTab.webState->GetVisibleURL() == kChromeUINewTabURL)
2488 return;
Gregory Chatzinoffe205f44642017-12-19 17:54:032489
2490 // Do not present the bubble if the tab is not scrolled to the top.
2491 if (![self isTabScrolledToTop:currentTab])
Gregory Chatzinoff876956212017-12-18 22:33:232492 return;
Gregory Chatzinofff192d5d2017-12-09 00:26:072493
Cooper Knaak120cee5e2017-08-10 20:57:002494 NSString* text =
2495 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2496 CGPoint tabSwitcherAnchor;
2497 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032498 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002499 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032500 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002501 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2502 } else {
Gauthier Ambard522668e2018-01-03 14:10:072503 if (base::FeatureList::IsEnabled(kCleanToolbar)) {
2504 UILayoutGuide* guide = FindNamedGuide(kTabSwitcherGuide, self.view);
2505 CGPoint anchorPoint =
2506 bubble_util::AnchorPoint(guide.layoutFrame, BubbleArrowDirectionUp);
2507 tabSwitcherAnchor =
2508 [guide.owningView convertPoint:anchorPoint
2509 toView:guide.owningView.window];
2510 } else {
Gauthier Ambard6e35ffb42018-01-05 09:57:202511 DCHECK([self.legacyToolbarCoordinator
Gauthier Ambard522668e2018-01-03 14:10:072512 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
Gauthier Ambard6e35ffb42018-01-05 09:57:202513 tabSwitcherAnchor = [self.legacyToolbarCoordinator
Gauthier Ambard522668e2018-01-03 14:10:072514 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2515 }
Cooper Knaak120cee5e2017-08-10 20:57:002516 }
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262517
Cooper Knaake963d6702017-08-11 21:03:112518 // If the feature engagement tracker does not consider it valid to display
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262519 // the new tab tip, then end early to prevent the potential reassignment
2520 // of the existing |tabTipBubblePresenter| to nil.
2521 BubbleViewControllerPresenter* presenter =
Cooper Knaak120cee5e2017-08-10 20:57:002522 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2523 direction:BubbleArrowDirectionUp
2524 alignment:BubbleAlignmentTrailing
2525 text:text];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262526 if (!presenter)
2527 return;
2528
2529 self.tabTipBubblePresenter = presenter;
2530
Cooper Knaak120cee5e2017-08-10 20:57:002531 [self.tabTipBubblePresenter presentInViewController:self
2532 view:self.view
2533 anchorPoint:tabSwitcherAnchor];
2534}
2535
Helen Yang9175bd52017-08-12 00:28:402536- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212537 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402538 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2539 // user is still considered engaged.
2540 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2541 __weak BrowserViewController* weakSelf = self;
2542 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2543 [weakSelf presentNewIncognitoTabTipBubble];
2544 };
2545
2546 // Use a callback in case the new incognito tab tip should be shown on
2547 // startup. This ensures that the tracker's database will be fully loaded
2548 // before checking if the promotion should be displayed.
2549 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2550 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2551 }
2552}
2553
2554- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212555 DCHECK(self.browserState);
Gregory Chatzinofff192d5d2017-12-09 00:26:072556 // If the BVC is not visible, do not present the bubble.
2557 if (!self.viewVisible)
2558 return;
2559
Gregory Chatzinoff876956212017-12-18 22:33:232560 // Do not present the bubble if there is no current tab.
2561 Tab* currentTab = [self.tabModel currentTab];
2562 if (!currentTab)
2563 return;
Gregory Chatzinoffe205f44642017-12-19 17:54:032564
2565 // Do not present the bubble if the tab is not scrolled to the top.
2566 if (![self isTabScrolledToTop:currentTab])
Gregory Chatzinoff876956212017-12-18 22:33:232567 return;
2568
Helen Yang9175bd52017-08-12 00:28:402569 NSString* text = l10n_util::GetNSStringWithFixup(
2570 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
Gauthier Ambard522668e2018-01-03 14:10:072571 CGPoint toolsButtonAnchor;
2572 if (base::FeatureList::IsEnabled(kCleanToolbar)) {
2573 UILayoutGuide* guide = FindNamedGuide(kToolsMenuGuide, self.view);
2574 CGPoint anchorPoint =
2575 bubble_util::AnchorPoint(guide.layoutFrame, BubbleArrowDirectionUp);
2576 toolsButtonAnchor = [guide.owningView convertPoint:anchorPoint
2577 toView:guide.owningView.window];
2578 } else {
Gauthier Ambard3aa4f742018-01-10 14:19:482579 DCHECK([self.legacyToolbarCoordinator
2580 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
Gauthier Ambard6e35ffb42018-01-05 09:57:202581 toolsButtonAnchor = [self.legacyToolbarCoordinator
Gauthier Ambard522668e2018-01-03 14:10:072582 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2583 }
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262584
2585 // If the feature engagement tracker does not consider it valid to display
2586 // the incognito tab tip, then end early to prevent the potential reassignment
2587 // of the existing |incognitoTabTipBubblePresenter| to nil.
2588 BubbleViewControllerPresenter* presenter =
Helen Yang9175bd52017-08-12 00:28:402589 [self bubblePresenterForFeature:feature_engagement::
2590 kIPHNewIncognitoTabTipFeature
2591 direction:BubbleArrowDirectionUp
2592 alignment:BubbleAlignmentTrailing
2593 text:text];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262594 if (!presenter)
2595 return;
2596
2597 self.incognitoTabTipBubblePresenter = presenter;
2598
Helen Yang9175bd52017-08-12 00:28:402599 [self.incognitoTabTipBubblePresenter
2600 presentInViewController:self
2601 view:self.view
2602 anchorPoint:toolsButtonAnchor];
gambarda675a2d2018-01-09 17:12:122603 [self.dispatcher triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402604}
2605
Mark Cogan776e0282018-01-02 09:00:062606#pragma mark - Private Methods: Find Bar UI
Gregory Chatzinoffe205f44642017-12-19 17:54:032607
Mark Cogan776e0282018-01-02 09:00:062608- (void)hideFindBarWithAnimation:(BOOL)animate {
2609 [_findBarController hideFindBarView:animate];
Gregory Chatzinoffe205f44642017-12-19 17:54:032610}
2611
Mark Cogan776e0282018-01-02 09:00:062612- (void)showFindBarWithAnimation:(BOOL)animate
2613 selectText:(BOOL)selectText
2614 shouldFocus:(BOOL)shouldFocus {
2615 DCHECK(_findBarController);
2616 Tab* tab = [_model currentTab];
2617 DCHECK(tab);
2618 CRWWebController* webController = tab.webController;
Mark Cogan849244ee2017-12-29 15:57:192619
Mark Cogan776e0282018-01-02 09:00:062620 CGRect referenceFrame = CGRectZero;
2621 if (IsIPadIdiom()) {
2622 referenceFrame = webController.visibleFrame;
2623 referenceFrame.origin.y -= kIPadFindBarOverlap;
Mark Cogan849244ee2017-12-29 15:57:192624 } else {
Mark Cogan776e0282018-01-02 09:00:062625 referenceFrame = _contentArea.frame;
Mark Cogan849244ee2017-12-29 15:57:192626 }
2627
Gauthier Ambard6e35ffb42018-01-05 09:57:202628 CGRect omniboxFrame;
2629 if (base::FeatureList::IsEnabled(kCleanToolbar)) {
2630 omniboxFrame = FindNamedGuide(kOmniboxGuide, self.view).layoutFrame;
2631 } else {
2632 omniboxFrame = [self.legacyToolbarCoordinator visibleOmniboxFrame];
2633 }
Mark Cogan776e0282018-01-02 09:00:062634 [_findBarController addFindBarView:animate
2635 intoView:self.view
2636 withFrame:referenceFrame
2637 alignWithFrame:omniboxFrame
2638 selectText:selectText];
2639 [self updateFindBar:YES shouldFocus:shouldFocus];
Mark Cogan849244ee2017-12-29 15:57:192640}
2641
Mark Cogan776e0282018-01-02 09:00:062642- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
2643 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
2644 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
2645 // in a crash.
2646 if (![_model currentTab]) {
2647 return;
2648 }
2649 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
2650 if (helper && helper->IsFindUIActive()) {
2651 if (initialUpdate && !_isOffTheRecord) {
2652 helper->RestoreSearchTerm();
Mark Cogan849244ee2017-12-29 15:57:192653 }
Mark Cogan776e0282018-01-02 09:00:062654
2655 [self setFramesForHeaders:[self headerViews]
2656 atOffset:[self currentHeaderOffset]];
2657 [_findBarController updateView:helper->GetFindResult()
2658 initialUpdate:initialUpdate
2659 focusTextfield:shouldFocus];
2660 } else {
2661 [self hideFindBarWithAnimation:YES];
Mark Cogan849244ee2017-12-29 15:57:192662 }
2663}
2664
Mark Cogan776e0282018-01-02 09:00:062665- (void)reshowFindBarIfNeededWithCoordinator:
2666 (id<UIViewControllerTransitionCoordinator>)coordinator {
2667 if (![_findBarController isFindInPageShown])
2668 return;
2669
2670 // Record focused state.
2671 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
2672
2673 [self hideFindBarWithAnimation:NO];
2674
2675 __weak BrowserViewController* weakSelf = self;
2676 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) =
2677 ^(id<UIViewControllerTransitionCoordinatorContext> context) {
2678 BrowserViewController* strongSelf = weakSelf;
2679 if (strongSelf)
2680 [strongSelf showFindBarWithAnimation:NO
2681 selectText:NO
2682 shouldFocus:isFocusedBeforeReshow];
2683 };
2684
2685 BOOL enqueued =
2686 [coordinator animateAlongsideTransition:nil completion:completion];
2687 if (!enqueued) {
2688 completion(nil);
2689 }
2690}
2691
2692#pragma mark - Private Methods: Alerts
2693
2694- (void)showErrorAlertWithStringTitle:(NSString*)title
2695 message:(NSString*)message {
2696 // Dismiss current alert.
2697 [_alertCoordinator stop];
2698
2699 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
2700 message:message
2701 viewController:self];
2702 [_alertCoordinator start];
2703}
2704
2705- (void)showSnackbar:(NSString*)text {
2706 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
2707 message.accessibilityLabel = text;
2708 message.duration = 2.0;
2709 message.category = kBrowserViewControllerSnackbarCategory;
2710 [self.dispatcher showSnackbarMessage:message];
2711}
2712
2713#pragma mark - Private Methods: Tap handling
sdefresnee65fd872016-12-19 13:38:132714
Mark Cogandfcdea72017-07-18 13:47:382715- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032716 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2717 _lastTapPoint = CGPointZero;
2718 } else {
2719 _lastTapPoint =
2720 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132721 }
Mark Cogane01ebce2017-07-12 19:31:032722 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132723}
2724
2725- (CGPoint)lastTapPoint {
2726 if (CACurrentMediaTime() - _lastTapTime < 1) {
2727 return _lastTapPoint;
2728 }
2729 return CGPointZero;
2730}
2731
2732- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2733 UIView* view = gestureRecognizer.view;
2734 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2735 _lastTapPoint =
2736 [[view superview] convertPoint:viewCoordinate toView:self.view];
2737 _lastTapTime = CACurrentMediaTime();
2738}
2739
Mark Cogan776e0282018-01-02 09:00:062740#pragma mark - Private Methods: Tab creation and selection
sdefresnee65fd872016-12-19 13:38:132741
2742// Called when either a tab finishes loading or when a tab with finished content
2743// is added directly to the model via pre-rendering.
2744- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2745 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2746
2747 // Persist the session on a delay.
2748 [_model saveSessionImmediately:NO];
2749}
2750
2751- (Tab*)addSelectedTabWithURL:(const GURL&)url
2752 postData:(TemplateURLRef::PostContent*)postData
2753 transition:(ui::PageTransition)transition {
2754 return [self addSelectedTabWithURL:url
2755 postData:postData
2756 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492757 transition:transition
2758 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132759}
2760
sdefresnee65fd872016-12-19 13:38:132761- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2762 postData:(TemplateURLRef::PostContent*)postData
2763 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492764 transition:(ui::PageTransition)transition
2765 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132766 if (position == NSNotFound)
2767 position = [_model count];
2768 DCHECK(position <= [_model count]);
2769
2770 web::NavigationManager::WebLoadParams params(URL);
2771 params.transition_type = transition;
2772 if (postData) {
2773 // Extract the content type and post params from |postData| and add them
2774 // to the load params.
2775 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2776 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2777 length:postData->second.length()];
[email protected]52706512017-12-29 17:50:182778 params.post_data = data;
2779 params.extra_headers = @{@"Content-Type" : contentType};
sdefresnee65fd872016-12-19 13:38:132780 }
Olivier Robind508a5632017-07-19 16:29:492781
2782 if (tabAddedCompletion) {
2783 if (self.foregroundTabWasAddedCompletionBlock) {
2784 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2785 self.foregroundTabWasAddedCompletionBlock;
2786 self.foregroundTabWasAddedCompletionBlock = ^{
2787 oldForegroundTabWasAddedCompletionBlock();
2788 tabAddedCompletion();
2789 };
2790 } else {
2791 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2792 }
2793 }
2794
sdefresnea6395912017-03-01 01:14:352795 Tab* tab = [_model insertTabWithLoadParams:params
2796 opener:nil
2797 openedByDOM:NO
2798 atIndex:position
2799 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132800 return tab;
2801}
2802
sdefresnee65fd872016-12-19 13:38:132803- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322804 web::WebState* webState = tab.webState;
2805 if (!webState)
2806 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392807 web::NavigationItem* visibleItem =
2808 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322809 if (!visibleItem)
2810 return NO;
2811 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132812}
2813
sdefresnee65fd872016-12-19 13:38:132814- (UIImageView*)pageOpenCloseAnimationView {
2815 CGRect frame = [_contentArea bounds];
2816
Mark Cogan849244ee2017-12-29 15:57:192817 frame.size.height = frame.size.height - self.headerHeight;
2818 frame.origin.y = self.headerHeight;
sdefresnee65fd872016-12-19 13:38:132819
stkhapuginf58b10d02017-04-10 13:36:172820 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132821 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2822 pageView.center = center;
2823
2824 pageView.backgroundColor = [UIColor whiteColor];
2825 return pageView;
2826}
2827
2828- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142829 // Unregistration happens when the Tab is removed from the TabModel.
Sylvain Defresnef5d2d952017-11-14 11:15:312830 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
2831
2832 // There should be no pre-rendered Tabs in TabModel.
2833 PrerenderService* prerenderService =
2834 PrerenderServiceFactory::GetForBrowserState(_browserState);
2835 DCHECK(!prerenderService ||
2836 !prerenderService->IsWebStatePrerendered(tab.webState));
edchincd32fdf2017-10-25 12:45:452837
Sylvain Defresne599df792018-01-11 13:14:582838 SnapshotTabHelper::FromWebState(tab.webState)->SetDelegate(self);
Sylvain Defresne17b8aa42017-12-21 16:17:172839
edchincd32fdf2017-10-25 12:45:452840 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2841 if (PasswordTabHelper* passwordTabHelper =
2842 PasswordTabHelper::FromWebState(tab.webState)) {
edchin6941b8492017-12-01 18:59:592843 passwordTabHelper->SetBaseViewController(self);
edchincd32fdf2017-10-25 12:45:452844 passwordTabHelper->SetDispatcher(self.dispatcher);
2845 passwordTabHelper->SetPasswordControllerDelegate(self);
2846 }
2847
edchin01c88912018-01-26 16:45:012848 if (AutofillTabHelper* autofillTabHelper =
2849 AutofillTabHelper::FromWebState(tab.webState)) {
2850 autofillTabHelper->SetBaseViewController(self);
2851 }
2852
sdefresnee65fd872016-12-19 13:38:132853 tab.dialogDelegate = self;
sdefresnee65fd872016-12-19 13:38:132854 tab.passKitDialogProvider = self;
Kurt Horimotoa5a922a2017-11-07 00:21:092855 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472856 tab.legacyFullscreenControllerDelegate = self;
Kurt Horimoto803840622017-10-28 01:20:372857 }
sdefresnee65fd872016-12-19 13:38:132858 if (!IsIPadIdiom()) {
2859 tab.overscrollActionsControllerDelegate = self;
2860 }
olivierrobin9ce77b82017-01-12 17:29:192861 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132862 // Install the proper CRWWebController delegates.
2863 tab.webController.nativeProvider = self;
2864 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512865 // BrowserViewController presents SKStoreKitViewController on behalf of a
2866 // tab.
2867 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2868 if (tabHelper)
2869 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132870 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532871 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292872 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532873 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292874 }
Sylvain Defresnecacc3a52017-09-12 13:51:042875 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592876 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572877 NetExportTabHelper::CreateForWebState(tab.webState, self);
Mike Dougherty4620cf8e2017-10-31 23:37:092878 CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
Eugene But49a7c572017-12-11 20:54:152879 PassKitTabHelper::CreateForWebState(tab.webState, _passKitCoordinator);
edchin7af0f132018-01-19 01:20:252880 AppLauncherTabHelper::CreateForWebState(
2881 tab.webState, [[ExternalAppsLaunchPolicyDecider alloc] init],
2882 _appLauncherCoordinator);
edchincd32fdf2017-10-25 12:45:452883
Mark Coganca30df62017-11-20 14:29:112884 // The language detection helper accepts a callback from the translate
2885 // client, so must be created after it.
2886 // This will explode if the webState doesn't have a JS injection manager
2887 // (this only comes up in unit tests), so check for that and bypass the
2888 // init of the translation helpers if needed.
2889 // TODO(crbug.com/785238): Remove the need for this check.
2890 if (tab.webState->GetJSInjectionReceiver()) {
2891 ChromeIOSTranslateClient::CreateForWebState(tab.webState,
2892 _languageSelectionCoordinator);
2893 language::IOSLanguageDetectionTabHelper::CreateForWebState(
2894 tab.webState,
2895 ChromeIOSTranslateClient::FromWebState(tab.webState)
2896 ->GetTranslateDriver()
2897 ->CreateLanguageDetectionCallback(),
2898 UrlLanguageHistogramFactory::GetForBrowserState(self.browserState));
2899 }
2900
edchincd32fdf2017-10-25 12:45:452901 if (AccountConsistencyService* accountConsistencyService =
2902 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2903 self.browserState)) {
2904 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552905 }
sdefresnee65fd872016-12-19 13:38:132906}
2907
sdefresne49cf2862017-03-15 13:46:142908- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112909 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
edchincd32fdf2017-10-25 12:45:452910
2911 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2912 if (PasswordTabHelper* passwordTabHelper =
Sylvain Defresne17b8aa42017-12-21 16:17:172913 PasswordTabHelper::FromWebState(tab.webState)) {
edchincd32fdf2017-10-25 12:45:452914 passwordTabHelper->SetDispatcher(nil);
Sylvain Defresne17b8aa42017-12-21 16:17:172915 }
edchincd32fdf2017-10-25 12:45:452916
sdefresne49cf2862017-03-15 13:46:142917 tab.dialogDelegate = nil;
sdefresne49cf2862017-03-15 13:46:142918 tab.passKitDialogProvider = nil;
Kurt Horimotoa5a922a2017-11-07 00:21:092919 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472920 tab.legacyFullscreenControllerDelegate = nil;
Kurt Horimoto803840622017-10-28 01:20:372921 }
sdefresne49cf2862017-03-15 13:46:142922 if (!IsIPadIdiom()) {
2923 tab.overscrollActionsControllerDelegate = nil;
2924 }
2925 tab.tabHeadersDelegate = nil;
sdefresne49cf2862017-03-15 13:46:142926 tab.webController.nativeProvider = nil;
2927 tab.webController.swipeRecognizerProvider = nil;
2928 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2929 if (tabHelper)
2930 tabHelper->SetLauncher(nil);
2931 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452932 if (AccountConsistencyService* accountConsistencyService =
2933 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2934 self.browserState)) {
2935 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2936 }
Sylvain Defresne17b8aa42017-12-21 16:17:172937
2938 SnapshotTabHelper::FromWebState(tab.webState)->SetDelegate(nil);
sdefresne49cf2862017-03-15 13:46:142939}
2940
Gauthier Ambard64396902017-12-08 10:14:582941- (void)tabSelected:(Tab*)tab notifyToolbar:(BOOL)notifyToolbar {
sdefresnee65fd872016-12-19 13:38:132942 DCHECK(tab);
2943
2944 // Ignore changes while the tab stack view is visible (or while suspended).
2945 // The display will be refreshed when this view becomes active again.
2946 if (!self.visible || ![_model webUsageEnabled])
2947 return;
2948
Gauthier Ambard64396902017-12-08 10:14:582949 [self displayTab:tab isNewSelection:notifyToolbar];
sdefresnee65fd872016-12-19 13:38:132950
kkhorimotoa44349c12017-04-12 23:02:122951 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132952 // Now that the new tab has been displayed, return to normal. Rather than
2953 // keep a reference to the previous tab, just turn off preview mode for all
2954 // tabs (since doing so is a no-op for the tabs that don't have it set).
2955 _expectingForegroundTab = NO;
Sylvain Defresne5b1174cb2018-01-16 15:47:582956
2957 WebStateList* webStateList = _model.webStateList;
2958 for (int index = 0; index < webStateList->count(); ++index) {
2959 web::WebState* webState = webStateList->GetWebStateAt(index);
2960 PagePlaceholderTabHelper::FromWebState(webState)
Sylvain Defresne448351332017-12-27 10:38:362961 ->CancelPlaceholderForNextNavigation();
sdefresnee65fd872016-12-19 13:38:132962 }
2963 }
2964}
2965
Mark Cogan776e0282018-01-02 09:00:062966- (id)nativeControllerForTab:(Tab*)tab {
2967 id nativeController = tab.webController.nativeController;
2968 return nativeController ? nativeController : _temporaryNativeController;
2969}
2970
Mark Cogan776e0282018-01-02 09:00:062971#pragma mark - Private Methods: Voice Search
2972
2973- (void)ensureVoiceSearchControllerCreated {
2974 if (!_voiceSearchController) {
2975 VoiceSearchProvider* provider =
2976 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2977 if (provider) {
2978 _voiceSearchController =
2979 provider->CreateVoiceSearchController(_browserState);
2980 _voiceSearchController->SetDelegate(
Gauthier Ambard83207452018-01-04 07:51:392981 [self.primaryToolbarCoordinator voiceSearchDelegate]);
Mark Cogan776e0282018-01-02 09:00:062982 }
2983 }
2984}
2985
2986- (void)ensureVoiceSearchBarCreated {
2987 if (_voiceSearchBar)
2988 return;
2989
2990 CGFloat width = CGRectGetWidth([[self view] bounds]);
2991 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2992 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
2993 _voiceSearchBar = ios::GetChromeBrowserProvider()
2994 ->GetVoiceSearchProvider()
2995 ->BuildVoiceSearchBar(frame, self.dispatcher);
2996 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2997 [_voiceSearchBar setHidden:YES];
2998 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2999 UIViewAutoresizingFlexibleWidth];
3000 [self.view insertSubview:_voiceSearchBar
3001 belowSubview:_infoBarContainer->view()];
3002}
3003
3004- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
3005 // Voice search bar exists and is shown/hidden.
3006 BOOL show = self.shouldShowVoiceSearchBar;
3007 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
3008 return;
3009
3010 // Voice search bar doesn't exist and thus is not visible.
3011 if (!_voiceSearchBar && !show)
3012 return;
3013
3014 if (animated)
3015 [_voiceSearchBar animateToBecomeVisible:show];
3016 else
3017 _voiceSearchBar.hidden = !show;
3018}
3019
3020#pragma mark - Private Methods: Reading List
3021
3022- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
3023 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3024
3025 ReadingListModel* readingModel =
3026 ReadingListModelFactory::GetForBrowserState(_browserState);
3027 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3028 reading_list::ADDED_VIA_CURRENT_APP);
3029
3030 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
3031 [self showSnackbar:l10n_util::GetNSString(
3032 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
3033}
3034
3035#pragma mark - ** Protocol Implementations and Helpers **
3036
Sylvain Defresne599df792018-01-11 13:14:583037#pragma mark - SnapshotGeneratorDelegate methods
sdefresnee65fd872016-12-19 13:38:133038
Sylvain Defresne599df792018-01-11 13:14:583039- (BOOL)canTakeSnapshotForWebState:(web::WebState*)webState {
3040 DCHECK(webState);
3041 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3042 DCHECK([self.tabModel indexOfTab:tab] != NSNotFound);
3043 return !PagePlaceholderTabHelper::FromWebState(webState)
3044 ->displaying_placeholder();
3045}
3046
3047- (UIEdgeInsets)snapshotEdgeInsetsForWebState:(web::WebState*)webState {
3048 DCHECK(webState);
3049 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3050 DCHECK([self.tabModel indexOfTab:tab] != NSNotFound);
3051
3052 CGFloat headerHeight = [self headerHeightForTab:tab];
3053 id nativeController = [self nativeControllerForTab:tab];
3054 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3055 headerHeight += [nativeController toolbarHeight];
3056 return UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3057}
3058
3059- (NSArray<SnapshotOverlay*>*)snapshotOverlaysForWebState:
3060 (web::WebState*)webState {
3061 DCHECK(webState);
3062 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3063 DCHECK([self.tabModel indexOfTab:tab] != NSNotFound);
3064 if (!self.tabModel.webUsageEnabled) {
3065 return @[];
sdefresnee65fd872016-12-19 13:38:133066 }
Sylvain Defresne599df792018-01-11 13:14:583067
3068 NSMutableArray* overlays = [NSMutableArray array];
sdefresnee65fd872016-12-19 13:38:133069 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
3070 if (voiceSearchView) {
3071 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:273072 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:133073 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:273074 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:133075 [overlays addObject:voiceSearchOverlay];
3076 }
3077 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
3078 if (infoBarView) {
3079 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:273080 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:133081 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:273082 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:133083 [overlays addObject:infoBarOverlay];
3084 }
3085 return overlays;
3086}
3087
Sylvain Defresne599df792018-01-11 13:14:583088- (void)willUpdateSnapshotForWebState:(web::WebState*)webState {
3089 DCHECK(webState);
3090 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3091 DCHECK([self.tabModel indexOfTab:tab] != NSNotFound);
3092 id<CRWNativeContent> nativeController = [self nativeControllerForTab:tab];
3093 if ([nativeController respondsToSelector:@selector(willUpdateSnapshot)]) {
3094 [nativeController willUpdateSnapshot];
3095 }
3096 [tab willUpdateSnapshot];
3097}
3098
3099- (void)didUpdateSnapshotForWebState:(web::WebState*)webState
3100 withImage:(UIImage*)snapshot {
3101 DCHECK(webState);
3102 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3103 DCHECK([self.tabModel indexOfTab:tab] != NSNotFound);
3104 [self.tabModel notifyTabSnapshotChanged:tab withImage:snapshot];
3105}
3106
3107#pragma mark - SnapshotGeneratorDelegate helpers
sdefresnee65fd872016-12-19 13:38:133108
Mark Cogan849244ee2017-12-29 15:57:193109// Provides a view that encompasses currently displayed infobar(s) or nil
3110// if no infobar is presented.
sdefresnee65fd872016-12-19 13:38:133111- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
3112 if (IsIPadIdiom()) {
3113 // Not using overlays on iPad because the content is pushed down by
3114 // infobar and the transition between snapshot and fresh page can
3115 // cause both snapshot and real infobars to appear at the same time.
3116 return nil;
3117 }
3118 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:303119 if (currentTab && tab == currentTab) {
3120 DCHECK(currentTab.webState);
3121 infobars::InfoBarManager* infoBarManager =
3122 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:133123 if (infoBarManager->infobar_count() > 0) {
3124 DCHECK(_infoBarContainer);
3125 return _infoBarContainer->view();
3126 }
3127 }
3128 return nil;
3129}
3130
Mark Cogan849244ee2017-12-29 15:57:193131// Returns a vertical infobar offset relative to the tab content.
sdefresnee65fd872016-12-19 13:38:133132- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:273133 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:133134 // There is no UI representation for non-current tabs or there is
3135 // no _infoBarContainer instantiated yet.
3136 // Return offset outside of tab.
3137 return CGRectGetMaxY(self.view.frame);
3138 } else if (IsIPadIdiom()) {
3139 // The infobars on iPad are display at the top of a tab.
3140 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
3141 } else {
3142 // The infobars on iPhone are displayed at the bottom of a tab.
3143 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
3144 return CGRectGetMaxY(visibleFrame) -
3145 CGRectGetHeight(_infoBarContainer->view().frame);
3146 }
3147}
3148
Mark Cogan849244ee2017-12-29 15:57:193149// Provides a view that encompasses the voice search bar if it's displayed or
3150// nil if the voice search bar isn't displayed.
sdefresnee65fd872016-12-19 13:38:133151- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
3152 Tab* currentTab = [_model currentTab];
3153 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
3154 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
3155 return _voiceSearchBar;
3156 }
3157 return nil;
3158}
3159
Mark Cogan849244ee2017-12-29 15:57:193160// Returns a vertical voice search bar offset relative to the tab content.
sdefresnee65fd872016-12-19 13:38:133161- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
3162 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
3163 // There is no UI representation for non-current tabs or there is
3164 // no visible voice search. Return offset outside of tab.
3165 return CGRectGetMaxY(self.view.frame);
3166 } else {
3167 // The voice search bar on iPhone is displayed at the bottom of a tab.
3168 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
3169 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
3170 }
3171}
3172
sdefresnee65fd872016-12-19 13:38:133173#pragma mark - PassKitDialogProvider methods
3174
3175- (void)presentPassKitDialog:(NSData*)data {
3176 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:273177 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:133178 if (data)
stkhapuginc9eee7b2017-04-10 15:49:273179 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:133180 if (error || !data) {
3181 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:303182 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:133183 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:303184 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:133185 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
3186 // NULL, replace if with DCHECK).
3187 if (infoBarManager)
3188 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
3189 }
3190 } else {
3191 PKAddPassesViewController* passKitViewController =
3192 [_dependencyFactory newPassKitViewControllerForPass:pass];
3193 if (passKitViewController) {
3194 [self presentViewController:passKitViewController
3195 animated:YES
3196 completion:^{
3197 }];
3198 }
3199 }
3200}
3201
Tomasz Garbusb844e992017-09-29 12:44:553202#pragma mark - PasswordControllerDelegate methods
3203
3204- (BOOL)displaySignInNotification:(UIViewController*)viewController
3205 fromTabId:(NSString*)tabId {
3206 // Check if the call comes from currently visible tab.
3207 if ([tabId isEqual:[_model currentTab].tabId]) {
3208 [self addChildViewController:viewController];
3209 [self.view addSubview:viewController.view];
3210 [viewController didMoveToParentViewController:self];
3211 return YES;
3212 } else {
3213 return NO;
3214 }
3215}
3216
sdefresnee65fd872016-12-19 13:38:133217#pragma mark - CRWWebStateDelegate methods.
3218
eugenebut75a06fa72017-01-09 17:09:553219- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:513220 createNewWebStateForURL:(const GURL&)URL
3221 openerURL:(const GURL&)openerURL
3222 initiatedByUser:(BOOL)initiatedByUser {
3223 // Check if requested web state is a popup and block it if necessary.
3224 if (!initiatedByUser) {
3225 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
3226 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:103227 // It's possible for a page to inject a popup into a window created via
3228 // window.open before its initial load is committed. Rather than relying
3229 // on the last committed or pending NavigationItem's referrer policy, just
3230 // use ReferrerPolicyDefault.
3231 // TODO(crbug.com/719993): Update this to a more appropriate referrer
3232 // policy once referrer policies are correctly recorded in
3233 // NavigationItems.
3234 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:513235 helper->HandlePopup(URL, referrer);
3236 return nil;
3237 }
3238 }
3239
3240 // Requested web state should not be blocked from opening.
3241 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
Sylvain Defresne17b8aa42017-12-21 16:17:173242 SnapshotTabHelper::FromWebState(currentTab.webState)
3243 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
eugenebut275f5892017-03-09 22:20:513244
3245 // Tabs open by DOM are always renderer initiated.
3246 web::NavigationManager::WebLoadParams params(GURL{});
3247 params.transition_type = ui::PAGE_TRANSITION_LINK;
3248 params.is_renderer_initiated = true;
3249 Tab* childTab = [[self tabModel]
3250 insertTabWithLoadParams:params
3251 opener:currentTab
3252 openedByDOM:YES
3253 atIndex:TabModelConstants::kTabPositionAutomatically
3254 inBackground:NO];
3255 return childTab.webState;
3256}
3257
eugenebutb46b2122017-03-14 02:43:263258- (void)closeWebState:(web::WebState*)webState {
3259 // Only allow a web page to close itself if it was opened by DOM, or if there
3260 // are no navigation items.
3261 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:583262 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:263263
3264 if (![self tabModel])
3265 return;
3266
3267 NSUInteger index = [[self tabModel] indexOfTab:tab];
3268 if (index != NSNotFound)
3269 [[self tabModel] closeTabAtIndex:index];
3270}
3271
eugenebut275f5892017-03-09 22:20:513272- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:553273 openURLWithParams:(const web::WebState::OpenURLParams&)params {
3274 switch (params.disposition) {
3275 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
3276 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
3277 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:353278 insertTabWithURL:params.url
3279 referrer:params.referrer
3280 transition:params.transition
3281 opener:LegacyTabHelper::GetTabForWebState(webState)
3282 openedByDOM:NO
3283 atIndex:TabModelConstants::kTabPositionAutomatically
3284 inBackground:(params.disposition ==
3285 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:553286 return tab.webState;
3287 }
3288 case WindowOpenDisposition::CURRENT_TAB: {
3289 web::NavigationManager::WebLoadParams loadParams(params.url);
3290 loadParams.referrer = params.referrer;
3291 loadParams.transition_type = params.transition;
3292 loadParams.is_renderer_initiated = params.is_renderer_initiated;
3293 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
3294 return webState;
3295 }
eugenebutd0984e82017-02-22 23:47:513296 case WindowOpenDisposition::NEW_POPUP: {
3297 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:353298 insertTabWithURL:params.url
3299 referrer:params.referrer
3300 transition:params.transition
3301 opener:LegacyTabHelper::GetTabForWebState(webState)
3302 openedByDOM:YES
3303 atIndex:TabModelConstants::kTabPositionAutomatically
3304 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:513305 return tab.webState;
3306 }
eugenebut75a06fa72017-01-09 17:09:553307 default:
3308 NOTIMPLEMENTED();
3309 return nullptr;
3310 };
3311}
3312
Mike Dougherty4e6b3a32017-08-23 18:49:213313- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:133314 handleContextMenu:(const web::ContextMenuParams&)params {
3315 // Prevent context menu from displaying for a tab which is no longer the
3316 // current one.
3317 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:213318 return;
sdefresnee65fd872016-12-19 13:38:133319 }
3320
3321 // No custom context menu if no valid url is available in |params|.
3322 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:213323 return;
sdefresnee65fd872016-12-19 13:38:133324 }
3325
3326 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:133327
stkhapuginc9eee7b2017-04-10 15:49:273328 _contextMenuCoordinator =
3329 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
3330 params:params];
sdefresnee65fd872016-12-19 13:38:133331
3332 NSString* title = nil;
3333 ProceduralBlock action = nil;
3334
stkhapuginc9eee7b2017-04-10 15:49:273335 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:133336 GURL link = params.link_url;
3337 bool isLink = link.is_valid();
3338 GURL imageUrl = params.src_url;
3339 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:193340 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:133341
3342 if (isLink) {
3343 if (link.SchemeIs(url::kJavaScriptScheme)) {
3344 // Open
3345 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
3346 action = ^{
3347 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
3348 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
3349 };
3350 [_contextMenuCoordinator addItemWithTitle:title action:action];
3351 }
3352
3353 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193354 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133355
sdefresnee65fd872016-12-19 13:38:133356 // Open in New Tab.
3357 title = l10n_util::GetNSStringWithFixup(
3358 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
3359 action = ^{
3360 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003361 // The "New Tab" item in the context menu opens a new tab in the current
3362 // browser state. |isOffTheRecord| indicates whether or not the current
3363 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:133364 [weakSelf webPageOrderedOpen:link
3365 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003366 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:133367 inBackground:YES
3368 appendTo:kCurrentTab];
3369 };
3370 [_contextMenuCoordinator addItemWithTitle:title action:action];
3371 if (!_isOffTheRecord) {
3372 // Open in Incognito Tab.
3373 title = l10n_util::GetNSStringWithFixup(
3374 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
3375 action = ^{
3376 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
3377 [weakSelf webPageOrderedOpen:link
3378 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133379 inIncognito:YES
3380 inBackground:NO
3381 appendTo:kCurrentTab];
3382 };
3383 [_contextMenuCoordinator addItemWithTitle:title action:action];
3384 }
olivierrobin51d4cf42017-01-17 13:32:353385 }
gambard65d69152017-03-23 17:44:223386 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:353387 NSString* innerText = params.link_text;
3388 if ([innerText length] > 0) {
3389 // Add to reading list.
3390 title = l10n_util::GetNSStringWithFixup(
3391 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
3392 action = ^{
3393 Record(ACTION_READ_LATER, isImage, isLink);
3394 [weakSelf addToReadingListURL:link title:innerText];
3395 };
3396 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:533397 }
sdefresnee65fd872016-12-19 13:38:133398 }
3399 // Copy Link.
3400 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
3401 action = ^{
3402 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:283403 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:133404 };
3405 [_contextMenuCoordinator addItemWithTitle:title action:action];
3406 }
3407 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193408 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133409 // Save Image.
gambard98b4ddf2017-04-18 07:14:053410 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:133411 action = ^{
3412 Record(ACTION_SAVE_IMAGE, isImage, isLink);
3413 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
3414 };
3415 [_contextMenuCoordinator addItemWithTitle:title action:action];
3416 // Open Image.
3417 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
3418 action = ^{
3419 Record(ACTION_OPEN_IMAGE, isImage, isLink);
3420 [weakSelf loadURL:imageUrl
3421 referrer:referrer
3422 transition:ui::PAGE_TRANSITION_LINK
3423 rendererInitiated:YES];
3424 };
3425 [_contextMenuCoordinator addItemWithTitle:title action:action];
3426 // Open Image In New Tab.
3427 title = l10n_util::GetNSStringWithFixup(
3428 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
3429 action = ^{
3430 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
3431 [weakSelf webPageOrderedOpen:imageUrl
3432 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133433 inBackground:true
3434 appendTo:kCurrentTab];
3435 };
3436 [_contextMenuCoordinator addItemWithTitle:title action:action];
3437
3438 TemplateURLService* service =
3439 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103440 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133441 if (defaultURL && !defaultURL->image_url().empty() &&
3442 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3443 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3444 defaultURL->short_name());
3445 action = ^{
3446 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3447 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3448 };
3449 [_contextMenuCoordinator addItemWithTitle:title action:action];
3450 }
3451 }
3452
3453 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133454}
3455
eugenebutb739bdc2017-01-25 06:32:483456- (void)webState:(web::WebState*)webState
3457 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3458 // Display the action sheet with the arrow pointing at the top center of the
3459 // web contents.
sdefresne0452a9d2017-02-09 15:33:283460 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483461 UIView* view = webState->GetView();
3462 CGPoint dialogLocation =
3463 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283464 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033465 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173466 helper->PresentDialog(dialogLocation,
3467 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053468 handler(shouldContinue);
3469 }));
eugenebutb739bdc2017-01-25 06:32:483470}
3471
sdefresnee65fd872016-12-19 13:38:133472- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3473 (web::WebState*)webState {
3474 return _javaScriptDialogPresenter.get();
3475}
3476
eugenebut63232102017-01-19 16:19:403477- (void)webState:(web::WebState*)webState
3478 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3479 proposedCredential:(NSURLCredential*)proposedCredential
3480 completionHandler:(void (^)(NSString* username,
3481 NSString* password))handler {
3482 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3483 proposedCredential:proposedCredential
3484 webState:webState
3485 completionHandler:handler];
3486}
3487
Mark Cogan776e0282018-01-02 09:00:063488#pragma mark - CRWWebStateDelegate helpers
3489
3490// Evaluates Javascript asynchronously using the current page context.
3491- (void)openJavascript:(NSString*)javascript {
3492 DCHECK(javascript);
3493 javascript = [javascript stringByRemovingPercentEncoding];
3494 web::WebState* webState = [[_model currentTab] webState];
3495 if (webState) {
3496 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3497 }
3498}
3499
3500// Performs a search with the image at the given url. The referrer is used to
3501// download the image.
3502- (void)searchByImageAtURL:(const GURL&)url
3503 referrer:(const web::Referrer)referrer {
3504 DCHECK(url.is_valid());
3505 __weak BrowserViewController* weakSelf = self;
3506 const GURL image_source_url = url;
3507 image_fetcher::IOSImageDataFetcherCallback callback =
3508 ^(NSData* data, const image_fetcher::RequestMetadata& metadata) {
3509 DCHECK(data);
3510 dispatch_async(dispatch_get_main_queue(), ^{
3511 [weakSelf searchByImageData:data atURL:image_source_url];
3512 });
3513 };
3514 _imageFetcher->FetchImageDataWebpDecoded(
3515 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3516 web::PolicyForNavigation(url, referrer));
3517}
3518
3519// Performs a search using |data| and |imageURL| as inputs.
3520- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3521 NSData* imageData = data;
3522 UIImage* image = [UIImage imageWithData:imageData];
3523 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3524 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3525 // kSearchByImageMaxImageHeight).
3526 if (image &&
3527 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3528 (image.size.width > kSearchByImageMaxImageWidth ||
3529 image.size.height > kSearchByImageMaxImageHeight)) {
3530 CGSize newImageSize =
3531 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3532 image = [image gtm_imageByResizingToSize:newImageSize
3533 preserveAspectRatio:YES
3534 trimToFit:NO];
3535 imageData = UIImageJPEGRepresentation(image, 1.0);
3536 }
3537
3538 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3539 std::string byteString(bytes, [imageData length]);
3540
3541 TemplateURLService* templateUrlService =
3542 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
3543 const TemplateURL* defaultURL =
3544 templateUrlService->GetDefaultSearchProvider();
3545 DCHECK(!defaultURL->image_url().empty());
3546 DCHECK(defaultURL->image_url_ref().IsValid(
3547 templateUrlService->search_terms_data()));
3548 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3549 search_args.image_url = imageURL;
3550 search_args.image_thumbnail_content = byteString;
3551
3552 // Generate the URL and populate |post_content| with the content type and
3553 // HTTP body for the request.
3554 TemplateURLRef::PostContent post_content;
3555 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3556 search_args, templateUrlService->search_terms_data(), &post_content));
3557 [self addSelectedTabWithURL:result
3558 postData:&post_content
3559 transition:ui::PAGE_TRANSITION_TYPED];
3560}
3561
3562// Saves the image at the given URL on the system's album. The referrer is used
3563// to download the image.
3564- (void)saveImageAtURL:(const GURL&)url
3565 referrer:(const web::Referrer&)referrer {
3566 DCHECK(url.is_valid());
3567
Gauthier Ambard929699412018-01-02 10:05:413568 image_fetcher::IOSImageDataFetcherCallback callback =
3569 ^(NSData* data, const image_fetcher::RequestMetadata& metadata) {
3570 [self.imageSaver saveImageData:data withMetadata:metadata];
3571 };
Mark Cogan776e0282018-01-02 09:00:063572 _imageFetcher->FetchImageDataWebpDecoded(
3573 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3574 web::PolicyForNavigation(url, referrer));
3575}
3576
Kurt Horimoto62e97c72017-11-03 19:51:473577#pragma mark - LegacyFullscreenControllerDelegate methods
sdefresnee65fd872016-12-19 13:38:133578
Mark Cogan849244ee2017-12-29 15:57:193579// TODO(crbug.com/798064): Remove these methods and their helpers once the
3580// fullscreen migration is complete.
[email protected]a08e2bfb2017-11-24 19:16:453581- (void)redrawHeader {
3582 for (HeaderDefinition* header in self.headerViews) {
3583 [header.view setNeedsLayout];
3584 }
3585}
3586
Mark Cogan849244ee2017-12-29 15:57:193587- (CGFloat)headerHeightForLegacyFullscreen {
3588 return self.headerHeight;
sdefresnee65fd872016-12-19 13:38:133589}
3590
3591- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193592 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133593}
3594
3595- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453596 NSArray<HeaderDefinition*>* headers = [self headerViews];
3597 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133598 return 0.0;
3599
3600 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3601 // API documentation.
Sylvain Defresnef5d2d952017-11-14 11:15:313602 if (_insertedTabWasPrerenderedTab)
Mark Cogan849244ee2017-12-29 15:57:193603 return self.headerHeight;
sdefresnee65fd872016-12-19 13:38:133604
3605 UIView* topHeader = headers[0].view;
Mark Cogan849244ee2017-12-29 15:57:193606 return -(topHeader.frame.origin.y - self.headerOffset);
sdefresnee65fd872016-12-19 13:38:133607}
3608
Kurt Horimoto62e97c72017-11-03 19:51:473609- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133610 drawHeaderViewFromOffset:(CGFloat)headerOffset
3611 animate:(BOOL)animate {
Mark Cogan5bd86ba2017-12-28 14:32:383612 if ([self.sideSwipeController inSwipe])
sdefresnee65fd872016-12-19 13:38:133613 return;
3614
3615 CGRect footerFrame = CGRectZero;
3616 UIView* footer = nil;
3617 // Only animate the voice search bar if the tab is a voice search results tab.
3618 if ([_model currentTab].isVoiceSearchResultsTab) {
3619 footer = [self footerView];
3620 footerFrame = footer.frame;
3621 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3622 }
3623
stkhapugin952ecef2017-04-11 12:11:453624 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133625 void (^block)(void) = ^{
3626 [self setFramesForHeaders:headers atOffset:headerOffset];
3627 footer.frame = footerFrame;
3628 };
3629 void (^completion)(BOOL) = ^(BOOL finished) {
3630 [self fullScreenController:fullScreenController
3631 headerAnimationCompleted:finished
3632 offset:headerOffset];
3633 };
3634 if (animate) {
Kurt Horimoto62e97c72017-11-03 19:51:473635 [UIView
3636 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3637 delay:0.0
3638 options:UIViewAnimationOptionBeginFromCurrentState
3639 animations:block
3640 completion:completion];
sdefresnee65fd872016-12-19 13:38:133641 } else {
3642 block();
3643 completion(YES);
3644 }
3645}
3646
Kurt Horimoto62e97c72017-11-03 19:51:473647- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133648 drawHeaderViewFromOffset:(CGFloat)headerOffset
3649 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3650 changeTopContentPadding:(BOOL)changeTopContentPadding
3651 scrollingToOffset:(CGFloat)contentOffset {
3652 DCHECK(webViewProxy);
Mark Cogan5bd86ba2017-12-28 14:32:383653 if ([self.sideSwipeController inSwipe])
sdefresnee65fd872016-12-19 13:38:133654 return;
3655
3656 CGRect footerFrame;
3657 UIView* footer = nil;
3658 // Only animate the voice search bar if the tab is a voice search results tab.
3659 if ([_model currentTab].isVoiceSearchResultsTab) {
3660 footer = [self footerView];
3661 footerFrame = footer.frame;
3662 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3663 }
3664
stkhapugin952ecef2017-04-11 12:11:453665 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133666 void (^block)(void) = ^{
3667 [self setFramesForHeaders:headers atOffset:headerOffset];
3668 footer.frame = footerFrame;
3669 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3670 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3671 if (changeTopContentPadding)
3672 webViewProxy.topContentPadding = contentOffset;
3673 };
3674 void (^completion)(BOOL) = ^(BOOL finished) {
3675 [self fullScreenController:fullScreenController
3676 headerAnimationCompleted:finished
3677 offset:headerOffset];
3678 };
3679
Kurt Horimoto62e97c72017-11-03 19:51:473680 [UIView
3681 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3682 delay:0.0
3683 options:UIViewAnimationOptionBeginFromCurrentState
3684 animations:block
3685 completion:completion];
sdefresnee65fd872016-12-19 13:38:133686}
3687
Mark Cogan849244ee2017-12-29 15:57:193688#pragma mark - LegacyFullscreenControllerDelegate helpers
sdefresnee65fd872016-12-19 13:38:133689
Mark Cogan849244ee2017-12-29 15:57:193690// Returns the y coordinate for the footer's frame when animating the footer
3691// in/out of fullscreen.
3692- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3693 UIView* footer = [self footerView];
3694 CGFloat headerHeight = [self headerHeight];
3695 if (!footer || headerHeight == 0)
3696 return 0.0;
3697
3698 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3699 CGFloat offset = headerOffset * footerHeight / headerHeight;
3700 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3701}
3702
3703// Called when the animation for setting the header view's offset is finished.
3704// |completed| should indicate if the animation finished completely or was
3705// interrupted. |offset| should indicate the header offset after the animation.
3706// |dragged| should indicate if the header moved due to the user dragging.
3707- (void)fullScreenController:(LegacyFullscreenController*)controller
3708 headerAnimationCompleted:(BOOL)completed
3709 offset:(CGFloat)offset {
3710 if (completed)
3711 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133712}
3713
sdefresnee65fd872016-12-19 13:38:133714#pragma mark - OverscrollActionsControllerDelegate methods.
3715
3716- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053717 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133718 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053719 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383720 [self.dispatcher
3721 openNewTab:[OpenNewTabCommand
3722 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133723 break;
rohitrao922b7111c2017-01-03 14:31:053724 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243725 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133726 break;
Kurt Horimoto4ce19322017-11-28 19:10:473727 case OverscrollAction::REFRESH:
3728 [self reload];
sdefresnee65fd872016-12-19 13:38:133729 break;
rohitrao922b7111c2017-01-03 14:31:053730 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133731 NOTREACHED();
3732 break;
3733 }
3734}
3735
3736- (BOOL)shouldAllowOverscrollActions {
3737 return YES;
3738}
3739
3740- (UIView*)headerView {
Gauthier Ambard964f1b52018-01-10 14:00:093741 return self.primaryToolbarCoordinator.viewController.view;
sdefresnee65fd872016-12-19 13:38:133742}
3743
3744- (UIView*)toolbarSnapshotView {
Gauthier Ambard964f1b52018-01-10 14:00:093745 return [self.primaryToolbarCoordinator.viewController.view
sczs42f7f7482017-11-08 01:13:273746 snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133747}
3748
3749- (CGFloat)overscrollActionsControllerHeaderInset:
3750 (OverscrollActionsController*)controller {
3751 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
Mark Cogan849244ee2017-12-29 15:57:193752 return self.headerHeight;
sdefresnee65fd872016-12-19 13:38:133753 else
3754 return 0;
3755}
3756
3757- (CGFloat)overscrollHeaderHeight {
Mark Cogan849244ee2017-12-29 15:57:193758 return self.headerHeight + StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:133759}
3760
sdefresnee65fd872016-12-19 13:38:133761#pragma mark - CRWNativeContentProvider methods
3762
Mark Cogan849244ee2017-12-29 15:57:193763// TODO(crbug.com/725241): This method is deprecated and should be removed by
3764// switching to DidFinishnavigation.
sdefresnee65fd872016-12-19 13:38:133765- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3766 withError:(NSError*)error
3767 isPost:(BOOL)isPost {
3768 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173769 [[ErrorPageContent alloc] initWithLoader:self
3770 browserState:self.browserState
3771 url:url
3772 error:error
3773 isPost:isPost
3774 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133775 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3776 return errorPageContent;
3777}
3778
3779- (BOOL)hasControllerForURL:(const GURL&)url {
Marti Wong64481ec2017-10-31 03:38:003780 base::StringPiece host = url.host_piece();
olivierrobin5c861c22017-04-07 15:56:453781 if (host == kChromeUIOfflineHost) {
3782 // Only allow offline URL that are fully specified.
3783 return reading_list::IsOfflineURLValid(
3784 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3785 }
Justin Cohen8679e852017-08-14 16:35:253786 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133787}
3788
olivierrobind43eecb2017-01-27 20:35:263789- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3790 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133791 DCHECK(url.SchemeIs(kChromeUIScheme));
3792
3793 id<CRWNativeContent> nativeController = nil;
Marti Wong64481ec2017-10-31 03:38:003794 base::StringPiece url_host = url.host_piece();
Marti Wongc5d360f2018-01-15 12:47:113795 if (url_host == kChromeUINewTabHost) {
Gauthier Ambardd8890452017-09-29 12:07:463796 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3797 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3798 if (@available(iOS 11.0, *)) {
3799 safeAreaInset = self.view.safeAreaInsets;
3800 }
3801 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3802
sdefresnee65fd872016-12-19 13:38:133803 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173804 [[NewTabPageController alloc] initWithUrl:url
3805 loader:self
[email protected]76545762018-01-24 18:30:533806 focuser:self.dispatcher
stkhapuginf58b10d02017-04-10 13:36:173807 browserState:_browserState
Gauthier Ambard4d485dd2018-01-11 12:43:443808 toolbarDelegate:self.toolbarInterface
justincohenbc913632017-04-18 14:41:453809 tabModel:_model
justincohen75011c32017-04-28 16:31:393810 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463811 dispatcher:self.dispatcher
3812 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133813 pageController.swipeRecognizerProvider = self.sideSwipeController;
sdefresnee65fd872016-12-19 13:38:133814 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453815 } else if (url_host == kChromeUIOfflineHost &&
3816 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133817 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173818 [[OfflinePageNativeContent alloc] initWithLoader:self
3819 browserState:_browserState
3820 webState:webState
3821 URL:url];
sdefresnee65fd872016-12-19 13:38:133822 [self setOverScrollActionControllerToStaticNativeContent:
3823 staticNativeController];
3824 nativeController = staticNativeController;
3825 } else if (url_host == kChromeUIExternalFileHost) {
3826 // Return an instance of the |ExternalFileController| only if the file is
3827 // still in the sandbox.
3828 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3829 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173830 nativeController =
3831 [[ExternalFileController alloc] initWithURL:url
3832 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133833 }
peterlaurens44615d02017-05-23 20:23:093834 } else if (url_host == kChromeUICrashHost) {
3835 // There is no native controller for kChromeUICrashHost, it is instead
3836 // handled as any other renderer crash by the SadTabTabHelper.
3837 // nativeController must be set to nil to prevent defaulting to a
3838 // PageNotAvailableController.
3839 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133840 } else {
3841 DCHECK(![self hasControllerForURL:url]);
3842 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173843 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133844 }
3845 // If a native controller is vended before its tab is added to the tab model,
3846 // use the temporary key and add it under the new tab's tabId in the
3847 // TabModelObserver callback. This happens:
3848 // - when there is no current tab (occurs when vending the NTP controller for
3849 // the first tab that is opened),
3850 // - when the current tab's url doesn't match |url| (occurs when a native
3851 // controller is opened in a new tab)
3852 // - when the current tab's url matches |url| and there is already a native
3853 // controller of the appropriate type vended to it (occurs when a native
3854 // controller is opened in a new tab from a tab with a matching URL, e.g.
3855 // opening an NTP when an NTP is already displayed in the current tab).
3856 // For normal page loads, history navigations, tab restorations, and crash
3857 // recoveries, the tab will already exist in the tab model and the tabId can
3858 // be used as the native controller key.
3859 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3860 // that native controllers vended here always correspond to the current tab.
3861 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193862 if (!currentTab.webState ||
3863 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443864 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133865 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443866 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133867 }
sdefresnee65fd872016-12-19 13:38:133868 return nativeController;
3869}
3870
edchin2c6af4a2017-12-05 02:16:493871- (id<CRWNativeContent>)controllerForUnhandledContentAtURL:(const GURL&)URL
3872 webState:
3873 (web::WebState*)webState {
Eugene But87a09532017-12-08 20:02:053874 LegacyDownloadManagerController* downloadController =
3875 [[LegacyDownloadManagerController alloc] initWithWebState:webState
edchin2c6af4a2017-12-05 02:16:493876 downloadURL:URL
3877 baseViewController:self];
3878 [downloadController start];
3879 return downloadController;
3880}
3881
sdefresnee65fd872016-12-19 13:38:133882#pragma mark - DialogPresenterDelegate methods
3883
3884- (void)dialogPresenter:(DialogPresenter*)presenter
3885 willShowDialogForWebState:(web::WebState*)webState {
Sylvain Defresne5b1174cb2018-01-16 15:47:583886 WebStateList* webStateList = self.tabModel.webStateList;
3887 int indexOfWebState = webStateList->GetIndexOfWebState(webState);
3888 if (indexOfWebState != WebStateList::kInvalidIndex) {
3889 webStateList->ActivateWebStateAt(indexOfWebState);
3890 DCHECK([webState->GetView() isDescendantOfView:self.contentArea]);
sdefresnee65fd872016-12-19 13:38:133891 }
3892}
3893
Kurt Horimoto06b94252017-12-08 19:45:593894#pragma mark - FullscreenUIElement methods
3895
3896- (void)updateForFullscreenProgress:(CGFloat)progress {
3897 [self updateHeadersForFullscreenProgress:progress];
3898 [self updateFootersForFullscreenProgress:progress];
3899 [self updateContentViewTopPaddingForFullscreenProgress:progress];
3900}
3901
3902- (void)updateForFullscreenEnabled:(BOOL)enabled {
3903 if (!enabled)
3904 [self updateForFullscreenProgress:1.0];
3905}
3906
3907- (void)finishFullscreenScrollWithAnimator:
3908 (FullscreenScrollEndAnimator*)animator {
3909 BOOL showingToolbar = animator.finalProgress > animator.startProgress;
3910 CGFloat finalProgress = animator.finalProgress;
3911 // WKWebView does not re-render its content until its model layer's bounds
3912 // have been updated at the end of the animation. If the animator is going
3913 // to hide the toolbar, update the content view's top padding early so that
3914 // content is correctly rendered behind the toolbar that's being animated
3915 // away.
3916 if (!showingToolbar)
3917 [self updateContentViewTopPaddingForFullscreenProgress:finalProgress];
3918 [animator addAnimations:^{
3919 [self updateHeadersForFullscreenProgress:finalProgress];
3920 [self updateFootersForFullscreenProgress:finalProgress];
3921 }];
3922 // If the toolbar is being animated to become visible, update the content view
3923 // top padding in the completion block so that fixed-position elements can be
3924 // properly laid out in the new viewport.
3925 if (showingToolbar) {
3926 __weak FullscreenScrollEndAnimator* weakAnimator = animator;
3927 [animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
3928 [self updateContentViewTopPaddingForFullscreenProgress:
3929 [weakAnimator progressForAnimatingPosition:finalPosition]];
3930 }];
3931 }
3932}
3933
3934#pragma mark - FullscreenUIElement helpers
3935
3936// Translates the header views up and down according to |progress|, where a
3937// progress of 1.0 fully shows the headers and a progress of 0.0 fully hides
3938// them.
3939- (void)updateHeadersForFullscreenProgress:(CGFloat)progress {
3940 [self setFramesForHeaders:[self headerViews]
3941 atOffset:(1.0 - progress) * [self toolbarHeight]];
3942}
3943
3944// Translates the footer view up and down according to |progress|, where a
3945// progress of 1.0 fully shows the footer and a progress of 0.0 fully hides it.
3946- (void)updateFootersForFullscreenProgress:(CGFloat)progress {
3947 if (![_model currentTab].isVoiceSearchResultsTab)
3948 return;
3949
3950 UIView* footerView = [self footerView];
3951 DCHECK(footerView);
3952 CGRect frame = footerView.frame;
3953 frame.origin.y = CGRectGetMaxY(footerView.superview.bounds) -
3954 progress * CGRectGetHeight(frame);
3955 footerView.frame = frame;
3956}
3957
3958// Updates the top padding of the web view proxy. This either resets the frame
3959// of the WKWebView or the contentInsets of the WKWebView's UIScrollView,
3960// depending on the the proxy's |shouldUseInsetForTopPadding| property.
3961- (void)updateContentViewTopPaddingForFullscreenProgress:(CGFloat)progress {
3962 if (self.currentWebState) {
3963 self.currentWebState->GetWebViewProxy().topContentPadding =
3964 progress * [self toolbarHeight];
3965 }
3966}
3967
sdefresnee65fd872016-12-19 13:38:133968#pragma mark - KeyCommandsPlumbing
3969
3970- (BOOL)isOffTheRecord {
3971 return _isOffTheRecord;
3972}
3973
3974- (NSUInteger)tabsCount {
3975 return [_model count];
3976}
3977
lpromero47ea8862017-01-13 17:51:063978- (BOOL)canGoBack {
3979 return [_model currentTab].canGoBack;
3980}
3981
3982- (BOOL)canGoForward {
3983 return [_model currentTab].canGoForward;
3984}
3985
sdefresnee65fd872016-12-19 13:38:133986- (void)focusTabAtIndex:(NSUInteger)index {
3987 if ([_model count] > index) {
3988 [_model setCurrentTab:[_model tabAtIndex:index]];
3989 }
3990}
3991
3992- (void)focusNextTab {
3993 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3994 NSInteger modelCount = [_model count];
3995 if (currentTabIndex < modelCount - 1) {
3996 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3997 [_model setCurrentTab:nextTab];
3998 } else {
3999 [_model setCurrentTab:[_model tabAtIndex:0]];
4000 }
4001}
4002
4003- (void)focusPreviousTab {
4004 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
4005 if (currentTabIndex > 0) {
4006 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
4007 [_model setCurrentTab:previousTab];
4008 } else {
4009 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
4010 [_model setCurrentTab:lastTab];
4011 }
4012}
4013
4014- (void)reopenClosedTab {
4015 sessions::TabRestoreService* const tabRestoreService =
4016 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
4017 if (!tabRestoreService || tabRestoreService->entries().empty())
4018 return;
4019
4020 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
4021 tabRestoreService->entries().front();
4022 // Only handle the TAB type.
4023 if (entry->type != sessions::TabRestoreService::TAB)
4024 return;
4025
Mark Cogandfcdea72017-07-18 13:47:384026 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:134027 TabRestoreServiceDelegateImplIOS* const delegate =
4028 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
4029 _browserState);
4030 tabRestoreService->RestoreEntryById(delegate, entry->id,
4031 WindowOpenDisposition::CURRENT_TAB);
4032}
4033
Kurt Horimotoe9b6002c2017-12-04 23:19:194034#pragma mark - MainContentUI
4035
4036- (MainContentUIState*)mainContentUIState {
4037 return _mainContentUIUpdater.state;
4038}
4039
Mark Cogan776e0282018-01-02 09:00:064040#pragma mark - UrlLoader (Public)
Mark Cogan5bd86ba2017-12-28 14:32:384041
sdefresnee65fd872016-12-19 13:38:134042- (void)loadURL:(const GURL&)url
4043 referrer:(const web::Referrer&)referrer
4044 transition:(ui::PageTransition)transition
4045 rendererInitiated:(BOOL)rendererInitiated {
4046 [[OmniboxGeolocationController sharedInstance]
4047 locationBarDidSubmitURL:url
4048 transition:transition
4049 browserState:_browserState];
4050
4051 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
4052 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
4053 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
4054 }
4055
4056 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:244057 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:134058 // restart.
4059 if (url.host() == kChromeUIBrowserCrashHost) {
4060 [self induceBrowserCrash];
4061 // In debug the app can continue working even after the CHECK. Adding a
4062 // return avoids the crash url to be added to the history.
4063 return;
4064 }
4065
Danyao Wang85389a82017-10-25 18:56:274066 bool typed_or_generated_transition =
4067 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
4068 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
4069
Rohit Rao44f204302017-08-10 14:49:544070 PrerenderService* prerenderService =
4071 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4072 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:594073 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:544074 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:594075 DCHECK(newWebState);
4076
sdefresnee65fd872016-12-19 13:38:134077 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:594078 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:134079 DCHECK(oldTab);
4080 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:594081
kkhorimotod804c5732017-03-15 23:44:524082 bool canPruneItems =
4083 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:594084
Sylvain Defresne17b8aa42017-12-21 16:17:174085 if (canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:524086 [newTab navigationManager]->CopyStateFromAndPrune(
4087 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:594088
Sylvain Defresnef5d2d952017-11-14 11:15:314089 // Set _insertedTabWasPrerenderedTab to YES while the Tab is inserted
4090 // so that the correct toolbar height is used and animation are played.
4091 _insertedTabWasPrerenderedTab = YES;
sdefresne2c600c52017-04-04 16:49:594092 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
4093 std::move(newWebState));
Sylvain Defresnef5d2d952017-11-14 11:15:314094 _insertedTabWasPrerenderedTab = NO;
sdefresnee65fd872016-12-19 13:38:134095
Sylvain Defresne17b8aa42017-12-21 16:17:174096 if ([newTab loadFinished]) {
4097 // If the page has finished loading, take a snapshot. If the page is
4098 // still loading, do nothing, as the tab helper will automatically take
4099 // a snapshot once the load completes.
4100 SnapshotTabHelper::FromWebState(newTab.webState)
4101 ->UpdateSnapshot(
4102 /*with_overlays=*/true, /*visible_frame_only=*/true);
4103 }
4104
Danyao Wang85389a82017-10-25 18:56:274105 if (typed_or_generated_transition) {
4106 LoadTimingTabHelper::FromWebState(newTab.webState)
4107 ->DidPromotePrerenderTab();
4108 }
sdefresnee65fd872016-12-19 13:38:134109
sdefresne2f7781c2017-03-02 19:12:464110 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:134111 return;
4112 }
4113 }
4114
4115 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:544116 if (prerenderService) {
4117 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:134118 }
4119
sdefresnee65fd872016-12-19 13:38:134120 // Some URLs are not allowed while in incognito. If we are in incognito and
4121 // load a disallowed URL, instead create a new tab not in the incognito state.
4122 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
4123 [self webPageOrderedOpen:url
4124 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134125 inIncognito:NO
4126 inBackground:NO
4127 appendTo:kCurrentTab];
4128 return;
4129 }
4130
Danyao Wang85389a82017-10-25 18:56:274131 if (typed_or_generated_transition) {
4132 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
4133 ->DidInitiatePageLoad();
4134 }
4135
mrefaata84d5a02017-06-08 17:13:294136 // If this is a reload initiated from the omnibox.
4137 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
4138 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
4139 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
4140 [[_model currentTab] navigationManager]->Reload(
4141 web::ReloadType::NORMAL, true /* check_for_repost */);
4142 return;
4143 }
4144
sdefresnee65fd872016-12-19 13:38:134145 web::NavigationManager::WebLoadParams params(urlToLoad);
4146 params.referrer = referrer;
4147 params.transition_type = transition;
4148 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104149 Tab* currentTab = [_model currentTab];
4150 DCHECK(currentTab);
4151 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4152 currentTab.navigationManager->LoadURLWithParams(params);
4153 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4154 // to be animated on screen because the transition animator will handle the
4155 // animations. When a Tab stops being a voice search Tab, the voice search
4156 // bar should be animated away.
4157 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4158 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134159}
4160
4161- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544162 PrerenderService* prerenderService =
4163 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4164 if (prerenderService) {
4165 prerenderService->CancelPrerender();
4166 }
sdefresnee65fd872016-12-19 13:38:134167 DCHECK([_model currentTab]);
Mark Cogan776e0282018-01-02 09:00:064168 if (self.currentWebState)
4169 self.currentWebState->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134170}
4171
sdefresnee65fd872016-12-19 13:38:134172// Load a new URL on a new page/tab.
4173- (void)webPageOrderedOpen:(const GURL&)URL
4174 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134175 inBackground:(BOOL)inBackground
4176 appendTo:(OpenPosition)appendTo {
4177 Tab* adjacentTab = nil;
4178 if (appendTo == kCurrentTab)
4179 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354180 [_model insertTabWithURL:URL
4181 referrer:referrer
4182 transition:ui::PAGE_TRANSITION_LINK
4183 opener:adjacentTab
4184 openedByDOM:NO
4185 atIndex:TabModelConstants::kTabPositionAutomatically
4186 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134187}
4188
4189- (void)webPageOrderedOpen:(const GURL&)url
4190 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134191 inIncognito:(BOOL)inIncognito
4192 inBackground:(BOOL)inBackground
4193 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004194 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234195 // feature_engagement::Tracker based on |inIncognito|.
4196 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004197
sdefresnee65fd872016-12-19 13:38:134198 if (inIncognito == _isOffTheRecord) {
4199 [self webPageOrderedOpen:url
4200 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134201 inBackground:inBackground
4202 appendTo:appendTo];
4203 return;
4204 }
4205 // When sending an open command that switches modes, ensure the tab
4206 // ends up appended to the end of the model, not just next to what is
4207 // currently selected in the other mode. This is done with the |append|
4208 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274209 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134210 initWithURL:url
4211 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134212 inIncognito:inIncognito
4213 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274214 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154215 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134216}
4217
4218- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054219 WebStateList* webStateList = [_model webStateList];
4220 webStateList->ReplaceWebStateAt(
4221 webStateList->active_index(),
4222 session_util::CreateWebStateWithNavigationEntries(
4223 [_model browserState], sessionTab->current_navigation_index,
4224 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134225}
4226
Mark Cogan776e0282018-01-02 09:00:064227#pragma mark - UrlLoader helpers
4228
4229// Induce an intentional crash in the browser process.
4230- (void)induceBrowserCrash {
4231 CHECK(false);
4232 // Call another function, so that the above CHECK can't be tail-call
4233 // optimized. This ensures that this method's name will show up in the stack
4234 // for easier identification.
4235 CHECK(true);
sdefresnee65fd872016-12-19 13:38:134236}
4237
Mark Cogan776e0282018-01-02 09:00:064238#pragma mark - WebToolbarDelegate (Public)
sdefresnee65fd872016-12-19 13:38:134239
Gauthier Ambard45963ce22017-11-17 15:49:114240- (void)locationBarDidBecomeFirstResponder {
sdefresnee65fd872016-12-19 13:38:134241 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524242 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134243 object:nil];
Mark Cogan5bd86ba2017-12-28 14:32:384244 [self.sideSwipeController setEnabled:NO];
sdefresnee65fd872016-12-19 13:38:134245 if ([[_model currentTab].webController wantsKeyboardShield]) {
Mark Cogan5bd86ba2017-12-28 14:32:384246 [[self view] insertSubview:self.typingShield aboveSubview:_contentArea];
4247 [self.typingShield setAlpha:0.0];
4248 [self.typingShield setHidden:NO];
sdefresnee65fd872016-12-19 13:38:134249 [UIView animateWithDuration:0.3
4250 animations:^{
Mark Cogan5bd86ba2017-12-28 14:32:384251 [self.typingShield setAlpha:1.0];
sdefresnee65fd872016-12-19 13:38:134252 }];
4253 }
4254 [[OmniboxGeolocationController sharedInstance]
4255 locationBarDidBecomeFirstResponder:_browserState];
[email protected]502ae842018-01-25 13:59:404256
4257 [self.primaryToolbarCoordinator transitionToLocationBarFocusedState:YES];
sdefresnee65fd872016-12-19 13:38:134258}
4259
Gauthier Ambard45963ce22017-11-17 15:49:114260- (void)locationBarDidResignFirstResponder {
Mark Cogan5bd86ba2017-12-28 14:32:384261 [self.sideSwipeController setEnabled:YES];
sdefresnee65fd872016-12-19 13:38:134262 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524263 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134264 object:nil];
4265 [UIView animateWithDuration:0.3
4266 animations:^{
Mark Cogan5bd86ba2017-12-28 14:32:384267 [self.typingShield setAlpha:0.0];
sdefresnee65fd872016-12-19 13:38:134268 }
4269 completion:^(BOOL finished) {
4270 // This can happen if one quickly resigns the omnibox and then taps
4271 // on the omnibox again during this animation. If the animation is
4272 // interrupted and the toolbar controller is first responder, it's safe
Mark Cogan5bd86ba2017-12-28 14:32:384273 // to assume |self.typingShield| shouldn't be hidden here.
Gauthier Ambard83207452018-01-04 07:51:394274 if (!finished &&
4275 [self.primaryToolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134276 return;
Mark Cogan5bd86ba2017-12-28 14:32:384277 [self.typingShield setHidden:YES];
sdefresnee65fd872016-12-19 13:38:134278 }];
4279 [[OmniboxGeolocationController sharedInstance]
4280 locationBarDidResignFirstResponder:_browserState];
4281
4282 // If a load was cancelled by an omnibox edit, but nothing is loading when
4283 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4284 if (_locationBarEditCancelledLoad) {
4285 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294286
4287 web::WebState* webState = [_model currentTab].webState;
4288 if (!_toolbarModelIOS->IsLoading() && webState)
4289 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4290 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134291 }
[email protected]502ae842018-01-25 13:59:404292 [self.primaryToolbarCoordinator transitionToLocationBarFocusedState:NO];
sdefresnee65fd872016-12-19 13:38:134293}
4294
Gauthier Ambard45963ce22017-11-17 15:49:114295- (void)locationBarBeganEdit {
sdefresnee65fd872016-12-19 13:38:134296 // On handsets, if a page is currently loading it should be stopped.
4297 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354298 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134299 _locationBarEditCancelledLoad = YES;
4300 }
4301}
4302
sdefresnee65fd872016-12-19 13:38:134303- (ToolbarModelIOS*)toolbarModelIOS {
4304 return _toolbarModelIOS.get();
4305}
4306
sdefresnee65fd872016-12-19 13:38:134307
Mark Cogan849244ee2017-12-29 15:57:194308#pragma mark - ToolsMenuConfigurationProvider
Peter Laurense0b80f12017-11-21 07:52:404309
4310- (void)prepareForToolsMenuPresentationByCoordinator:
4311 (ToolsMenuCoordinator*)coordinator {
4312 DCHECK(_browserState);
4313 DCHECK(self.visible || self.dismissingModal);
4314
4315 // Dismiss the omnibox (if open).
[email protected]76545762018-01-24 18:30:534316 [self.dispatcher cancelOmniboxEdit];
Peter Laurense0b80f12017-11-21 07:52:404317 // Dismiss the soft keyboard (if open).
4318 [[_model currentTab].webController dismissKeyboard];
4319 // Dismiss Find in Page focus.
4320 [self updateFindBar:NO shouldFocus:NO];
4321
4322 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4323 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4324 }
4325}
4326
4327- (ToolsMenuConfiguration*)menuConfigurationForToolsMenuCoordinator:
4328 (ToolsMenuCoordinator*)coordinator {
4329 ToolsMenuConfiguration* configuration =
4330 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4331 baseViewController:self];
4332 configuration.requestStartTime = [NSDate date].timeIntervalSinceReferenceDate;
4333
4334 if ([_model count] == 0)
4335 [configuration setNoOpenedTabs:YES];
4336
4337 if (_isOffTheRecord)
4338 [configuration setInIncognito:YES];
4339
4340 if (!_readingListMenuNotifier) {
4341 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4342 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4343 _browserState)];
4344 }
4345
4346 feature_engagement::Tracker* engagementTracker =
4347 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4348 if (engagementTracker->ShouldTriggerHelpUI(
4349 feature_engagement::kIPHBadgedReadingListFeature)) {
4350 [configuration setShowReadingListNewBadge:YES];
4351 [configuration setEngagementTracker:engagementTracker];
4352 }
4353 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4354
4355 [configuration setUserAgentType:self.userAgentType];
4356
4357 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4358 [configuration setHighlightNewIncognitoTabCell:YES];
4359 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4360 }
4361
4362 return configuration;
4363}
4364
4365- (BOOL)shouldHighlightBookmarkButtonForToolsMenuCoordinator:
4366 (ToolsMenuCoordinator*)coordinator {
4367 return [_model currentTab] ? _toolbarModelIOS->IsCurrentTabBookmarked() : NO;
4368}
4369
4370- (BOOL)shouldShowFindBarForToolsMenuCoordinator:
4371 (ToolsMenuCoordinator*)coordinator {
4372 return [_model currentTab] ? self.canShowFindBar : NO;
4373}
4374
4375- (BOOL)shouldShowShareMenuForToolsMenuCoordinator:
4376 (ToolsMenuCoordinator*)coordinator {
4377 return [_model currentTab] ? self.canShowShareMenu : NO;
4378}
4379
4380- (BOOL)isTabLoadingForToolsMenuCoordinator:(ToolsMenuCoordinator*)coordinator {
4381 return ([_model currentTab] && !IsIPadIdiom()) ? _toolbarModelIOS->IsLoading()
4382 : NO;
4383}
4384
Mark Cogan6ebbde02017-07-07 12:50:134385#pragma mark - BrowserCommands
4386
4387- (void)goBack {
4388 [[_model currentTab] goBack];
4389}
4390
4391- (void)goForward {
4392 [[_model currentTab] goForward];
4393}
4394
Mark Coganb9aac6432017-07-07 13:26:354395- (void)stopLoading {
4396 [_model currentTab].webState->Stop();
4397}
4398
4399- (void)reload {
4400 web::WebState* webState = [_model currentTab].webState;
4401 if (webState) {
4402 // |check_for_repost| is true because the reload is explicitly initiated
4403 // by the user.
4404 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4405 true /* check_for_repost */);
4406 }
4407}
4408
Mark Cogan8e791022017-07-10 09:55:354409- (void)bookmarkPage {
4410 [self initializeBookmarkInteractionController];
4411 [_bookmarkInteractionController
4412 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064413 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354414}
4415
Mark Cogandfcdea72017-07-18 13:47:384416- (void)openNewTab:(OpenNewTabCommand*)command {
4417 if (self.isOffTheRecord != command.incognito) {
edchin3ab78ff2017-11-13 19:13:144418 // Must take a snapshot of the tab before we switch the incognito mode
4419 // because the currentTab will change after the switch.
4420 Tab* currentTab = [_model currentTab];
4421 if (currentTab) {
Sylvain Defresne17b8aa42017-12-21 16:17:174422 SnapshotTabHelper::FromWebState(currentTab.webState)
4423 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
edchin3ab78ff2017-11-13 19:13:144424 }
Mark Cogandfcdea72017-07-18 13:47:384425 // Not for this browser state, send it on its way.
4426 [self.dispatcher switchModesAndOpenNewTab:command];
4427 return;
4428 }
4429
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004430 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234431 // events to the feature_engagement::Tracker based on |command.userInitiated|
4432 // and |command.incognito|.
4433 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004434
Mark Cogandfcdea72017-07-18 13:47:384435 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4436 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494437 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4438 self.foregroundTabWasAddedCompletionBlock;
Louis Romero960a12f2017-11-30 19:08:594439 __weak BrowserViewController* weakSelf = self;
Mark Cogandfcdea72017-07-18 13:47:384440 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494441 if (oldForegroundTabWasAddedCompletionBlock) {
4442 oldForegroundTabWasAddedCompletionBlock();
4443 }
Mark Cogandfcdea72017-07-18 13:47:384444 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4445 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4446 if (offTheRecord) {
4447 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4448 timeDelta);
4449 } else {
4450 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4451 }
Louis Romero960a12f2017-11-30 19:08:594452 if (command.shouldFocusOmnibox) {
Mark Cogan5bd86ba2017-12-28 14:32:384453 [weakSelf.dispatcher focusOmnibox];
Louis Romero960a12f2017-11-30 19:08:594454 }
Mark Cogandfcdea72017-07-18 13:47:384455 };
4456
4457 [self setLastTapPoint:command];
Rohit Rao2e22b8d2017-11-07 19:54:544458 // When the tab switcher presentation experiment is enabled, the new tab can
4459 // be opened before BVC has been made visible onscreen. Test for this case by
4460 // checking if the parent container VC is currently in the process of being
4461 // presented.
4462 DCHECK(self.visible || self.dismissingModal ||
4463 (TabSwitcherPresentsBVCEnabled() &&
4464 self.parentViewController.isBeingPresented));
edchin3ab78ff2017-11-13 19:13:144465
4466 // In most cases, we want to take a snapshot of the current tab before opening
4467 // a new tab. However, if the current tab is not fully visible (did not finish
4468 // |-viewDidAppear:|, then we must not take an empty snapshot, replacing an
4469 // existing snapshot for the tab. This can happen when a new regular tab is
4470 // opened from an incognito tab. A different BVC is displayed, which may not
4471 // have enough time to finish appearing before a snapshot is requested.
Mark Cogandfcdea72017-07-18 13:47:384472 Tab* currentTab = [_model currentTab];
edchin3ab78ff2017-11-13 19:13:144473 if (currentTab && self.viewVisible) {
Sylvain Defresne17b8aa42017-12-21 16:17:174474 SnapshotTabHelper::FromWebState(currentTab.webState)
4475 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
Mark Cogandfcdea72017-07-18 13:47:384476 }
4477 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4478 transition:ui::PAGE_TRANSITION_TYPED];
4479}
4480
Mark Cogan123895002017-07-20 12:54:064481- (void)printTab {
4482 Tab* currentTab = [_model currentTab];
4483 // The UI should prevent users from printing non-printable pages. However, a
4484 // redirection to an un-printable page can happen before it is reflected in
4485 // the UI.
4486 if (![currentTab viewForPrinting]) {
4487 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364488 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064489 return;
4490 }
4491 DCHECK(_browserState);
4492 if (!_printController) {
4493 _printController = [[PrintController alloc]
4494 initWithContextGetter:_browserState->GetRequestContext()];
4495 }
4496 [_printController printView:[currentTab viewForPrinting]
4497 withTitle:[currentTab title]
4498 viewController:self];
4499}
4500
Mark Coganfa25b052017-07-20 17:31:034501- (void)addToReadingList:(ReadingListAddCommand*)command {
4502 [self addToReadingListURL:[command URL] title:[command title]];
4503}
4504
sczs3a8c8602017-08-01 20:14:084505- (void)showReadingList {
4506 _readingListCoordinator = [[ReadingListCoordinator alloc]
4507 initWithBaseViewController:self
4508 browserState:self.browserState
4509 loader:self];
4510
4511 [_readingListCoordinator start];
4512}
4513
Jean-François Geyelinedef9552017-08-07 09:56:564514- (void)preloadVoiceSearch {
4515 // Preload VoiceSearchController and views and view controllers needed
4516 // for voice search.
4517 [self ensureVoiceSearchControllerCreated];
4518 _voiceSearchController->PrepareToAppear();
4519}
4520
edchinc5720722017-08-14 22:06:314521#if !defined(NDEBUG)
4522- (void)viewSource {
4523 Tab* tab = [_model currentTab];
4524 DCHECK(tab);
4525 CRWWebController* webController = tab.webController;
4526 NSString* script = @"document.documentElement.outerHTML;";
4527 __weak Tab* weakTab = tab;
4528 __weak BrowserViewController* weakSelf = self;
4529 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4530 Tab* strongTab = weakTab;
4531 if (!strongTab)
4532 return;
4533 if (![result isKindOfClass:[NSString class]])
4534 result = @"Not an HTML page";
4535 std::string base64HTML;
4536 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4537 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194538 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314539 web::ReferrerPolicyDefault);
4540
4541 [[weakSelf tabModel]
4542 insertTabWithURL:URL
4543 referrer:referrer
4544 transition:ui::PAGE_TRANSITION_LINK
4545 opener:strongTab
4546 openedByDOM:YES
4547 atIndex:TabModelConstants::kTabPositionAutomatically
4548 inBackground:NO];
4549 };
4550 [webController executeJavaScript:script
4551 completionHandler:completionHandlerBlock];
4552}
4553#endif // !defined(NDEBUG)
4554
edchin2134c042017-08-18 13:57:354555// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4556- (void)showRateThisAppDialog {
4557 DCHECK(!_rateThisAppDialog);
4558
4559 // Store the current timestamp whenever this dialog is shown.
4560 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4561 base::Time::Now().ToInternalValue());
4562
Gregory Chatzinofff39ec5162017-10-05 20:28:534563 // iOS11 no longer supports the itms link to the app store. So, use a deep
4564 // link for iOS11 and the itms link for prior versions.
4565 NSURL* storeURL;
4566 if (base::ios::IsRunningOnIOS11OrLater()) {
4567 storeURL =
4568 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4569 @"google-chrome-the-fast-and-secure-web-browser/"
4570 @"id535886823?action=write-review")];
4571 } else {
4572 storeURL = [NSURL
4573 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4574 @"MZStore.woa/wa/"
4575 @"viewContentsUserReviews?type=Purple+Software&id="
4576 @"535886823&pt=9008&ct=rating")];
4577 }
edchin2134c042017-08-18 13:57:354578
4579 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014580 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354581
4582 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4583 [_rateThisAppDialog setAppStoreURL:storeURL];
4584 [_rateThisAppDialog setDelegate:self];
4585 [_rateThisAppDialog show];
4586}
4587
Gregory Chatzinoff3f40c1542017-08-30 07:50:044588- (void)showFindInPage {
4589 if (!self.canShowFindBar)
4590 return;
4591
4592 if (!_findBarController) {
4593 _findBarController =
4594 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4595 _findBarController.dispatcher = self.dispatcher;
4596 }
4597
4598 Tab* tab = [_model currentTab];
4599 DCHECK(tab);
4600 auto* helper = FindTabHelper::FromWebState(tab.webState);
4601 DCHECK(!helper->IsFindUIActive());
4602 helper->SetFindUIActive(true);
4603 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4604}
4605
4606- (void)closeFindInPage {
4607 __weak BrowserViewController* weakSelf = self;
4608 Tab* currentTab = [_model currentTab];
4609 if (currentTab) {
4610 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4611 [weakSelf updateFindBar:NO shouldFocus:NO];
4612 });
4613 }
4614}
4615
4616- (void)searchFindInPage {
4617 DCHECK([_model currentTab]);
4618 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4619 __weak BrowserViewController* weakSelf = self;
4620 helper->StartFinding(
4621 [_findBarController searchTerm], ^(FindInPageModel* model) {
4622 BrowserViewController* strongSelf = weakSelf;
4623 if (!strongSelf) {
4624 return;
4625 }
4626 [strongSelf->_findBarController updateResultsCount:model];
4627 });
4628
4629 if (!_isOffTheRecord)
4630 helper->PersistSearchTerm();
4631}
4632
4633- (void)findNextStringInPage {
4634 Tab* currentTab = [_model currentTab];
4635 DCHECK(currentTab);
4636 // TODO(crbug.com/603524): Reshow find bar if necessary.
4637 FindTabHelper::FromWebState(currentTab.webState)
4638 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4639 [_findBarController updateResultsCount:model];
4640 });
4641}
4642
4643- (void)findPreviousStringInPage {
4644 Tab* currentTab = [_model currentTab];
4645 DCHECK(currentTab);
4646 // TODO(crbug.com/603524): Reshow find bar if necessary.
4647 FindTabHelper::FromWebState(currentTab.webState)
4648 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4649 [_findBarController updateResultsCount:model];
4650 });
4651}
4652
edchinf84b2502017-08-31 21:30:454653- (void)showHelpPage {
4654 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4655 [self webPageOrderedOpen:helpUrl
4656 referrer:web::Referrer()
4657 inBackground:NO
4658 appendTo:kCurrentTab];
4659}
4660
edchinb59b5602017-09-01 15:00:204661- (void)showBookmarksManager {
Marti Wongc5d360f2018-01-15 12:47:114662 [self initializeBookmarkInteractionController];
4663 [_bookmarkInteractionController presentBookmarks];
edchinb59b5602017-09-01 15:00:204664}
4665
edchin8ee0807d2017-09-01 23:52:474666- (void)showRecentTabs {
Marti Wongc5d360f2018-01-15 12:47:114667 if (!self.recentTabsCoordinator) {
4668 self.recentTabsCoordinator =
4669 [[RecentTabsHandsetCoordinator alloc] initWithBaseViewController:self];
4670 self.recentTabsCoordinator.loader = self;
4671 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4672 self.recentTabsCoordinator.browserState = _browserState;
edchin8ee0807d2017-09-01 23:52:474673 }
Marti Wongc5d360f2018-01-15 12:47:114674 [self.recentTabsCoordinator start];
edchin8ee0807d2017-09-01 23:52:474675}
4676
Mark Cogan6de7e9a2017-09-06 12:57:214677- (void)requestDesktopSite {
4678 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4679}
4680
4681- (void)requestMobileSite {
4682 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4683}
4684
sdefresnee65fd872016-12-19 13:38:134685- (void)closeCurrentTab {
4686 Tab* currentTab = [_model currentTab];
4687 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4688 if (tabIndex == NSNotFound)
4689 return;
4690
jif7fed8122017-02-08 13:15:254691 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4692 // iPad.
sdefresnee65fd872016-12-19 13:38:134693 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4694 exitingPage.image =
Sylvain Defresne17b8aa42017-12-21 16:17:174695 SnapshotTabHelper::FromWebState(currentTab.webState)
4696 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
sdefresnee65fd872016-12-19 13:38:134697
4698 // Close the actual tab, and add its image as a subview.
4699 [_model closeTabAtIndex:tabIndex];
4700
4701 // Do not animate close in iPad.
4702 if (!IsIPadIdiom()) {
4703 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524704 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134705 exitingPage, 0, YES, IsPortrait(), ^{
4706 [exitingPage removeFromSuperview];
4707 });
4708 }
4709}
4710
Gauthier Ambard1f052b62018-01-17 17:12:384711- (void)navigateToMemexTabSwitcher {
4712 // TODO(crbug.com/799601): Delete this once its not needed.
4713 const GURL memexURL("https://chrome-memex.appspot.com");
4714 [self loadURL:memexURL
4715 referrer:web::Referrer()
4716 transition:ui::PAGE_TRANSITION_LINK
4717 rendererInitiated:NO];
4718}
4719
Mark Cogan776e0282018-01-02 09:00:064720#pragma mark - ToolbarOwner (Public)
sdefresnee65fd872016-12-19 13:38:134721
Kurt Horimotoea429dd2017-11-28 02:24:304722- (CGFloat)toolbarHeight {
Mark Cogan849244ee2017-12-29 15:57:194723 return self.headerHeight;
Kurt Horimotoea429dd2017-11-28 02:24:304724}
4725
Gauthier Ambard04ddb512017-11-07 09:14:164726- (CGRect)toolbarFrame {
Gauthier Ambard964f1b52018-01-10 14:00:094727 return _toolbarCoordinator.viewController.view.frame;
Gauthier Ambard04ddb512017-11-07 09:14:164728}
4729
Gauthier Ambard996d9b12017-11-06 09:39:214730- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
4731 id<ToolbarSnapshotProviding> toolbarSnapshotProvider = nil;
Gauthier Ambard964f1b52018-01-10 14:00:094732 if (_toolbarCoordinator.viewController.view.hidden) {
Gauthier Ambard996d9b12017-11-06 09:39:214733 Tab* currentTab = [_model currentTab];
4734 if (currentTab.webState &&
4735 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
4736 // Use the native content controller's toolbar when the BVC's is hidden.
4737 id nativeController = [self nativeControllerForTab:currentTab];
4738 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4739 toolbarSnapshotProvider = [nativeController toolbarSnapshotProvider];
4740 }
4741 }
4742 } else {
4743 toolbarSnapshotProvider = _toolbarCoordinator;
4744 }
4745 return toolbarSnapshotProvider;
4746}
4747
sdefresnee65fd872016-12-19 13:38:134748#pragma mark - TabModelObserver methods
4749
4750// Observer method, tab inserted.
4751- (void)tabModel:(TabModel*)model
4752 didInsertTab:(Tab*)tab
4753 atIndex:(NSUInteger)modelIndex
4754 inForeground:(BOOL)fg {
4755 DCHECK(tab);
4756 [self installDelegatesForTab:tab];
4757
4758 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194759 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134760 }
4761}
4762
4763// Observer method, active tab changed.
4764- (void)tabModel:(TabModel*)model
4765 didChangeActiveTab:(Tab*)newTab
4766 previousTab:(Tab*)previousTab
4767 atIndex:(NSUInteger)index {
4768 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4769 // Currently this observer method is always called with a non-nil |newTab|,
4770 // but that may change in the future. Remove this DCHECK when it does.
4771 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274772 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304773 DCHECK(newTab.webState);
4774 infobars::InfoBarManager* infoBarManager =
4775 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134776 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
Mark Coganb12391c62017-12-28 13:35:064777
4778 // Dismiss the language selector, if any; this is a no-op when there's
4779 // no language selector presented.
4780 [_languageSelectionCoordinator dismissLanguageSelector];
sdefresnee65fd872016-12-19 13:38:134781 }
4782 [self updateVoiceSearchBarVisibilityAnimated:NO];
4783
Mohamad Ahmadi7d09ec32017-07-11 22:32:194784 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134785
Gauthier Ambard64396902017-12-08 10:14:584786 [self tabSelected:newTab notifyToolbar:YES];
sdefresnee65fd872016-12-19 13:38:134787}
4788
Mark Cogan5b494642018-01-02 12:55:464789- (void)tabModel:(TabModel*)model willStartLoadingTab:(Tab*)tab {
4790 // Stop any Find in Page searches and close the find bar when navigating to a
4791 // new page.
4792 [self closeFindInPage];
4793}
4794
sdefresnee65fd872016-12-19 13:38:134795- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4796 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4797 if (tab == [_model currentTab]) {
4798 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134799 }
4800}
4801
Mark Cogan5b494642018-01-02 12:55:464802- (void)tabModel:(TabModel*)model didStartLoadingTab:(Tab*)tab {
4803 if (tab == [_model currentTab]) {
4804 if (![self isTabNativePage:tab]) {
Gauthier Ambard83207452018-01-04 07:51:394805 [self.legacyToolbarCoordinator currentPageLoadStarted];
Mark Cogan5b494642018-01-02 12:55:464806 }
4807 [self updateVoiceSearchBarVisibilityAnimated:NO];
4808 }
4809}
4810
4811- (void)tabModel:(TabModel*)model
4812 didFinishLoadingTab:(Tab*)tab
Mark Cogan50a4c06a2018-01-02 18:26:204813 success:(BOOL)success {
Mark Cogan5b494642018-01-02 12:55:464814 [self tabLoadComplete:tab withSuccess:success];
Mark Coganfc591c8c2018-01-02 16:07:004815 if (IsIPadIdiom()) {
4816 UIUserInterfaceSizeClass sizeClass =
4817 self.view.window.traitCollection.horizontalSizeClass;
4818 [SizeClassRecorder pageLoadedWithHorizontalSizeClass:sizeClass];
4819 }
Mark Cogan5b494642018-01-02 12:55:464820}
4821
4822- (void)tabModel:(TabModel*)model
4823 newTabWillOpen:(Tab*)tab
4824 inBackground:(BOOL)background {
4825 DCHECK(tab);
4826 _temporaryNativeController = nil;
4827
4828 // When adding new tabs, check what kind of reminder infobar should
4829 // be added to the new tab. Try to add only one of them.
4830 // This check is done when a new tab is added either through the Tools Menu
4831 // "New Tab" or through "New Tab" in Stack View Controller. This method
4832 // is called after a new tab has added and finished initial navigation.
4833 // If this is added earlier, the initial navigation may end up clearing
4834 // the infobar(s) that are just added. See http://crbug/340250 for details.
4835 web::WebState* webState = tab.webState;
4836 DCHECK(webState);
4837
4838 infobars::InfoBarManager* infoBarManager =
4839 InfoBarManagerImpl::FromWebState(webState);
4840 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
4841 forTabId:[tab tabId]];
4842 if (!ReSignInInfoBarDelegate::Create(_browserState, tab,
4843 self /* id<SigninPresenter> */)) {
4844 DisplaySyncErrors(_browserState, tab, self /* id<SyncPresenter> */);
4845 }
4846
4847 // The rest of this function initiates the new tab animation, which is
4848 // phone-specific. Call the foreground tab added completion block; for
4849 // iPhones, this will get executed after the animation has finished.
4850 if (IsIPadIdiom()) {
4851 if (self.foregroundTabWasAddedCompletionBlock) {
4852 // This callback is called before webState is activated (on
4853 // kTabModelNewTabWillOpenNotification notification). Dispatch the
4854 // callback asynchronously to be sure the activation is complete.
4855 dispatch_async(dispatch_get_main_queue(), ^() {
4856 // Test existence again as the block may have been deleted.
4857 if (self.foregroundTabWasAddedCompletionBlock) {
4858 self.foregroundTabWasAddedCompletionBlock();
4859 self.foregroundTabWasAddedCompletionBlock = nil;
4860 }
4861 });
4862 }
4863 return;
4864 }
4865
4866 // Do nothing if browsing is currently suspended. The BVC will set everything
4867 // up correctly when browsing resumes.
4868 if (!self.visible || ![_model webUsageEnabled])
4869 return;
4870
4871 // Block that starts voice search at the end of new Tab animation if
4872 // necessary.
4873 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
4874 if (_startVoiceSearchAfterNewTabAnimation) {
4875 _startVoiceSearchAfterNewTabAnimation = NO;
4876 [self startVoiceSearchWithOriginView:nil];
4877 }
4878 };
4879
4880 self.inNewTabAnimation = YES;
4881 if (!background) {
4882 UIView* animationParentView = _contentArea;
4883 // Create the new page image, and load with the new tab snapshot except if
4884 // it is the NTP.
4885 CGFloat newPageOffset = 0;
4886 UIView* newPage;
4887 CGFloat offset = 0;
4888 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
4889 !_isOffTheRecord && !IsIPadIdiom()) {
4890 offset = 0;
4891 animationParentView = self.view;
4892 newPage = tab.view;
4893 newPage.userInteractionEnabled = NO;
4894 // Compute a frame for the new page by removing the status bar height from
4895 // the bounds of |self.view|.
4896 CGRect viewBounds, remainder;
4897 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
4898 CGRectMinYEdge);
4899 newPage.frame = viewBounds;
4900 } else {
4901 UIImageView* pageScreenshot = [self pageOpenCloseAnimationView];
4902 tab.view.frame = _contentArea.bounds;
4903 pageScreenshot.image = SnapshotTabHelper::FromWebState(tab.webState)
4904 ->UpdateSnapshot(/*with_overlays=*/true,
4905 /*visible_frame_only=*/true);
4906 newPage = pageScreenshot;
4907 offset =
4908 pageScreenshot.frame.size.height - pageScreenshot.image.size.height;
4909 }
4910 newPageOffset = newPage.frame.origin.y;
4911
4912 [animationParentView addSubview:newPage];
4913 CGPoint origin = [self lastTapPoint];
4914 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
4915 newPage, -newPageOffset, offset, origin, _isOffTheRecord, NULL, ^{
4916 [tab view].frame = _contentArea.bounds;
4917 newPage.userInteractionEnabled = YES;
4918 [newPage removeFromSuperview];
4919 self.inNewTabAnimation = NO;
4920 // Use the model's currentTab here because it is possible that it can
4921 // be reset to a new value before the new Tab animation finished (e.g.
4922 // if another Tab shows a dialog via |dialogPresenter|). However, that
4923 // tab's view hasn't been displayed yet because it was in a new tab
4924 // animation.
4925 Tab* currentTab = [_model currentTab];
4926 if (currentTab) {
4927 [self tabSelected:currentTab notifyToolbar:NO];
4928 }
4929 startVoiceSearchIfNecessaryBlock();
4930
4931 if (self.foregroundTabWasAddedCompletionBlock) {
4932 self.foregroundTabWasAddedCompletionBlock();
4933 self.foregroundTabWasAddedCompletionBlock = nil;
4934 }
4935 });
4936 } else {
4937 // SnapshotTabHelper::UpdateSnapshot will force a screen redraw, so take the
4938 // snapshot before adding the views needed for the background animation.
4939 Tab* topTab = [_model currentTab];
4940 UIImage* image =
4941 SnapshotTabHelper::FromWebState(topTab.webState)
4942 ->UpdateSnapshot(/*with_overlays=*/true,
4943 /*visible_frame_only=*/self.isToolbarOnScreen);
4944
4945 // The size of the |image| above can be wrong if the snapshot fails, grab
4946 // the correct size here.
4947 CGRect imageFrame = CGRectZero;
4948 if (self.isToolbarOnScreen) {
4949 imageFrame = UIEdgeInsetsInsetRect(
Sylvain Defresne599df792018-01-11 13:14:584950 _contentArea.bounds,
4951 [self snapshotEdgeInsetsForWebState:topTab.webState]);
Mark Cogan5b494642018-01-02 12:55:464952 } else {
4953 imageFrame = [topTab.webState->GetView() bounds];
4954 }
4955
4956 // Add three layers in order on top of the contentArea for the animation:
4957 // 1. The black "background" screen.
4958 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
4959 InstallBackgroundInView(background);
4960 [_contentArea addSubview:background];
4961
4962 // 2. A CardView displaying the data from the current tab.
4963 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
4964 NSString* title = [topTab title];
4965 if (![title length])
4966 title = [topTab urlDisplayString];
4967 [topCard setTitle:title];
4968 [topCard setImage:image];
4969 [topCard setFavicon:nil];
4970
4971 favicon::FaviconDriver* faviconDriver =
4972 favicon::WebFaviconDriver::FromWebState(topTab.webState);
4973 if (faviconDriver && faviconDriver->FaviconIsValid()) {
4974 gfx::Image favicon = faviconDriver->GetFavicon();
4975 if (!favicon.IsEmpty())
4976 [topCard setFavicon:favicon.ToUIImage()];
4977 }
4978
4979 // 3. A new, blank CardView to represent the new tab being added.
4980 // Launch the new background tab animation.
4981 page_animation_util::AnimateNewBackgroundPageWithCompletion(
4982 topCard, [_contentArea frame], imageFrame, IsPortrait(), ^{
4983 [background removeFromSuperview];
4984 [topCard removeFromSuperview];
4985 self.inNewTabAnimation = NO;
4986 // Resnapshot the top card if it has its own toolbar, as the toolbar
4987 // will be captured in the new tab animation, but isn't desired for
4988 // the stack view snapshots.
4989 id nativeController = [self nativeControllerForTab:topTab];
4990 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4991 SnapshotTabHelper::FromWebState(topTab.webState)
4992 ->UpdateSnapshot(/*with_overlays=*/true,
4993 /*visible_frame_only=*/true);
4994 }
4995 startVoiceSearchIfNecessaryBlock();
4996 });
4997 // Reset the foreground tab completion block so that it can never be
4998 // called more than once regardless of foreground/background tab
4999 // appearances.
5000 self.foregroundTabWasAddedCompletionBlock = nil;
5001 }
5002}
5003
5004- (void)tabModel:(TabModel*)model didDeselectTab:(Tab*)tab {
5005 [tab wasHidden];
5006 [self dismissPopups];
5007}
5008
sdefresne49cf2862017-03-15 13:46:145009// Observer method, tab replaced.
5010- (void)tabModel:(TabModel*)model
5011 didReplaceTab:(Tab*)oldTab
5012 withTab:(Tab*)newTab
5013 atIndex:(NSUInteger)index {
5014 [self uninstallDelegatesForTab:oldTab];
5015 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:265016
michaeldo79909fb2017-05-09 23:42:505017 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:305018 infobars::InfoBarManager* infoBarManager = nullptr;
5019 if (newTab) {
5020 DCHECK(newTab.webState);
5021 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
5022 }
michaeldo79909fb2017-05-09 23:42:505023 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
5024 }
5025
Kurt Horimoto4d1f6962017-12-19 23:38:095026 // Add |newTab|'s view to the hierarchy if it's the current Tab.
5027 if (self.active && model.currentTab == newTab)
kkhorimotofa0844cc2017-03-20 17:01:265028 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:465029
5030 if (newTab)
5031 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:145032}
5033
sdefresnee65fd872016-12-19 13:38:135034// A tab has been removed, remove its views from display if necessary.
5035- (void)tabModel:(TabModel*)model
5036 didRemoveTab:(Tab*)tab
5037 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:145038 [self uninstallDelegatesForTab:tab];
5039
kkhorimoto496fdd72017-06-12 19:56:315040 // Cancel dialogs for |tab|'s WebState.
5041 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5042
sdefresnee65fd872016-12-19 13:38:135043 // Ignore changes while the tab stack view is visible (or while suspended).
5044 // The display will be refreshed when this view becomes active again.
5045 if (!self.visible || !model.webUsageEnabled)
5046 return;
5047
5048 // Remove the find bar for now.
5049 [self hideFindBarWithAnimation:NO];
5050}
5051
5052- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
5053 if (tab == [model currentTab]) {
5054 [_contentArea displayContentView:nil];
Gauthier Ambard83207452018-01-04 07:51:395055 [self.legacyToolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:135056 }
5057
Mohamad Ahmadi7d09ec32017-07-11 22:32:195058 [_paymentRequestManager stopTrackingWebState:tab.webState];
5059
sdefresnee65fd872016-12-19 13:38:135060 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
5061 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:195062 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:135063 }
5064}
5065
5066// Called when the number of tabs changes. Update the toolbar accordingly.
5067- (void)tabModelDidChangeTabCount:(TabModel*)model {
5068 DCHECK(model == _model);
Gauthier Ambard83207452018-01-04 07:51:395069 [self.legacyToolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:135070}
5071
Mark Cogan849244ee2017-12-29 15:57:195072#pragma mark - UpgradeCenterClient
sdefresnee65fd872016-12-19 13:38:135073
5074- (void)showUpgrade:(UpgradeCenter*)center {
5075 // Add an infobar on all the open tabs.
Sylvain Defresne5b1174cb2018-01-16 15:47:585076 WebStateList* webStateList = _model.webStateList;
5077 for (int index = 0; index < webStateList->count(); ++index) {
5078 web::WebState* webState = webStateList->GetWebStateAt(index);
5079 NSString* tabId = TabIdTabHelper::FromWebState(webState)->tab_id();
Rohit Raoaf46af92017-08-10 12:52:305080 infobars::InfoBarManager* infoBarManager =
Sylvain Defresne5b1174cb2018-01-16 15:47:585081 InfoBarManagerImpl::FromWebState(webState);
Rohit Raoaf46af92017-08-10 12:52:305082 DCHECK(infoBarManager);
5083 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:135084 }
5085}
5086
Mark Cogan80aa28d2017-11-30 13:11:345087#pragma mark - InfobarContainerStateDelegate
sdefresnee65fd872016-12-19 13:38:135088
Mark Cogan80aa28d2017-11-30 13:11:345089- (void)infoBarContainerStateDidChangeAnimated:(BOOL)animated {
sdefresnee65fd872016-12-19 13:38:135090 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
5091 DCHECK(infoBarContainerView);
5092 CGRect containerFrame = infoBarContainerView.frame;
5093 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
5094 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
5095 containerFrame.size.height = height;
5096 BOOL isViewVisible = self.visible;
5097 [UIView animateWithDuration:0.1
5098 animations:^{
5099 [infoBarContainerView setFrame:containerFrame];
5100 }
5101 completion:^(BOOL finished) {
5102 if (!isViewVisible)
5103 return;
5104 UIAccessibilityPostNotification(
5105 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
5106 }];
5107}
5108
Mark Cogan80aa28d2017-11-30 13:11:345109#pragma mark - UIGestureRecognizerDelegate
sdefresnee65fd872016-12-19 13:38:135110
5111// Always return yes, as this tap should work with various recognizers,
5112// including UITextTapRecognizer, UILongPressGestureRecognizer,
5113// UIScrollViewPanGestureRecognizer and others.
5114- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
5115 shouldRecognizeSimultaneouslyWithGestureRecognizer:
5116 (UIGestureRecognizer*)otherGestureRecognizer {
5117 return YES;
5118}
5119
5120// Tap gestures should only be recognized within |_contentArea|.
5121- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
5122 CGPoint location = [gesture locationInView:self.view];
5123
5124 // Only allow touches on descendant views of |_contentArea|.
5125 UIView* hitView = [self.view hitTest:location withEvent:nil];
5126 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
5127}
5128
Mark Cogan849244ee2017-12-29 15:57:195129#pragma mark - SideSwipeControllerDelegate
sdefresnee65fd872016-12-19 13:38:135130
5131- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
5132 DCHECK(!IsIPadIdiom());
5133 // Update frame incase orientation changed while |_contentArea| was out of
5134 // the view hierarchy.
5135 [_contentArea setFrame:[sideSwipeView frame]];
5136
Justin Cohen16ad60e2017-11-10 14:56:265137 [self.view insertSubview:_contentArea aboveSubview:_fakeStatusBarView];
sdefresnee65fd872016-12-19 13:38:135138 [self updateVoiceSearchBarVisibilityAnimated:NO];
5139 [self updateToolbar];
5140
5141 // Reset horizontal stack view.
5142 [sideSwipeView removeFromSuperview];
Mark Cogan5bd86ba2017-12-28 14:32:385143 [self.sideSwipeController setInSwipe:NO];
sdefresnee65fd872016-12-19 13:38:135144 [_infoBarContainer->view() setHidden:NO];
5145}
5146
Mark Cogan849244ee2017-12-29 15:57:195147- (UIView*)sideSwipeContentView {
sdefresnee65fd872016-12-19 13:38:135148 return _contentArea;
5149}
5150
Mark Cogan849244ee2017-12-29 15:57:195151- (void)sideSwipeRedisplayTab:(Tab*)tab {
5152 [self displayTab:tab isNewSelection:YES];
5153}
5154
sdefresnee65fd872016-12-19 13:38:135155- (BOOL)preventSideSwipe {
Gauthier Ambard4d485dd2018-01-11 12:43:445156 if ([self.toolbarInterface isShowingToolsMenu])
sdefresnee65fd872016-12-19 13:38:135157 return YES;
5158
5159 if (_voiceSearchController && _voiceSearchController->IsVisible())
5160 return YES;
5161
sdefresnee65fd872016-12-19 13:38:135162 if (!self.active)
5163 return YES;
5164
5165 return NO;
5166}
5167
5168- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5169 if (visible) {
5170 [self updateVoiceSearchBarVisibilityAnimated:NO];
5171 [self updateToolbar];
5172 [_infoBarContainer->view() setHidden:NO];
5173 } else {
5174 // Hide UI accessories such as find bar and first visit overlays
5175 // for welcome page.
5176 [self hideFindBarWithAnimation:NO];
5177 [_infoBarContainer->view() setHidden:YES];
5178 [_voiceSearchBar setHidden:YES];
5179 }
5180}
5181
Mark Cogan849244ee2017-12-29 15:57:195182- (CGFloat)headerHeightForSideSwipe {
5183 return self.headerHeight;
5184}
5185
sdefresnee65fd872016-12-19 13:38:135186- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5187 BOOL seenToolbar = NO;
5188 BOOL seenInfoBarContainer = NO;
5189 BOOL seenContentArea = NO;
5190 for (UIView* view in views.subviews) {
Gauthier Ambard964f1b52018-01-10 14:00:095191 if (view == _toolbarCoordinator.viewController.view)
sdefresnee65fd872016-12-19 13:38:135192 seenToolbar = YES;
5193 else if (view == _infoBarContainer->view())
5194 seenInfoBarContainer = YES;
5195 else if (view == _contentArea)
5196 seenContentArea = YES;
5197 if ((seenToolbar && !seenInfoBarContainer) ||
5198 (seenInfoBarContainer && !seenContentArea))
5199 return NO;
5200 }
5201 return YES;
5202}
5203
5204#pragma mark - PreloadControllerDelegate methods
5205
rohitraoeeb5293b2017-06-15 14:40:025206- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445207 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135208}
5209
rohitraoeeb5293b2017-06-15 14:40:025210- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5211 return [self hasControllerForURL:url];
5212}
5213
Gauthier Ambard65e949b092017-11-29 08:46:205214// TODO(crbug.com/788705): BVC doesn't need to implement
5215// BookmarkModelBridgeObserver once the new toolbar is turned on.
5216#pragma mark - BookmarkModelBridgeObserver
sdefresnee65fd872016-12-19 13:38:135217
5218// If an added or removed bookmark is the same as the current url, update the
5219// toolbar so the star highlight is kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205220- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
5221 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:135222}
5223
5224// If all bookmarks are removed, update the toolbar so the star highlight is
5225// kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205226- (void)bookmarkModelRemovedAllNodes {
sdefresnee65fd872016-12-19 13:38:135227 [self updateToolbar];
5228}
5229
Gauthier Ambard65e949b092017-11-29 08:46:205230// In case we are on a bookmarked page before the model is loaded.
5231- (void)bookmarkModelLoaded {
5232 [self updateToolbar];
5233}
5234
5235- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
5236 // No-op -- required by BookmarkModelBridgeObserver but not used.
5237}
5238
5239- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
5240 movedFromParent:(const BookmarkNode*)oldParent
5241 toParent:(const BookmarkNode*)newParent {
5242 // No-op -- required by BookmarkModelBridgeObserver but not used.
5243}
5244
5245- (void)bookmarkNodeDeleted:(const BookmarkNode*)node
5246 fromFolder:(const BookmarkNode*)folder {
5247 // No-op -- required by BookmarkModelBridgeObserver but not used.
5248}
5249
Mark Cogan849244ee2017-12-29 15:57:195250#pragma mark - NetExportTabHelperDelegate
sdefresnee65fd872016-12-19 13:38:135251
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575252- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5253 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135254 if (![MFMailComposeViewController canSendMail]) {
5255 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575256 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135257 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575258 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135259 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5260 return;
5261 }
stkhapuginc9eee7b2017-04-10 15:49:275262 MFMailComposeViewController* mailViewController =
5263 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135264 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575265 [mailViewController setToRecipients:[context toRecipients]];
5266 [mailViewController setSubject:[context subject]];
5267 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135268
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575269 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135270 if (!textFile.empty()) {
5271 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5272 NSData* data = [NSData dataWithContentsOfFile:filename];
5273 if (data) {
5274 NSString* displayName =
5275 base::SysUTF8ToNSString(textFile.BaseName().value());
5276 [mailViewController addAttachmentData:data
5277 mimeType:@"text/plain"
5278 fileName:displayName];
5279 }
5280 }
5281
5282 [mailViewController setMailComposeDelegate:self];
5283 [self presentViewController:mailViewController animated:YES completion:nil];
5284}
5285
5286#pragma mark - MFMailComposeViewControllerDelegate methods
5287
5288- (void)mailComposeController:(MFMailComposeViewController*)controller
5289 didFinishWithResult:(MFMailComposeResult)result
5290 error:(NSError*)error {
5291 [self dismissViewControllerAnimated:YES completion:nil];
5292}
5293
Mark Cogan849244ee2017-12-29 15:57:195294#pragma mark - SKStoreProductViewControllerDelegate
sdefresnee65fd872016-12-19 13:38:135295
5296- (void)productViewControllerDidFinish:
5297 (SKStoreProductViewController*)viewController {
5298 [self dismissViewControllerAnimated:YES completion:nil];
5299}
5300
Mark Cogan849244ee2017-12-29 15:57:195301#pragma mark - StoreKitLauncher methods
5302
sdefresnee65fd872016-12-19 13:38:135303- (void)openAppStore:(NSString*)appId {
5304 if (![appId length])
5305 return;
5306 NSDictionary* product =
5307 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275308 SKStoreProductViewController* storeViewController =
5309 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135310 [storeViewController setDelegate:self];
5311 [storeViewController loadProductWithParameters:product completionBlock:nil];
5312 [self presentViewController:storeViewController animated:YES completion:nil];
5313}
5314
5315#pragma mark - TabDialogDelegate methods
5316
sdefresnee65fd872016-12-19 13:38:135317- (void)cancelDialogForTab:(Tab*)tab {
5318 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5319}
5320
Mark Cogan849244ee2017-12-29 15:57:195321#pragma mark - AppRatingPromptDelegate
sdefresnee65fd872016-12-19 13:38:135322
5323- (void)userTappedRateApp:(UIView*)view {
5324 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275325 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135326}
5327
5328- (void)userTappedSendFeedback:(UIView*)view {
5329 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275330 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205331 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135332}
5333
5334- (void)userTappedDismiss:(UIView*)view {
5335 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275336 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135337}
5338
Mark Cogan849244ee2017-12-29 15:57:195339#pragma mark - VoiceSearchBarOwner
5340
5341- (id<VoiceSearchBar>)voiceSearchBar {
5342 return _voiceSearchBar;
5343}
5344
sdefresnee65fd872016-12-19 13:38:135345#pragma mark - VoiceSearchBarDelegate
5346
5347- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275348 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135349 [self ensureVoiceSearchControllerCreated];
5350 return _voiceSearchController->IsTextToSpeechEnabled() &&
5351 _voiceSearchController->IsTextToSpeechSupported();
5352}
5353
5354- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275355 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
Sylvain Defresne17b8aa42017-12-21 16:17:175356 SnapshotTabHelper::FromWebState(self.tabModel.currentTab.webState)
5357 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
sdefresnee65fd872016-12-19 13:38:135358}
5359
Mark Cogan776e0282018-01-02 09:00:065360#pragma mark - VoiceSearchPresenter (Public)
sdefresnee65fd872016-12-19 13:38:135361
5362- (UIView*)voiceSearchButton {
5363 return _voiceSearchButton;
5364}
5365
5366- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5367 return [self currentLogoAnimationControllerOwner];
5368}
5369
Mark Cogan849244ee2017-12-29 15:57:195370#pragma mark - VoiceSearchPresenter helpers
5371
5372// The LogoAnimationControllerOwner to be used for the next logo transition
5373// animation.
5374- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
5375 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
5376 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
5377 self.shouldShowVoiceSearchBar) {
5378 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
5379 // animations.
5380 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
5381 }
5382 id currentNativeController =
5383 [self nativeControllerForTab:self.tabModel.currentTab];
5384 Protocol* possibleOwnerProtocol =
5385 @protocol(LogoAnimationControllerOwnerOwner);
5386 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
5387 [currentNativeController logoAnimationControllerOwner]) {
5388 // If the current native controller is showing a GLIF view (e.g. the NTP
5389 // when there is no doodle), use that GLIFControllerOwner.
5390 return [currentNativeController logoAnimationControllerOwner];
5391 }
5392 return nil;
5393}
5394
5395#pragma mark - ActivityServicePresentation
Rohit Rao01e0e002017-08-14 20:49:435396
5397- (void)presentActivityServiceViewController:(UIViewController*)controller {
5398 [self presentViewController:controller animated:YES completion:nil];
5399}
5400
5401- (void)activityServiceDidEndPresenting {
Mark Cogan5bd86ba2017-12-28 14:32:385402 self.dialogPresenterDelegateIsPresenting = NO;
Rohit Rao01e0e002017-08-14 20:49:435403 [self.dialogPresenter tryToPresent];
5404}
5405
Mark Cogan849244ee2017-12-29 15:57:195406- (void)showActivityServiceErrorAlertWithStringTitle:(NSString*)title
5407 message:(NSString*)message {
5408 [self showErrorAlertWithStringTitle:title message:message];
5409}
5410
5411#pragma mark - QRScannerPresenting
Rohit Raocda0a992017-08-16 15:37:115412
5413- (void)presentQRScannerViewController:(UIViewController*)controller {
5414 [self presentViewController:controller animated:YES completion:nil];
5415}
5416
5417- (void)dismissQRScannerViewController:(UIViewController*)controller
5418 completion:(void (^)(void))completion {
5419 DCHECK_EQ(controller, self.presentedViewController);
5420 [self dismissViewControllerAnimated:YES completion:completion];
5421}
5422
Mark Cogan849244ee2017-12-29 15:57:195423#pragma mark - TabHeadersDelegate
5424
5425- (CGFloat)tabHeaderHeightForTab:(Tab*)tab {
5426 return [self headerHeightForTab:tab];
5427}
5428
5429#pragma mark - TabHistoryPresentation
sczsdd860eba2017-08-10 01:55:385430
sczs0a726d22017-08-21 22:40:135431- (UIView*)viewForTabHistoryPresentation {
5432 return self.view;
5433}
5434
sczsdd860eba2017-08-10 01:55:385435- (void)prepareForTabHistoryPresentation {
5436 DCHECK(self.visible || self.dismissingModal);
5437 [[self.tabModel currentTab].webController dismissKeyboard];
[email protected]76545762018-01-24 18:30:535438 [self.dispatcher cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385439}
5440
Mike Doughertya1ec26402017-08-23 19:46:315441#pragma mark - CaptivePortalDetectorTabHelperDelegate
5442
Mike Dougherty4620cf8e2017-10-31 23:37:095443- (void)captivePortalDetectorTabHelper:
5444 (CaptivePortalDetectorTabHelper*)tabHelper
5445 connectWithLandingURL:(const GURL&)landingURL {
Mike Dougherty66e58812017-11-03 06:54:285446 [self addSelectedTabWithURL:landingURL transition:ui::PAGE_TRANSITION_TYPED];
Mike Doughertya1ec26402017-08-23 19:46:315447}
5448
Gregory Chatzinoffdf93d692017-09-09 01:32:275449#pragma mark - PageInfoPresentation
5450
Gregory Chatzinoffb6a01f72017-09-20 20:06:395451- (void)presentPageInfoView:(UIView*)pageInfoView {
5452 [pageInfoView setFrame:self.view.bounds];
5453 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275454}
5455
5456- (void)prepareForPageInfoPresentation {
5457 // Dismiss the omnibox (if open).
[email protected]76545762018-01-24 18:30:535458 [self.dispatcher cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275459}
5460
Gregory Chatzinoffb6a01f72017-09-20 20:06:395461- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5462 return [self.view convertPoint:origin fromView:nil];
5463}
5464
Sylvain Defresnecacc3a52017-09-12 13:51:045465#pragma mark - WebStatePrinter
5466
5467- (void)printWebState:(web::WebState*)webState {
5468 if (webState == [_model currentTab].webState)
Mark Cogan849244ee2017-12-29 15:57:195469 [self.dispatcher printTab];
Sylvain Defresnecacc3a52017-09-12 13:51:045470}
5471
Eugene But35ded552017-09-13 23:31:595472#pragma mark - RepostFormTabHelperDelegate
5473
5474- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
Sylvain Defresnee3c698122017-11-17 11:16:325475 presentRepostFormDialogForWebState:(web::WebState*)webState
5476 dialogAtPoint:(CGPoint)location
5477 completionHandler:(void (^)(BOOL))completion {
5478 _repostFormCoordinator =
5479 [[RepostFormCoordinator alloc] initWithBaseViewController:self
5480 dialogLocation:location
5481 webState:webState
5482 completionHandler:completion];
Eugene But35ded552017-09-13 23:31:595483 [_repostFormCoordinator start];
5484}
5485
5486- (void)repostFormTabHelperDismissRepostFormDialog:
5487 (RepostFormTabHelper*)helper {
5488 _repostFormCoordinator = nil;
5489}
5490
edchinf5150c682017-09-18 02:50:035491#pragma mark - TabStripPresentation
5492
5493- (BOOL)isTabStripFullyVisible {
5494 return ([self currentHeaderOffset] == 0.0f);
5495}
5496
5497- (void)showTabStripView:(UIView*)tabStripView {
5498 DCHECK([self isViewLoaded]);
5499 DCHECK(tabStripView);
5500 self.tabStripView = tabStripView;
5501 CGRect tabStripFrame = [self.tabStripView frame];
5502 tabStripFrame.origin = CGPointZero;
5503 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5504 // because the CGPointZero above will break reset the offset, but it's not
5505 // clear what removing that will do.
Mark Cogan849244ee2017-12-29 15:57:195506 tabStripFrame.origin.y = self.headerOffset;
edchinf5150c682017-09-18 02:50:035507 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5508 [self.tabStripView setFrame:tabStripFrame];
5509 [[self view] addSubview:tabStripView];
5510}
5511
edchincd32fdf2017-10-25 12:45:455512#pragma mark - ManageAccountsDelegate
5513
5514- (void)onManageAccounts {
5515 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5516 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5517 ->GetState());
edchin5b8aa052017-10-30 23:27:285518 [self.dispatcher showAccountsSettingsFromViewController:self];
edchincd32fdf2017-10-25 12:45:455519}
5520
5521- (void)onAddAccount {
5522 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5523 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5524 ->GetState());
edchinb082b2982017-11-03 19:41:215525 [self.dispatcher showAddAccountFromViewController:self];
edchincd32fdf2017-10-25 12:45:455526}
5527
5528- (void)onGoIncognito:(const GURL&)url {
5529 // The user taps on go incognito from the mobile U-turn webpage (the web page
5530 // that displays all users accounts available in the content area). As the
5531 // user chooses to go to incognito, the mobile U-turn page is no longer
5532 // neeeded. The current solution is to go back in history. This has the
5533 // advantage of keeping the current browsing session and give a good user
5534 // experience when the user comes back from incognito.
5535 [self.tabModel.currentTab goBack];
5536
5537 if (url.is_valid()) {
5538 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5539 initWithURL:url
5540 referrer:web::Referrer() // Strip referrer when switching modes.
5541 inIncognito:YES
5542 inBackground:NO
5543 appendTo:kLastTab];
5544 [self.dispatcher openURL:command];
5545 } else {
5546 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5547 }
5548}
5549
Mark Cogan776e0282018-01-02 09:00:065550#pragma mark - SyncPresenter (Public)
edchin95c927072017-11-04 00:35:075551
5552- (void)showReauthenticateSignin {
5553 [self.dispatcher
edchin3b46e8d2017-11-07 22:48:125554 showSignin:
5555 [[ShowSigninCommand alloc]
5556 initWithOperation:AUTHENTICATION_OPERATION_REAUTHENTICATE
5557 accessPoint:signin_metrics::AccessPoint::
5558 ACCESS_POINT_UNKNOWN]
5559 baseViewController:self];
edchin95c927072017-11-04 00:35:075560}
5561
5562- (void)showSyncSettings {
edchina14d7182017-11-06 18:37:505563 [self.dispatcher showSyncSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075564}
5565
5566- (void)showSyncPassphraseSettings {
edchinec723062017-11-06 20:03:545567 [self.dispatcher showSyncPassphraseSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075568}
5569
edchin9e7a1112017-11-07 18:28:035570#pragma mark - SigninPresenter
5571
5572- (void)showSignin:(ShowSigninCommand*)command {
edchin3b46e8d2017-11-07 22:48:125573 [self.dispatcher showSignin:command baseViewController:self];
edchin9e7a1112017-11-07 18:28:035574}
5575
sdefresnee65fd872016-12-19 13:38:135576@end