blob: 81ba938932f409b677eee224bbc913111104f422 [file] [log] [blame]
sdefresnee65fd872016-12-19 13:38:131// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ios/chrome/browser/ui/browser_view_controller.h"
6
7#import <AssetsLibrary/AssetsLibrary.h>
8#import <MobileCoreServices/MobileCoreServices.h>
9#import <PassKit/PassKit.h>
sdefresnee65fd872016-12-19 13:38:1310#import <QuartzCore/QuartzCore.h>
11
12#include <stdint.h>
13#include <cmath>
14#include <memory>
15
16#include "base/base64.h"
17#include "base/command_line.h"
mathp9b4c11d2017-07-06 20:24:1318#include "base/feature_list.h"
gambard9efce7a2017-02-09 18:53:1719#include "base/files/file_path.h"
sdefresnee65fd872016-12-19 13:38:1320#include "base/i18n/rtl.h"
21#include "base/ios/block_types.h"
22#include "base/ios/ios_util.h"
sdefresnee65fd872016-12-19 13:38:1323#include "base/logging.h"
24#include "base/mac/bind_objc_block.h"
25#include "base/mac/bundle_locations.h"
26#include "base/mac/foundation_util.h"
sdefresnee65fd872016-12-19 13:38:1327#include "base/macros.h"
28#include "base/memory/ptr_util.h"
asvitkinef1899e32017-01-27 16:30:2929#include "base/metrics/histogram_macros.h"
sdefresnee65fd872016-12-19 13:38:1330#include "base/metrics/user_metrics.h"
31#include "base/metrics/user_metrics_action.h"
sdefresnee65fd872016-12-19 13:38:1332#include "base/strings/sys_string_conversions.h"
rohitraocd324eb72017-04-04 15:36:3933#include "base/strings/utf_string_conversions.h"
Sylvain Defresnefd3ecf22017-07-12 18:47:2434#include "base/task_scheduler/post_task.h"
tzik14236032017-02-15 06:41:0135#include "base/threading/sequenced_worker_pool.h"
sdefresnee65fd872016-12-19 13:38:1336#include "components/bookmarks/browser/base_bookmark_model_observer.h"
37#include "components/bookmarks/browser/bookmark_model.h"
Sylvain Defresne7178d4c2017-09-14 13:22:3738#include "components/favicon/ios/web_favicon_driver.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2339#include "components/feature_engagement/public/event_constants.h"
Cooper Knaake4f495cf2017-07-27 23:30:0340#include "components/feature_engagement/public/feature_constants.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2341#include "components/feature_engagement/public/tracker.h"
gambardbdc07cc2017-02-03 16:43:1142#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
sdefresnee65fd872016-12-19 13:38:1343#include "components/infobars/core/infobar_manager.h"
Mark Coganca30df62017-11-20 14:29:1144#import "components/language/ios/browser/ios_language_detection_tab_helper.h"
mathp9b4c11d2017-07-06 20:24:1345#include "components/payments/core/features.h"
sdefresnee65fd872016-12-19 13:38:1346#include "components/prefs/pref_service.h"
olivierrobin52b6cd6ec2017-03-23 13:55:5447#include "components/reading_list/core/reading_list_model.h"
sdefresnee65fd872016-12-19 13:38:1348#include "components/search_engines/search_engines_pref_names.h"
49#include "components/search_engines/template_url_service.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0550#include "components/sessions/core/session_types.h"
sdefresnee65fd872016-12-19 13:38:1351#include "components/sessions/core/tab_restore_service_helper.h"
edchincd32fdf2017-10-25 12:45:4552#include "components/signin/core/browser/account_reconcilor.h"
53#include "components/signin/core/browser/signin_metrics.h"
54#import "components/signin/ios/browser/account_consistency_service.h"
Eugene Butc90499d52017-09-22 16:02:0955#include "components/signin/ios/browser/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:1356#include "components/strings/grit/components_strings.h"
57#include "components/toolbar/toolbar_model_impl.h"
58#include "ios/chrome/app/tests_hook.h"
59#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
60#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
61#include "ios/chrome/browser/chrome_url_constants.h"
62#include "ios/chrome/browser/chrome_url_util.h"
Eugene But49a7c572017-12-11 20:54:1563#import "ios/chrome/browser/download/pass_kit_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1364#include "ios/chrome/browser/experimental_flags.h"
65#import "ios/chrome/browser/favicon/favicon_loader.h"
66#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2367#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
68#include "ios/chrome/browser/feature_engagement/tracker_util.h"
sdefresnee65fd872016-12-19 13:38:1369#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
70#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3671#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1372#include "ios/chrome/browser/first_run/first_run.h"
73#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
Mark Cogan80aa28d2017-11-30 13:11:3474#include "ios/chrome/browser/infobars/infobar_container_delegate_ios.h"
sdefresnee65fd872016-12-19 13:38:1375#include "ios/chrome/browser/infobars/infobar_container_ios.h"
Mark Cogan80aa28d2017-11-30 13:11:3476#import "ios/chrome/browser/infobars/infobar_container_state_delegate.h"
sdefresnee65fd872016-12-19 13:38:1377#include "ios/chrome/browser/infobars/infobar_container_view.h"
Rohit Raoaf46af92017-08-10 12:52:3078#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
Mark Coganca30df62017-11-20 14:29:1179#import "ios/chrome/browser/language/url_language_histogram_factory.h"
sdefresnee65fd872016-12-19 13:38:1380#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
Mark Coganfc591c8c2018-01-02 16:07:0081#import "ios/chrome/browser/metrics/size_class_recorder.h"
sdefresnee65fd872016-12-19 13:38:1382#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
sdefresnee65fd872016-12-19 13:38:1383#import "ios/chrome/browser/passwords/password_controller.h"
Tomasz Garbusb844e992017-09-29 12:44:5584#include "ios/chrome/browser/passwords/password_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1385#include "ios/chrome/browser/pref_names.h"
Rohit Rao44f204302017-08-10 14:49:5486#import "ios/chrome/browser/prerender/preload_controller_delegate.h"
87#import "ios/chrome/browser/prerender/prerender_service.h"
88#import "ios/chrome/browser/prerender/prerender_service_factory.h"
olivierrobin013ba672017-03-01 21:16:2489#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1390#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
91#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
92#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0593#include "ios/chrome/browser/sessions/session_util.h"
sdefresnee65fd872016-12-19 13:38:1394#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
95#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
edchincd32fdf2017-10-25 12:45:4596#import "ios/chrome/browser/signin/account_consistency_service_factory.h"
97#include "ios/chrome/browser/signin/account_reconcilor_factory.h"
sdefresnee65fd872016-12-19 13:38:1398#import "ios/chrome/browser/snapshots/snapshot_cache.h"
99#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
100#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
Sylvain Defresne17b8aa42017-12-21 16:17:17101#import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
Mike Dougherty4620cf8e2017-10-31 23:37:09102#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
103#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper_delegate.h"
pkld6e73e52017-03-08 15:56:51104#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:28105#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13106#import "ios/chrome/browser/tabs/tab.h"
107#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:19108#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:13109#import "ios/chrome/browser/tabs/tab_model.h"
110#import "ios/chrome/browser/tabs/tab_model_observer.h"
Sylvain Defresne72c530e42017-08-25 15:28:16111#import "ios/chrome/browser/tabs/tab_private.h"
sdefresnee65fd872016-12-19 13:38:13112#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
Mark Coganca30df62017-11-20 14:29:11113#import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
114#import "ios/chrome/browser/translate/language_selection_handler.h"
Rohit Rao01e0e002017-08-14 20:49:43115#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
116#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
sdefresnee65fd872016-12-19 13:38:13117#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
Eugene But35ded552017-09-13 23:31:59118#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13119#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
120#import "ios/chrome/browser/ui/background_generator.h"
121#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
Gauthier Ambard65e949b092017-11-29 08:46:20122#include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
sdefresnee65fd872016-12-19 13:38:13123#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13124#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
Cooper Knaak33f9f402017-08-09 18:04:38125#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
sdefresnee65fd872016-12-19 13:38:13126#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
Mark Cogan5e3da152017-07-11 15:57:30127#import "ios/chrome/browser/ui/commands/application_commands.h"
Mark Cogan6c58ea92017-07-06 13:08:24128#import "ios/chrome/browser/ui/commands/browser_commands.h"
edchin9badb062017-08-16 18:47:54129#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
Mark Cogandfcdea72017-07-18 13:47:38130#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13131#import "ios/chrome/browser/ui/commands/open_url_command.h"
132#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
edchin95c927072017-11-04 00:35:07133#import "ios/chrome/browser/ui/commands/show_signin_command.h"
edchin7f210cd2017-09-28 08:03:53134#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00135#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23136#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13137#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13138#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
139#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
Eugene But87a09532017-12-08 20:02:05140#import "ios/chrome/browser/ui/download/legacy_download_manager_controller.h"
Eugene But49a7c572017-12-11 20:54:15141#import "ios/chrome/browser/ui/download/pass_kit_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13142#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
143#import "ios/chrome/browser/ui/external_file_controller.h"
Louis Romerod11747a2017-10-20 20:10:35144#import "ios/chrome/browser/ui/external_search/external_search_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13145#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
146#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19147#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
148#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_factory.h"
Kurt Horimoto803840622017-10-28 01:20:37149#import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
Kurt Horimoto06b94252017-12-08 19:45:59150#import "ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.h"
151#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h"
152#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h"
Kurt Horimoto62e97c72017-11-03 19:51:47153#import "ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38154#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13155#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
Gauthier Ambard929699412018-01-02 10:05:41156#import "ios/chrome/browser/ui/image_util/image_saver.h"
sdefresnee65fd872016-12-19 13:38:13157#import "ios/chrome/browser/ui/key_commands_provider.h"
Kurt Horimoto1945ef42017-10-26 03:57:26158#import "ios/chrome/browser/ui/location_bar_notification_names.h"
Rohit Rao9a8ad772017-10-30 22:35:59159#import "ios/chrome/browser/ui/main/main_feature_flags.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19160#import "ios/chrome/browser/ui/main_content/main_content_ui.h"
161#import "ios/chrome/browser/ui/main_content/main_content_ui_broadcasting_util.h"
162#import "ios/chrome/browser/ui/main_content/main_content_ui_state.h"
163#import "ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10164#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13165#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42166#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13167#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27168#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
169#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13170#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37171#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
Mark Coganca30df62017-11-20 14:29:11172#import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
sdefresnee65fd872016-12-19 13:38:13173#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11174#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
175#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13176#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03177#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13178#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13179#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53180#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
sczs40443972017-09-13 19:02:39181#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13182#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
edchin9e7a1112017-11-07 18:28:03183#import "ios/chrome/browser/ui/signin_interaction/public/signin_presenter.h"
edchin7f210cd2017-09-28 08:03:53184#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13185#import "ios/chrome/browser/ui/stack_view/card_view.h"
186#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
187#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13188#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03189#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
190#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
191#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
Gauthier Ambard76d826ac2017-11-16 16:27:34192#include "ios/chrome/browser/ui/toolbar/legacy_toolbar_coordinator.h"
Kurt Horimotoea429dd2017-11-28 02:24:30193#import "ios/chrome/browser/ui/toolbar/legacy_toolbar_ui_updater.h"
sdefresnee65fd872016-12-19 13:38:13194#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
195#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
Gauthier Ambard29939db12017-10-30 16:47:31196#import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
Kurt Horimotoea429dd2017-11-28 02:24:30197#import "ios/chrome/browser/ui/toolbar/toolbar_ui.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19198#import "ios/chrome/browser/ui/toolbar/toolbar_ui_broadcasting_util.h"
sczsc2b4f152017-10-11 01:44:24199#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
Peter Laurense0b80f12017-11-21 07:52:40200#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_configuration_provider.h"
201#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_presentation_provider.h"
sczsbbad1632017-07-29 03:48:00202#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13203#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
Mark Coganca30df62017-11-20 14:29:11204#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13205#include "ios/chrome/browser/ui/ui_util.h"
206#import "ios/chrome/browser/ui/uikit_ui_util.h"
Gauthier Ambard087e3572017-12-20 12:54:47207#import "ios/chrome/browser/ui/util/named_guide.h"
gambard6a138362017-02-06 17:19:28208#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13209#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
210#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51211#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13212#import "ios/chrome/browser/web/error_page_content.h"
Danyao Wang85389a82017-10-25 18:56:27213#import "ios/chrome/browser/web/load_timing_tab_helper.h"
Sylvain Defresne448351332017-12-27 10:38:36214#import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13215#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04216#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05217#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59218#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53219#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04220#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05221#import "ios/chrome/browser/web_state_list/web_state_list.h"
222#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57223#import "ios/chrome/browser/webui/net_export_tab_helper.h"
224#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
225#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13226#include "ios/chrome/grit/ios_chromium_strings.h"
227#include "ios/chrome/grit/ios_strings.h"
228#import "ios/net/request_tracker.h"
229#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
230#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
231#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
232#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
233#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
234#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
235#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
236#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
edchineeb4d422017-10-02 17:39:36237#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
sdefresnee65fd872016-12-19 13:38:13238#include "ios/web/public/navigation_item.h"
239#import "ios/web/public/navigation_manager.h"
240#include "ios/web/public/referrer_util.h"
241#include "ios/web/public/ssl_status.h"
242#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39243#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13244#include "ios/web/public/web_client.h"
245#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13246#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29247#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
Sylvain Defresnee7f2c8a2017-10-17 02:39:19248#import "ios/web/public/web_state/web_state.h"
sdefresnee65fd872016-12-19 13:38:13249#import "ios/web/public/web_state/web_state_delegate_bridge.h"
250#include "ios/web/public/web_thread.h"
251#import "ios/web/web_state/ui/crw_web_controller.h"
252#import "net/base/mac/url_conversions.h"
253#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
254#include "net/ssl/ssl_info.h"
255#include "net/url_request/url_request_context_getter.h"
256#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
257#include "ui/base/l10n/l10n_util.h"
258#include "ui/base/l10n/l10n_util_mac.h"
259#include "ui/base/page_transition_types.h"
260#include "url/gurl.h"
261
stkhapuginf58b10d02017-04-10 13:36:17262#if !defined(__has_feature) || !__has_feature(objc_arc)
263#error "This file requires ARC support."
264#endif
265
sdefresnee65fd872016-12-19 13:38:13266using base::UserMetricsAction;
267using bookmarks::BookmarkNode;
268
sdefresnee65fd872016-12-19 13:38:13269class InfoBarContainerDelegateIOS;
270
sdefresnee65fd872016-12-19 13:38:13271namespace {
272
273typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
274 // Note: these values must match the ContextMenuOption enum in histograms.xml.
275 ACTION_OPEN_IN_NEW_TAB = 0,
276 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
277 ACTION_COPY_LINK_ADDRESS = 2,
278 ACTION_SAVE_IMAGE = 6,
279 ACTION_OPEN_IMAGE = 7,
280 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
281 ACTION_SEARCH_BY_IMAGE = 11,
282 ACTION_OPEN_JAVASCRIPT = 21,
283 ACTION_READ_LATER = 22,
284 NUM_ACTIONS = 23,
285};
286
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28287void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13288 if (is_image) {
289 if (is_link) {
290 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
291 NUM_ACTIONS);
292 } else {
293 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
294 NUM_ACTIONS);
295 }
296 } else {
297 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
298 NUM_ACTIONS);
299 }
300}
301
edchinf5150c682017-09-18 02:50:03302// Returns the status bar background color.
303UIColor* StatusBarBackgroundColor() {
304 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
305}
306
307// Duration of the toolbar animation.
Kurt Horimoto62e97c72017-11-03 19:51:47308const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3;
edchinf5150c682017-09-18 02:50:03309
sdefresnee65fd872016-12-19 13:38:13310const CGFloat kVoiceSearchBarHeight = 59.0;
311
312// Dimensions to use when downsizing an image for search-by-image.
313const CGFloat kSearchByImageMaxImageArea = 90000.0;
314const CGFloat kSearchByImageMaxImageWidth = 600.0;
315const CGFloat kSearchByImageMaxImageHeight = 400.0;
316
sdefresnee65fd872016-12-19 13:38:13317enum HeaderBehaviour {
318 // The header moves completely out of the screen.
319 Hideable = 0,
320 // This header stays on screen and doesn't overlap with the content.
321 Visible,
322 // This header stay on screen and covers part of the content.
323 Overlap
324};
325
sdefresnee65fd872016-12-19 13:38:13326const CGFloat kIPadFindBarOverlap = 11;
327
328bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24329 // Most URLs are allowed in incognito; the following is an exception.
330 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13331}
332
edchineeb4d422017-10-02 17:39:36333// Snackbar category for browser view controller.
334NSString* const kBrowserViewControllerSnackbarCategory =
335 @"BrowserViewControllerSnackbarCategory";
336
rohitrao005a6432017-03-16 20:52:42337} // namespace
sdefresnee65fd872016-12-19 13:38:13338
stkhapugin952ecef2017-04-11 12:11:45339#pragma mark - HeaderDefinition helper
340
341@interface HeaderDefinition : NSObject
342
343// The header view.
344@property(nonatomic, strong) UIView* view;
345// How to place the view, and its behaviour when the headers move.
346@property(nonatomic, assign) HeaderBehaviour behaviour;
347// Reduces the height of a header to adjust for shadows.
348@property(nonatomic, assign) CGFloat heightAdjustement;
349// Nudges that particular header up by this number of points.
350@property(nonatomic, assign) CGFloat inset;
351
352- (instancetype)initWithView:(UIView*)view
353 headerBehaviour:(HeaderBehaviour)behaviour
354 heightAdjustment:(CGFloat)heightAdjustment
355 inset:(CGFloat)inset;
356
357+ (instancetype)definitionWithView:(UIView*)view
358 headerBehaviour:(HeaderBehaviour)behaviour
359 heightAdjustment:(CGFloat)heightAdjustment
360 inset:(CGFloat)inset;
361
362@end
363
364@implementation HeaderDefinition
365@synthesize view = _view;
366@synthesize behaviour = _behaviour;
367@synthesize heightAdjustement = _heightAdjustement;
368@synthesize inset = _inset;
369
370+ (instancetype)definitionWithView:(UIView*)view
371 headerBehaviour:(HeaderBehaviour)behaviour
372 heightAdjustment:(CGFloat)heightAdjustment
373 inset:(CGFloat)inset {
374 return [[self alloc] initWithView:view
375 headerBehaviour:behaviour
376 heightAdjustment:heightAdjustment
377 inset:inset];
378}
379
380- (instancetype)initWithView:(UIView*)view
381 headerBehaviour:(HeaderBehaviour)behaviour
382 heightAdjustment:(CGFloat)heightAdjustment
383 inset:(CGFloat)inset {
384 self = [super init];
385 if (self) {
386 _view = view;
387 _behaviour = behaviour;
388 _heightAdjustement = heightAdjustment;
389 _inset = inset;
390 }
391 return self;
392}
393
394@end
395
396#pragma mark - BVC
397
Rohit Rao01e0e002017-08-14 20:49:43398@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43399 AppRatingPromptDelegate,
Gauthier Ambard65e949b092017-11-29 08:46:20400 BookmarkModelBridgeObserver,
Mike Dougherty4620cf8e2017-10-31 23:37:09401 CaptivePortalDetectorTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13402 CRWNativeContentProvider,
403 CRWWebStateDelegate,
404 DialogPresenterDelegate,
Kurt Horimoto06b94252017-12-08 19:45:59405 FullscreenUIElement,
Kurt Horimoto62e97c72017-11-03 19:51:47406 LegacyFullscreenControllerDelegate,
Mark Cogan80aa28d2017-11-30 13:11:34407 InfobarContainerStateDelegate,
sdefresnee65fd872016-12-19 13:38:13408 KeyCommandsPlumbing,
Kurt Horimotoe9b6002c2017-12-04 23:19:19409 MainContentUI,
edchincd32fdf2017-10-25 12:45:45410 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13411 MFMailComposeViewControllerDelegate,
Mark Cogan849244ee2017-12-29 15:57:19412 NetExportTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13413 NewTabPageControllerObserver,
414 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27415 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13416 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55417 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13418 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11419 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59420 RepostFormTabHelperDelegate,
Gauthier Ambard29939db12017-10-30 16:47:31421 SideSwipeControllerDelegate,
Mark Cogan849244ee2017-12-29 15:57:19422 SigninPresenter,
sdefresnee65fd872016-12-19 13:38:13423 SKStoreProductViewControllerDelegate,
424 SnapshotOverlayProvider,
425 StoreKitLauncher,
426 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19427 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38428 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13429 TabModelObserver,
430 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03431 TabStripPresentation,
Peter Laurense0b80f12017-11-21 07:52:40432 ToolsMenuConfigurationProvider,
sdefresnee65fd872016-12-19 13:38:13433 UIGestureRecognizerDelegate,
Mark Cogan849244ee2017-12-29 15:57:19434 UpgradeCenterClient,
sdefresnee65fd872016-12-19 13:38:13435 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04436 VoiceSearchBarOwner,
437 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13438 // The dependency factory passed on initialization. Used to vend objects used
439 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27440 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13441
442 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27443 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13444
sczsf1620e52017-10-02 22:54:46445 // Facade objects used by |_toolbarCoordinator|.
446 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13447 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
448 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
449
sdefresnee65fd872016-12-19 13:38:13450 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27451 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13452
453 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27454 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13455
456 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27457 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13458
459 // Handles presentation of JavaScript dialogs.
460 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
461
justincohen75011c32017-04-28 16:31:39462 // Handles command dispatching.
463 CommandDispatcher* _dispatcher;
464
sdefresnee65fd872016-12-19 13:38:13465 // Keyboard commands provider. It offloads most of the keyboard commands
466 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27467 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13468
sdefresnee65fd872016-12-19 13:38:13469 // Used to inject Javascript implementing the PaymentRequest API and to
470 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27471 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13472
sdefresnee65fd872016-12-19 13:38:13473 // Used to display the Voice Search UI. Nil if not visible.
474 scoped_refptr<VoiceSearchController> _voiceSearchController;
475
gambard6299cc1d2017-02-21 13:06:03476 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27477 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03478
sdefresnee65fd872016-12-19 13:38:13479 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27480 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13481
sdefresnee65fd872016-12-19 13:38:13482 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27483 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13484
sdefresnee65fd872016-12-19 13:38:13485 // Adapter to let BVC be the delegate for WebState.
486 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
487
488 // YES if new tab is animating in.
489 BOOL _inNewTabAnimation;
490
491 // YES if Voice Search should be started when the new tab animation is
492 // finished.
493 BOOL _startVoiceSearchAfterNewTabAnimation;
494
495 // YES if the user interacts with the location bar.
496 BOOL _locationBarHasFocus;
497 // YES if a load was cancelled due to typing in the location bar.
498 BOOL _locationBarEditCancelledLoad;
499 // YES if waiting for a foreground tab due to expectNewForegroundTab.
500 BOOL _expectingForegroundTab;
501
Sylvain Defresne41170aa2017-06-15 10:25:20502 // Whether or not -shutdown has been called.
503 BOOL _isShutdown;
504
sdefresnee65fd872016-12-19 13:38:13505 // The ChromeBrowserState associated with this BVC.
506 ios::ChromeBrowserState* _browserState; // weak
507
508 // Whether or not Incognito* is enabled.
509 BOOL _isOffTheRecord;
510
511 // The last point within |_contentArea| that's received a touch.
512 CGPoint _lastTapPoint;
513
514 // The time at which |_lastTapPoint| was most recently set.
515 CFTimeInterval _lastTapTime;
516
517 // A single infobar container handles all infobars in all tabs. It keeps
518 // track of infobars for current tab (accessed via infobar helper of
519 // the current tab).
520 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
521
522 // Bridge class to deliver container change notifications to BVC.
523 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
524
525 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37526 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27527 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13528
529 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11530 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13531
sdefresnee65fd872016-12-19 13:38:13532 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27533 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13534
535 // Bridge to register for bookmark changes.
Gauthier Ambard65e949b092017-11-29 08:46:20536 std::unique_ptr<bookmarks::BookmarkModelBridge> _bookmarkModelBridge;
sdefresnee65fd872016-12-19 13:38:13537
538 // Cached pointer to the bookmarks model.
539 bookmarks::BookmarkModel* _bookmarkModel; // weak
540
541 // The controller that shows the bookmarking UI after the user taps the star
542 // button.
stkhapuginc9eee7b2017-04-10 15:49:27543 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13544
sdefresnee65fd872016-12-19 13:38:13545 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27546 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13547
Eugene But56efc322017-08-11 14:03:44548 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28549 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13550
551 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27552 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13553
Jean-François Geyelin3d47c212017-08-03 09:24:09554 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27555 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13556
Rohit Rao01e0e002017-08-14 20:49:43557 // Coordinator for the share menu (Activity Services).
558 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
559
sdefresnee65fd872016-12-19 13:38:13560 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27561 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38562
Rohit Raocda0a992017-08-16 15:37:11563 // Coordinator for the QR scanner.
564 QRScannerLegacyCoordinator* _qrScannerCoordinator;
565
sczsdd860eba2017-08-10 01:55:38566 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13567 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53568
569 // Coordinator for displaying Sad Tab.
570 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27571
572 // Coordinator for Page Info UI.
573 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59574
575 // Coordinator for displaying Repost Form dialog.
576 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22577
edchin7f210cd2017-09-28 08:03:53578 // Coordinator for displaying snackbars.
579 SnackbarCoordinator* _snackbarCoordinator;
580
sczsf1620e52017-10-02 22:54:46581 // Coordinator for the toolbar.
582 LegacyToolbarCoordinator* _toolbarCoordinator;
583
Kurt Horimotoea429dd2017-11-28 02:24:30584 // The toolbar UI updater for the toolbar managed by |_toolbarCoordinator|.
585 LegacyToolbarUIUpdater* _toolbarUIUpdater;
586
Kurt Horimotoe9b6002c2017-12-04 23:19:19587 // The main content UI updater for the content displayed by this BVC.
588 MainContentUIStateUpdater* _mainContentUIUpdater;
589
590 // The forwarder for web scroll view interation events.
591 WebScrollViewMainContentUIForwarder* _webMainContentUIForwarder;
592
Kurt Horimoto06b94252017-12-08 19:45:59593 // The updater that adjusts the toolbar's layout for fullscreen events.
594 std::unique_ptr<FullscreenUIUpdater> _fullscreenUIUpdater;
595
Louis Romerod11747a2017-10-20 20:10:35596 // Coordinator for the External Search UI.
597 ExternalSearchCoordinator* _externalSearchCoordinator;
598
Mark Coganca30df62017-11-20 14:29:11599 // Coordinator for the language selection UI.
600 LanguageSelectionCoordinator* _languageSelectionCoordinator;
601
Eugene But49a7c572017-12-11 20:54:15602 // Coordinator for the PassKit UI presentation.
603 PassKitCoordinator* _passKitCoordinator;
604
Justin Cohenb3170c32017-09-19 01:55:22605 // Fake status bar view used to blend the toolbar into the status bar.
606 UIView* _fakeStatusBarView;
Sylvain Defresnef5d2d952017-11-14 11:15:31607
608 // Stores whether the Tab currently inserted was a pre-rendered Tab. This
609 // is used to determine whether the pre-rendering animation should be played
610 // or not.
611 BOOL _insertedTabWasPrerenderedTab;
sdefresnee65fd872016-12-19 13:38:13612}
613
614// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17615@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13616// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17617@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13618// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17619@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13620// Whether the current tab can enable the request desktop menu item.
621@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
622// Whether the sharing menu should be enabled.
623@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
624// Helper method to check web controller canShowFindBar method.
625@property(nonatomic, assign, readonly) BOOL canShowFindBar;
626// Whether the controller's view is currently available.
627// YES from viewWillAppear to viewWillDisappear.
628@property(nonatomic, assign, getter=isVisible) BOOL visible;
629// Whether the controller's view is currently visible.
630// YES from viewDidAppear to viewWillDisappear.
631@property(nonatomic, assign) BOOL viewVisible;
Kurt Horimotoe9b6002c2017-12-04 23:19:19632// Whether the controller should broadcast its UI.
633@property(nonatomic, assign, getter=isBroadcasting) BOOL broadcasting;
sdefresnee65fd872016-12-19 13:38:13634// Whether the controller is currently dismissing a presented view controller.
635@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
636// Returns YES if the toolbar has not been scrolled out by fullscreen.
637@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
638 BOOL toolbarOnScreen;
639// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12640@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13641// Whether BVC prefers to hide the status bar. This value is used to determine
642// the response from the |prefersStatusBarHidden| method.
643@property(nonatomic, assign) BOOL hideStatusBar;
644// Whether the VoiceSearchBar should be displayed.
645@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
646// Coordinator for displaying a modal overlay with activity indicator to prevent
647// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17648@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13649 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39650// A block to be run when the |tabWasAdded:| method completes the animation
651// for the presentation of a new tab. Can be used to record performance metrics.
652@property(nonatomic, strong, nullable)
653 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42654// Coordinator for Recent Tabs.
655@property(nonatomic, strong)
656 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03657// Coordinator for tablet tab strip.
658@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
659// A weak reference to the view of the tab strip on tablet.
660@property(nonatomic, weak) UIView* tabStripView;
Gauthier Ambard929699412018-01-02 10:05:41661// Helper for saving images.
662@property(nonatomic, strong) ImageSaver* imageSaver;
sdefresnee65fd872016-12-19 13:38:13663
Gauthier Ambard929699412018-01-02 10:05:41664// The user agent type used to load the currently visible page. User agent
665// type is NONE if there is no visible page or visible page is a native
666// page.
liaoyukeea9f3ee62017-03-07 22:05:39667@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
668
stkhapugin952ecef2017-04-11 12:11:45669// Returns the header views, all the chrome on top of the page, including the
670// ones that cannot be scrolled off screen by full screen.
671@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
672
Cooper Knaakd0a974cd2017-08-10 18:05:47673// Used to display the new tab tip in-product help promotion bubble. |nil| if
674// the new tab tip bubble has not yet been presented. Once the bubble is
675// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38676@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47677 BubbleViewControllerPresenter* tabTipBubblePresenter;
Helen Yang9175bd52017-08-12 00:28:40678// Used to display the new incognito tab tip in-product help promotion bubble.
679@property(nonatomic, strong)
680 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
681
Justin Cohen9fe9ef672017-12-01 20:37:43682// Vertical offset for fullscreen toolbar.
683@property(nonatomic, strong) NSLayoutConstraint* toolbarOffsetConstraint;
Mark Cogan849244ee2017-12-29 15:57:19684// Y-dimension offset for placement of the header.
685@property(nonatomic, readonly) CGFloat headerOffset;
Mark Cogan849244ee2017-12-29 15:57:19686// Height of the header view for the tab model's current tab.
687@property(nonatomic, readonly) CGFloat headerHeight;
688
Mark Cogan776e0282018-01-02 09:00:06689// The webState of the active tab.
690@property(nonatomic, readonly) web::WebState* currentWebState;
sdefresnee65fd872016-12-19 13:38:13691
Mark Cogan776e0282018-01-02 09:00:06692// BVC initialization
693// ------------------
694// If the BVC is initialized with a valid browser state & tab model immediately,
695// the path is straightforward: functionality is enabled, and the UI is built
696// when -viewDidLoad is called.
697// If the BVC is initialized without a browser state or tab model, the tab model
698// and browser state may or may not be provided before -viewDidLoad is called.
699// In most cases, they will not, to improve startup performance.
700// In order to handle this, initialization of various aspects of BVC have been
701// broken out into the following functions, which have expectations (enforced
702// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
703
sdefresnee65fd872016-12-19 13:38:13704// Updates non-view-related functionality with the given browser state and tab
705// model.
706// Does not matter whether or not the view has been loaded.
707- (void)updateWithTabModel:(TabModel*)model
708 browserState:(ios::ChromeBrowserState*)browserState;
709// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
710// the status bar to mimic this layout.
711- (void)installFakeStatusBar;
712// Builds the UI parts of tab strip and the toolbar. Does not matter whether
713// or not browser state and tab model are valid.
714- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50715// Sets up the constraints on the toolbar.
716- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13717// Updates view-related functionality with the given tab model and browser
718// state. The view must have been loaded. Uses |_browserState| and |_model|.
719- (void)addUIFunctionalityForModelAndBrowserState;
Justin Cohen4eeada32017-11-13 18:21:28720// Sets the correct frame and hierarchy for subviews and helper views. Only
721// insert views on |initialLayout|.
722- (void)setUpViewLayout:(BOOL)initialLayout;
sdefresnee65fd872016-12-19 13:38:13723// Makes |tab| the currently visible tab, displaying its view. Calls
724// -selectedTabChanged on the toolbar only if |newSelection| is YES.
725- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
726// Initializes the bookmark interaction controller if not already initialized.
727- (void)initializeBookmarkInteractionController;
Mark Cogan776e0282018-01-02 09:00:06728// Installs the BVC as overscroll actions controller of |nativeContent| if
729// needed. Sets the style of the overscroll actions toolbar.
730- (void)setOverScrollActionControllerToStaticNativeContent:
731 (StaticHtmlNativeContent*)nativeContent;
732
733// UI Configuration, update and Layout
734// -----------------------------------
sdefresnee65fd872016-12-19 13:38:13735// Updates the toolbar display based on the current tab.
736- (void)updateToolbar;
Kurt Horimotoe9b6002c2017-12-04 23:19:19737// Starts or stops broadcasting the toolbar UI and main content UI depending on
738// whether the BVC is visible and active.
739- (void)updateBroadcastState;
sdefresnee65fd872016-12-19 13:38:13740// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12741// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13742- (void)updateDialogPresenterActiveState;
743// Dismisses popups and modal dialogs that are displayed above the BVC upon size
744// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
745// is performed.
746// TODO(crbug.com/522721): Support size changes for all popups and modal
747// dialogs.
748- (void)dismissPopups;
Mark Cogan776e0282018-01-02 09:00:06749// Returns whether |tab| is scrolled to the top.
750- (BOOL)isTabScrolledToTop:(Tab*)tab;
751// Returns the footer view if one exists (e.g. the voice search bar).
752- (UIView*)footerView;
753// Returns the header height needed for |tab|.
754- (CGFloat)headerHeightForTab:(Tab*)tab;
755// Sets the frame for the headers.
756- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
757 atOffset:(CGFloat)headerOffset;
758// Adds a CardView on top of the contentArea either taking the size of the full
759// screen or just the size of the space under the header.
760// Returns the CardView that was added.
761- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
Cooper Knaakd0a974cd2017-08-10 18:05:47762
Mark Cogan776e0282018-01-02 09:00:06763// Showing and Dismissing child UI
764// -------------------------------
765// Show the bookmarks page.
766- (void)showAllBookmarks;
767// Shows a panel within the New Tab Page.
768- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
769// Dismisses the "rate this app" dialog.
770- (void)dismissRateThisAppDialog;
771
772// Bubble Views
773// ------------
Cooper Knaakd0a974cd2017-08-10 18:05:47774// Returns a bubble associated with an in-product help promotion if
775// it is valid to show the promotion and |nil| otherwise. |feature| is the
776// base::Feature object associated with the given promotion. |direction| is the
777// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21778// arrow on the button. |text| is the text displayed by the bubble. This method
779// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47780- (BubbleViewControllerPresenter*)
781bubblePresenterForFeature:(const base::Feature&)feature
782 direction:(BubbleArrowDirection)direction
783 alignment:(BubbleAlignment)alignment
784 text:(NSString*)text;
785
Cooper Knaak120cee5e2017-08-10 20:57:00786// Waits to present a bubble associated with the new tab tip in-product help
787// promotion until the feature engagement tracker database is fully initialized.
788// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
789// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11790// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21791// determines it is not valid to present it. This method requires that
792// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00793- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11794// Optionally presents a bubble associated with the new tab tip in-product help
795// promotion. If the feature engagement tracker determines it is valid to show
796// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
797// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21798// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
799// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00800- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40801// Waits to present a bubble associated with the new incognito tab tip
802// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21803// fully initialized. This method requires that |self.browserState| is
804// not NULL.
Helen Yang9175bd52017-08-12 00:28:40805- (void)presentNewIncognitoTabTipBubbleOnInitialized;
806// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21807// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40808- (void)presentNewIncognitoTabTipBubble;
Cooper Knaak120cee5e2017-08-10 20:57:00809
Mark Cogan776e0282018-01-02 09:00:06810// Find Bar UI
811// -----------
sdefresnee65fd872016-12-19 13:38:13812// Update find bar with model data. If |shouldFocus| is set to YES, the text
813// field will become first responder.
814- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13815// Hide find bar.
816- (void)hideFindBarWithAnimation:(BOOL)animate;
817// Shows find bar. If |selectText| is YES, all text inside the Find Bar
818// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
819// set to be first responder.
820- (void)showFindBarWithAnimation:(BOOL)animate
821 selectText:(BOOL)selectText
822 shouldFocus:(BOOL)shouldFocus;
Mark Cogan776e0282018-01-02 09:00:06823// Redisplays the find bar if necessary furing a view controller size change,
824// using the transition coordinator |coordinator|.
825- (void)reshowFindBarIfNeededWithCoordinator:
826 (id<UIViewControllerTransitionCoordinator>)coordinator;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36827
Mark Cogan776e0282018-01-02 09:00:06828// Alerts
829// ------
830// Shows a self-dismissing snackbar displaying |message|.
831- (void)showSnackbar:(NSString*)message;
832// Shows an alert dialog with |title| and |message|.
833- (void)showErrorAlertWithStringTitle:(NSString*)title
834 message:(NSString*)message;
835
836// Tap Handling
837// ------------
838// Record the last tap point based on the |originPoint| (if any) passed in
839// |command|.
840- (void)setLastTapPoint:(OpenNewTabCommand*)command;
841// Returns the last stored |_lastTapPoint| if it's been set within the past
842// second.
843- (CGPoint)lastTapPoint;
844// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
845- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
846
847// Tab creation and selection
848// --------------------------
sdefresnee65fd872016-12-19 13:38:13849// Called when either a tab finishes loading or when a tab with finished content
850// is added directly to the model via pre-rendering. The tab must be non-nil and
851// must be a member of the tab model controlled by this BrowserViewController.
852- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
Mark Cogan776e0282018-01-02 09:00:06853// Adds a new tab with |url| and |postData| at the end of the model, and make it
854// the selected tab and return it.
855- (Tab*)addSelectedTabWithURL:(const GURL&)url
856 postData:(TemplateURLRef::PostContent*)postData
857 transition:(ui::PageTransition)transition;
858// Internal method that all of the similar public and private methods call.
859// Adds a new tab with |url| and |postData| (if not null) at |position| in the
860// tab model (or at the end if |position is NSNotFound|, with |transition| as
861// the page transition type. If |tabAddedCompletion| is nonnull, it's called
862// synchronously after the tab is added.
863- (Tab*)addSelectedTabWithURL:(const GURL&)url
864 postData:(TemplateURLRef::PostContent*)postData
865 atIndex:(NSUInteger)position
866 transition:(ui::PageTransition)transition
867 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion;
868// Whether the given tab's URL is an application specific URL.
869- (BOOL)isTabNativePage:(Tab*)tab;
870// Returns the view to use when animating a page in or out, positioning it to
871// fill the content area but not actually adding it to the view hierarchy.
872- (UIImageView*)pageOpenCloseAnimationView;
873// Add all delegates to the provided |tab|.
874- (void)installDelegatesForTab:(Tab*)tab;
875// Remove delegates from the provided |tab|.
876- (void)uninstallDelegatesForTab:(Tab*)tab;
877// Called when a tab is selected in the model. Make any required view changes.
878// The notification will not be sent when the tab is already the selected tab.
879// |notifyToolbar| indicates whether the toolbar is notified that the tab has
880// changed.
881- (void)tabSelected:(Tab*)tab notifyToolbar:(BOOL)notifyToolbar;
882// Returns the native controller being used by |tab|'s web controller.
883- (id)nativeControllerForTab:(Tab*)tab;
884
Mark Cogan776e0282018-01-02 09:00:06885// Voice Search
886// ------------
sdefresnee65fd872016-12-19 13:38:13887// Lazily instantiates |_voiceSearchController|.
888- (void)ensureVoiceSearchControllerCreated;
889// Lazily instantiates |_voiceSearchBar| and adds it to the view.
890- (void)ensureVoiceSearchBarCreated;
891// Shows/hides the voice search bar.
892- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
sdefresnee65fd872016-12-19 13:38:13893
Mark Cogan776e0282018-01-02 09:00:06894// Reading List
895// ------------
sdefresnee65fd872016-12-19 13:38:13896// Adds the given url to the reading list.
897- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
Mark Cogan776e0282018-01-02 09:00:06898
sdefresnee65fd872016-12-19 13:38:13899@end
900
sdefresnee65fd872016-12-19 13:38:13901@implementation BrowserViewController
Mark Cogan5bd86ba2017-12-28 14:32:38902// Public synthesized propeties.
sdefresnee65fd872016-12-19 13:38:13903@synthesize contentArea = _contentArea;
904@synthesize typingShield = _typingShield;
905@synthesize active = _active;
Mark Cogan5bd86ba2017-12-28 14:32:38906// Private synthesized properties
sdefresnee65fd872016-12-19 13:38:13907@synthesize visible = _visible;
908@synthesize viewVisible = _viewVisible;
Kurt Horimotoe9b6002c2017-12-04 23:19:19909@synthesize broadcasting = _broadcasting;
sdefresnee65fd872016-12-19 13:38:13910@synthesize dismissingModal = _dismissingModal;
911@synthesize hideStatusBar = _hideStatusBar;
912@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39913@synthesize foregroundTabWasAddedCompletionBlock =
914 _foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42915@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03916@synthesize tabStripCoordinator = _tabStripCoordinator;
917@synthesize tabStripView = _tabStripView;
Mark Cogan5bd86ba2017-12-28 14:32:38918@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
919@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Justin Cohen9fe9ef672017-12-01 20:37:43920@synthesize toolbarOffsetConstraint = _toolbarOffsetConstraint;
Gauthier Ambard929699412018-01-02 10:05:41921@synthesize imageSaver = _imageSaver;
Mark Cogan5bd86ba2017-12-28 14:32:38922// DialogPresenterDelegate property
923@synthesize dialogPresenterDelegateIsPresenting =
924 _dialogPresenterDelegateIsPresenting;
sdefresnee65fd872016-12-19 13:38:13925
926#pragma mark - Object lifecycle
927
Mark Cogan5e3da152017-07-11 15:57:30928- (instancetype)
929 initWithTabModel:(TabModel*)model
930 browserState:(ios::ChromeBrowserState*)browserState
931 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
932applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:13933 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
934 if (self) {
935 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17936
stkhapuginc9eee7b2017-04-10 15:49:27937 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:27938 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
939 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39940 _dispatcher = [[CommandDispatcher alloc] init];
941 [_dispatcher startDispatchingToTarget:self
942 forProtocol:@protocol(UrlLoader)];
943 [_dispatcher startDispatchingToTarget:self
944 forProtocol:@protocol(WebToolbarDelegate)];
945 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:24946 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:30947 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
948 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:32949 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
950 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
951 // dispatched to the endpoint as well. Since this is potentially
952 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
953 DCHECK(!applicationCommandEndpoint ||
954 [applicationCommandEndpoint
955 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
956 [_dispatcher
957 startDispatchingToTarget:applicationCommandEndpoint
958 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:39959
edchin7f210cd2017-09-28 08:03:53960 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
961 _snackbarCoordinator.dispatcher = _dispatcher;
962 [_snackbarCoordinator start];
963
Mark Coganca30df62017-11-20 14:29:11964 _languageSelectionCoordinator =
965 [[LanguageSelectionCoordinator alloc] initWithBaseViewController:self];
966 _languageSelectionCoordinator.presenter =
967 [[VerticalAnimationContainer alloc] init];
968
Eugene But49a7c572017-12-11 20:54:15969 _passKitCoordinator =
970 [[PassKitCoordinator alloc] initWithBaseViewController:self];
971
sdefresnee65fd872016-12-19 13:38:13972 _javaScriptDialogPresenter.reset(
973 new JavaScriptDialogPresenterImpl(_dialogPresenter));
974 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
975 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:15976 [[UpgradeCenter sharedInstance] registerClient:self
977 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:13978 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:13979 if (model && browserState)
980 [self updateWithTabModel:model browserState:browserState];
sdefresnee65fd872016-12-19 13:38:13981 }
982 return self;
983}
984
985- (instancetype)initWithNibName:(NSString*)nibNameOrNil
986 bundle:(NSBundle*)nibBundleOrNil {
987 NOTREACHED();
988 return nil;
989}
990
991- (instancetype)initWithCoder:(NSCoder*)aDecoder {
992 NOTREACHED();
993 return nil;
994}
995
996- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:20997 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:13998}
999
Mark Cogan5bd86ba2017-12-28 14:32:381000#pragma mark - Public Properties
sdefresnee65fd872016-12-19 13:38:131001
edchin3365c7d2017-09-01 22:20:371002- (id<ApplicationCommands,
1003 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371004 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531005 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371006 UrlLoader,
1007 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561008 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531009 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1010 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241011}
1012
sdefresnee65fd872016-12-19 13:38:131013- (void)setActive:(BOOL)active {
1014 if (_active == active) {
1015 return;
1016 }
1017 _active = active;
1018
1019 // If not active, display an activity indicator overlay over the view to
1020 // prevent interaction with the web page.
1021 // TODO(crbug.com/637093): This coordinator should be managed by the
1022 // coordinator used to present BrowserViewController, when implemented.
1023 if (active) {
1024 [self.activityOverlayCoordinator stop];
1025 self.activityOverlayCoordinator = nil;
1026 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171027 self.activityOverlayCoordinator =
1028 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131029 [self.activityOverlayCoordinator start];
1030 }
1031
1032 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091033 ActiveStateManager* active_state_manager =
1034 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131035 active_state_manager->SetActive(active);
1036 }
1037
1038 [_model setWebUsageEnabled:active];
1039 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191040 [self updateBroadcastState];
sdefresnee65fd872016-12-19 13:38:131041
1042 if (active) {
1043 // Make sure the tab (if any; it's possible to get here without a current
1044 // tab if the caller is about to create one) ends up on screen completely.
1045 Tab* currentTab = [_model currentTab];
1046 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441047 [self loadViewIfNeeded];
Sylvain Defresne448351332017-12-27 10:38:361048 if (_expectingForegroundTab) {
1049 PagePlaceholderTabHelper::FromWebState(currentTab.webState)
1050 ->AddPlaceholderForNextNavigation();
1051 }
sdefresnee65fd872016-12-19 13:38:131052 if (currentTab)
1053 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341054 } else {
1055 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131056 }
sdefresnee65fd872016-12-19 13:38:131057 [_paymentRequestManager enablePaymentRequest:active];
1058
1059 [self setNeedsStatusBarAppearanceUpdate];
1060}
1061
sdefresnee65fd872016-12-19 13:38:131062- (BOOL)isPlayingTTS {
1063 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1064}
1065
Mark Cogan5bd86ba2017-12-28 14:32:381066- (TabModel*)tabModel {
1067 return _model;
1068}
1069
sdefresne6165c8742017-01-16 15:42:021070- (ios::ChromeBrowserState*)browserState {
1071 return _browserState;
1072}
1073
Mark Cogan5bd86ba2017-12-28 14:32:381074#pragma mark - Private Properties
sdefresne6165c8742017-01-16 15:42:021075
sdefresnee65fd872016-12-19 13:38:131076- (SideSwipeController*)sideSwipeController {
1077 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271078 _sideSwipeController =
1079 [[SideSwipeController alloc] initWithTabModel:_model
1080 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131081 [_sideSwipeController setSnapshotDelegate:self];
Gauthier Ambard29939db12017-10-30 16:47:311082 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sdefresnee65fd872016-12-19 13:38:131083 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031084 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131085 }
1086 return _sideSwipeController;
1087}
1088
sdefresnee65fd872016-12-19 13:38:131089- (DialogPresenter*)dialogPresenter {
1090 return _dialogPresenter;
1091}
1092
Mark Cogan776e0282018-01-02 09:00:061093- (KeyCommandsProvider*)keyCommandsProvider {
1094 if (!_keyCommandsProvider) {
1095 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
1096 }
1097 return _keyCommandsProvider;
1098}
1099
sdefresnee65fd872016-12-19 13:38:131100- (BOOL)canUseDesktopUserAgent {
1101 Tab* tab = [_model currentTab];
1102 if ([self isTabNativePage:tab])
1103 return NO;
1104
1105 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441106 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131107}
1108
1109// Whether the sharing menu should be shown.
1110- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191111 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251112 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131113}
1114
1115- (BOOL)canShowFindBar {
1116 // Make sure web controller can handle find in page.
1117 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421118 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131119 return NO;
rohitrao005a6432017-03-16 20:52:421120 }
sdefresnee65fd872016-12-19 13:38:131121
rohitrao005a6432017-03-16 20:52:421122 auto* helper = FindTabHelper::FromWebState(tab.webState);
1123 return (helper && helper->CurrentPageSupportsFindInPage() &&
1124 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131125}
1126
liaoyukeea9f3ee62017-03-07 22:05:391127- (web::UserAgentType)userAgentType {
1128 web::WebState* webState = [_model currentTab].webState;
1129 if (!webState)
1130 return web::UserAgentType::NONE;
1131 web::NavigationItem* visibleItem =
1132 webState->GetNavigationManager()->GetVisibleItem();
1133 if (!visibleItem)
1134 return web::UserAgentType::NONE;
1135
1136 return visibleItem->GetUserAgentType();
1137}
1138
sdefresnee65fd872016-12-19 13:38:131139- (void)setVisible:(BOOL)visible {
1140 if (_visible == visible)
1141 return;
Peter Laurense0b80f12017-11-21 07:52:401142
sdefresnee65fd872016-12-19 13:38:131143 _visible = visible;
1144}
1145
1146- (void)setViewVisible:(BOOL)viewVisible {
1147 if (_viewVisible == viewVisible)
1148 return;
1149 _viewVisible = viewVisible;
1150 self.visible = viewVisible;
1151 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191152 [self updateBroadcastState];
1153}
1154
1155- (void)setBroadcasting:(BOOL)broadcasting {
1156 if (_broadcasting == broadcasting)
1157 return;
1158 _broadcasting = broadcasting;
1159 if (base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
1160 // TODO(crbug.com/790886): Use the Browser's broadcaster once Browsers are
1161 // supported.
Kurt Horimoto06b94252017-12-08 19:45:591162 FullscreenController* fullscreenController =
1163 FullscreenControllerFactory::GetInstance()->GetForBrowserState(
1164 _browserState);
1165 ChromeBroadcaster* broadcaster = fullscreenController->broadcaster();
Kurt Horimotoe9b6002c2017-12-04 23:19:191166 if (_broadcasting) {
1167 _toolbarUIUpdater = [[LegacyToolbarUIUpdater alloc]
1168 initWithToolbarUI:[[ToolbarUIState alloc] init]
1169 toolbarOwner:self
1170 webStateList:[_model webStateList]];
1171 [_toolbarUIUpdater startUpdating];
1172 StartBroadcastingToolbarUI(_toolbarUIUpdater.toolbarUI, broadcaster);
Kurt Horimoto06b94252017-12-08 19:45:591173
Kurt Horimotoe9b6002c2017-12-04 23:19:191174 _mainContentUIUpdater = [[MainContentUIStateUpdater alloc]
1175 initWithState:[[MainContentUIState alloc] init]];
1176 _webMainContentUIForwarder = [[WebScrollViewMainContentUIForwarder alloc]
1177 initWithUpdater:_mainContentUIUpdater
1178 webStateList:[_model webStateList]];
1179 StartBroadcastingMainContentUI(self, broadcaster);
Kurt Horimoto06b94252017-12-08 19:45:591180
1181 _fullscreenUIUpdater = base::MakeUnique<FullscreenUIUpdater>(self);
1182 fullscreenController->AddObserver(_fullscreenUIUpdater.get());
1183
1184 fullscreenController->SetWebStateList([_model webStateList]);
Kurt Horimotoe9b6002c2017-12-04 23:19:191185 } else {
1186 StopBroadcastingToolbarUI(broadcaster);
1187 StopBroadcastingMainContentUI(broadcaster);
1188 [_toolbarUIUpdater stopUpdating];
1189 _toolbarUIUpdater = nil;
1190 _mainContentUIUpdater = nil;
1191 [_webMainContentUIForwarder disconnect];
1192 _webMainContentUIForwarder = nil;
Kurt Horimoto06b94252017-12-08 19:45:591193 fullscreenController->RemoveObserver(_fullscreenUIUpdater.get());
1194 _fullscreenUIUpdater = nullptr;
1195 fullscreenController->SetWebStateList(nullptr);
Kurt Horimotoe9b6002c2017-12-04 23:19:191196 }
1197 }
sdefresnee65fd872016-12-19 13:38:131198}
1199
1200- (BOOL)isToolbarOnScreen {
Mark Cogan849244ee2017-12-29 15:57:191201 return self.headerHeight - [self currentHeaderOffset] > 0;
sdefresnee65fd872016-12-19 13:38:131202}
1203
kkhorimotoa44349c12017-04-12 23:02:121204- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1205 if (_inNewTabAnimation == inNewTabAnimation)
1206 return;
1207 _inNewTabAnimation = inNewTabAnimation;
1208 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191209 [self updateBroadcastState];
kkhorimotoa44349c12017-04-12 23:02:121210}
1211
sdefresnee65fd872016-12-19 13:38:131212- (BOOL)isInNewTabAnimation {
1213 return _inNewTabAnimation;
1214}
1215
1216- (BOOL)shouldShowVoiceSearchBar {
1217 // On iPads, the voice search bar should only be shown for regular horizontal
1218 // size class configurations. It should always be shown for voice search
1219 // results Tabs on iPhones, including configurations with regular horizontal
1220 // size classes (i.e. landscape iPhone 6 Plus).
1221 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1222 UIUserInterfaceSizeClassCompact;
1223 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1224 (!IsIPadIdiom() || compactWidth);
1225}
1226
1227- (void)setHideStatusBar:(BOOL)hideStatusBar {
1228 if (_hideStatusBar == hideStatusBar)
1229 return;
1230 _hideStatusBar = hideStatusBar;
1231 [self setNeedsStatusBarAppearanceUpdate];
1232}
1233
Mark Cogan776e0282018-01-02 09:00:061234- (NSArray<HeaderDefinition*>*)headerViews {
1235 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
1236 if (![self isViewLoaded])
1237 return results;
1238
1239 if (!IsIPadIdiom()) {
1240 if (_toolbarCoordinator.toolbarViewController.view) {
1241 [results addObject:[HeaderDefinition
1242 definitionWithView:_toolbarCoordinator
1243 .toolbarViewController.view
1244 headerBehaviour:Hideable
1245 heightAdjustment:0.0
1246 inset:0.0]];
1247 }
1248 } else {
1249 if (self.tabStripView) {
1250 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
1251 headerBehaviour:Hideable
1252 heightAdjustment:0.0
1253 inset:0.0]];
1254 }
1255 if (_toolbarCoordinator.toolbarViewController.view) {
1256 [results addObject:[HeaderDefinition
1257 definitionWithView:_toolbarCoordinator
1258 .toolbarViewController.view
1259 headerBehaviour:Hideable
1260 heightAdjustment:0.0
1261 inset:0.0]];
1262 }
1263 if ([_findBarController view]) {
1264 [results addObject:[HeaderDefinition
1265 definitionWithView:[_findBarController view]
1266 headerBehaviour:Overlap
1267 heightAdjustment:0.0
1268 inset:kIPadFindBarOverlap]];
1269 }
1270 }
1271 return [results copy];
1272}
1273
Mark Cogan849244ee2017-12-29 15:57:191274- (CGFloat)headerOffset {
1275 if (IsIPadIdiom())
1276 return StatusBarHeight();
1277 return 0.0;
1278}
1279
1280- (CGFloat)headerHeight {
1281 return [self headerHeightForTab:[_model currentTab]];
1282}
1283
Mark Cogan776e0282018-01-02 09:00:061284- (web::WebState*)currentWebState {
1285 return [[_model currentTab] webState];
1286}
1287
Mark Cogan5bd86ba2017-12-28 14:32:381288#pragma mark - Public methods
1289
1290- (void)setPrimary:(BOOL)primary {
1291 [_model setPrimary:primary];
1292 if (primary) {
1293 [self updateDialogPresenterActiveState];
1294 [self updateBroadcastState];
1295 } else {
1296 self.dialogPresenter.active = false;
1297 }
1298}
sdefresnee65fd872016-12-19 13:38:131299
1300- (void)shieldWasTapped:(id)sender {
sczsf1620e52017-10-02 22:54:461301 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131302}
1303
Cooper Knaakd0a974cd2017-08-10 18:05:471304- (void)userEnteredTabSwitcher {
1305 if ([self.tabTipBubblePresenter isUserEngaged]) {
1306 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1307 }
1308}
1309
Cooper Knaake963d6702017-08-11 21:03:111310- (void)presentBubblesIfEligible {
1311 [self presentNewTabTipBubbleOnInitialized];
Gregory Chatzinoff56635192017-12-06 02:11:261312 [self presentNewIncognitoTabTipBubbleOnInitialized];
Cooper Knaake963d6702017-08-11 21:03:111313}
1314
Mark Cogan5bd86ba2017-12-28 14:32:381315- (void)browserStateDestroyed {
1316 [self setActive:NO];
1317 [_paymentRequestManager close];
1318 _paymentRequestManager = nil;
1319 [_toolbarCoordinator browserStateDestroyed];
1320 [_model browserStateDestroyed];
1321
1322 // Disconnect child coordinators.
1323 [_activityServiceCoordinator disconnect];
1324 [_qrScannerCoordinator disconnect];
1325 [_tabHistoryCoordinator disconnect];
1326 [_pageInfoCoordinator disconnect];
1327 [_externalSearchCoordinator disconnect];
1328 [self.tabStripCoordinator stop];
1329 self.tabStripCoordinator = nil;
1330 self.tabStripView = nil;
1331
1332 _browserState = nullptr;
1333 [_dispatcher stopDispatchingToTarget:self];
1334 _dispatcher = nil;
1335}
1336
1337- (Tab*)addSelectedTabWithURL:(const GURL&)url
1338 transition:(ui::PageTransition)transition {
1339 return [self addSelectedTabWithURL:url
1340 atIndex:[_model count]
1341 transition:transition];
1342}
1343
1344- (Tab*)addSelectedTabWithURL:(const GURL&)url
1345 atIndex:(NSUInteger)position
1346 transition:(ui::PageTransition)transition {
1347 return [self addSelectedTabWithURL:url
1348 atIndex:position
1349 transition:transition
1350 tabAddedCompletion:nil];
1351}
1352
1353- (Tab*)addSelectedTabWithURL:(const GURL&)url
1354 atIndex:(NSUInteger)position
1355 transition:(ui::PageTransition)transition
1356 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
1357 return [self addSelectedTabWithURL:url
1358 postData:NULL
1359 atIndex:position
1360 transition:transition
1361 tabAddedCompletion:tabAddedCompletion];
1362}
1363
1364- (void)expectNewForegroundTab {
1365 _expectingForegroundTab = YES;
1366}
1367
1368- (void)startVoiceSearchWithOriginView:(UIView*)originView {
1369 _voiceSearchButton = originView;
1370 // Delay Voice Search until new tab animations have finished.
1371 if (self.inNewTabAnimation) {
1372 _startVoiceSearchAfterNewTabAnimation = YES;
1373 return;
1374 }
1375
1376 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
1377 // dismiss the keyboard.
1378 [self closeFindInPage];
1379 [[_model currentTab].webController dismissKeyboard];
1380
1381 // Ensure that voice search objects are created.
1382 [self ensureVoiceSearchControllerCreated];
1383 [self ensureVoiceSearchBarCreated];
1384
1385 // Present voice search.
1386 [_voiceSearchBar prepareToPresentVoiceSearch];
1387 _voiceSearchController->StartRecognition(self, [_model currentTab]);
1388 [_toolbarCoordinator cancelOmniboxEdit];
1389}
1390
1391- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
1392 dismissOmnibox:(BOOL)dismissOmnibox {
1393 [_activityServiceCoordinator cancelShare];
1394 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
1395 [_bookmarkInteractionController dismissSnackbar];
1396 if (dismissOmnibox) {
1397 [_toolbarCoordinator cancelOmniboxEdit];
1398 }
1399 [_dialogPresenter cancelAllDialogs];
1400 [self.dispatcher hidePageInfo];
1401 [self.tabTipBubblePresenter dismissAnimated:NO];
1402 [self.incognitoTabTipBubblePresenter dismissAnimated:NO];
1403 if (_voiceSearchController)
1404 _voiceSearchController->DismissMicPermissionsHelp();
1405
1406 Tab* currentTab = [_model currentTab];
1407 [currentTab dismissModals];
1408
1409 if (currentTab) {
1410 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
1411 if (findHelper) {
1412 findHelper->StopFinding(^{
1413 [self updateFindBar:NO shouldFocus:NO];
1414 });
1415 }
1416 }
1417
1418 [_paymentRequestManager cancelRequest];
1419 [_printController dismissAnimated:YES];
1420 _printController = nil;
1421 [self.dispatcher dismissToolsMenu];
1422 [_contextMenuCoordinator stop];
1423 [self dismissRateThisAppDialog];
1424
1425 if (self.presentedViewController) {
1426 // Dismisses any other modal controllers that may be present, e.g. Recent
1427 // Tabs.
1428 //
1429 // Note that currently, some controllers like the bookmark ones were already
1430 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
1431 // but are still reported as the presentedViewController. Calling
1432 // |dismissViewControllerAnimated:completion:| again would dismiss the BVC
1433 // itself, so instead check the value of |self.dismissingModal| and only
1434 // call dismiss if one of the above calls has not already triggered a
1435 // dismissal.
1436 //
1437 // To ensure the completion is called, nil is passed to the call to dismiss,
1438 // and the completion is called explicitly below.
1439 if (!TabSwitcherPresentsBVCEnabled() || !self.dismissingModal) {
1440 [self dismissViewControllerAnimated:NO completion:nil];
1441 }
1442 // Dismissed controllers will be so after a delay. Queue the completion
1443 // callback after that.
1444 if (completion) {
1445 dispatch_after(
1446 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
1447 dispatch_get_main_queue(), ^{
1448 completion();
1449 });
1450 }
1451 } else if (completion) {
1452 // If no view controllers are presented, we should be ok with dispatching
1453 // the completion block directly.
1454 dispatch_async(dispatch_get_main_queue(), completion);
1455 }
1456}
1457
1458- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
1459 return [self.tabStripCoordinator placeholderView];
1460}
1461
1462- (void)shutdown {
1463 DCHECK(!_isShutdown);
1464 _isShutdown = YES;
1465 [self.tabStripCoordinator stop];
1466 self.tabStripCoordinator = nil;
1467 [_toolbarCoordinator stop];
1468 _toolbarCoordinator = nil;
1469 self.tabStripView = nil;
1470 _infoBarContainer = nil;
1471 _readingListMenuNotifier = nil;
1472 _bookmarkModelBridge.reset();
1473 [_model removeObserver:self];
1474 [[UpgradeCenter sharedInstance] unregisterClient:self];
1475 [[NSNotificationCenter defaultCenter] removeObserver:self];
1476 [_toolbarCoordinator setToolbarDelegate:nil];
1477 if (_voiceSearchController)
1478 _voiceSearchController->SetDelegate(nil);
1479 [_rateThisAppDialog setDelegate:nil];
1480 [_model closeAllTabs];
1481 [_paymentRequestManager setActiveWebState:nullptr];
1482}
1483
1484#pragma mark - NSObject
1485
1486- (BOOL)accessibilityPerformEscape {
1487 [self dismissPopups];
1488 return YES;
1489}
1490
Mark Cogan776e0282018-01-02 09:00:061491#pragma mark - UIResponder
1492
1493- (NSArray*)keyCommands {
1494 if (![self shouldRegisterKeyboardCommands]) {
1495 return nil;
1496 }
1497 return [self.keyCommandsProvider
1498 keyCommandsForConsumer:self
1499 baseViewController:self
1500 dispatcher:self.dispatcher
1501 editingText:![self isFirstResponder]];
1502}
1503
1504#pragma mark - UIResponder helpers
1505
1506// Whether the BVC should declare keyboard commands.
1507- (BOOL)shouldRegisterKeyboardCommands {
1508 if ([self presentedViewController])
1509 return NO;
1510
1511 if (_voiceSearchController && _voiceSearchController->IsVisible())
1512 return NO;
1513
1514 // If there is no first responder, try to make the webview the first
1515 // responder.
1516 if (!GetFirstResponder()) {
1517 web::WebState* webState = _model.currentTab.webState;
1518 if (webState)
1519 [webState->GetWebViewProxy() becomeFirstResponder];
1520 }
1521
1522 return YES;
1523}
1524
Mark Cogan5bd86ba2017-12-28 14:32:381525#pragma mark - UIViewController
sdefresnee65fd872016-12-19 13:38:131526
1527// Perform additional set up after loading the view, typically from a nib.
1528- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091529 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281530 initialViewsRect.origin.y += StatusBarHeight();
1531 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131532 UIViewAutoresizing initialViewAutoresizing =
1533 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1534
stkhapuginf58b10d02017-04-10 13:36:171535 self.contentArea =
1536 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131537 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171538 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131539 self.typingShield.autoresizingMask = initialViewAutoresizing;
Justin Cohen41b1f382017-12-04 15:22:081540 self.typingShield.accessibilityIdentifier = @"Typing Shield";
1541 self.typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1542
sdefresnee65fd872016-12-19 13:38:131543 [self.typingShield addTarget:self
1544 action:@selector(shieldWasTapped:)
1545 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131546 self.view.autoresizingMask = initialViewAutoresizing;
1547 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1548 [self.view addSubview:self.contentArea];
1549 [self.view addSubview:self.typingShield];
1550 [super viewDidLoad];
1551
1552 // Install fake status bar for iPad iOS7
1553 [self installFakeStatusBar];
1554 [self buildToolbarAndTabStrip];
Justin Cohen4eeada32017-11-13 18:21:281555 [self setUpViewLayout:YES];
Justin Cohenba27610e2017-11-08 19:34:451556 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:501557 [self addConstraintsToToolbar];
1558 }
sdefresnee65fd872016-12-19 13:38:131559 // If the tab model and browser state are valid, finish initialization.
1560 if (_model && _browserState)
1561 [self addUIFunctionalityForModelAndBrowserState];
1562
1563 // Add a tap gesture recognizer to save the last tap location for the source
1564 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271565 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1566 initWithTarget:self
1567 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131568 [tapRecognizer setDelegate:self];
1569 [tapRecognizer setCancelsTouchesInView:NO];
1570 [_contentArea addGestureRecognizer:tapRecognizer];
1571}
1572
Justin Cohenb3170c32017-09-19 01:55:221573- (void)viewSafeAreaInsetsDidChange {
1574 [super viewSafeAreaInsetsDidChange];
1575 // Gate this behind iPhone X, since it's currently the only device that
1576 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501577 if (IsIPhoneX()) {
Justin Cohen4eeada32017-11-13 18:21:281578 [self setUpViewLayout:NO];
Jean-François Geyelined4cde72017-10-11 11:34:501579 }
Justin Cohenba27610e2017-11-08 19:34:451580 if (IsSafeAreaCompatibleToolbarEnabled()) {
Gauthier Ambard100670f72017-10-27 09:54:271581 // TODO(crbug.com/778236): Check if this call can be removed once the
1582 // Toolbar is a contained ViewController.
sczs42f7f7482017-11-08 01:13:271583 [_toolbarCoordinator.toolbarViewController viewSafeAreaInsetsDidChange];
Gauthier Ambard100670f72017-10-27 09:54:271584 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501585 }
Justin Cohenb3170c32017-09-19 01:55:221586}
1587
sdefresnee65fd872016-12-19 13:38:131588- (void)viewDidAppear:(BOOL)animated {
1589 [super viewDidAppear:animated];
1590 self.viewVisible = YES;
1591 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191592 [self updateBroadcastState];
Gregory Chatzinoff541b8642017-10-25 00:25:211593
1594 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1595 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1596 // check for |self.browserState| before calling the presenting the bubbles.
1597 if (self.browserState) {
1598 [self presentBubblesIfEligible];
1599 }
sdefresnee65fd872016-12-19 13:38:131600}
1601
1602- (void)viewWillAppear:(BOOL)animated {
1603 [super viewWillAppear:animated];
1604
sdefresnee65fd872016-12-19 13:38:131605 self.visible = YES;
1606
1607 // Restore hidden infobars.
Rohit Rao755c37b2017-11-10 14:05:521608 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131609 _infoBarContainer->RestoreInfobars();
1610 }
1611
1612 // If the controller is suspended, or has been paged out due to low memory,
1613 // updating the view will be handled when it's displayed again.
1614 if (![_model webUsageEnabled] || !self.contentArea)
1615 return;
1616 // Update the displayed tab (if any; the switcher may not have created one
1617 // yet) in case it changed while showing the switcher.
1618 Tab* currentTab = [_model currentTab];
1619 if (currentTab)
1620 [self displayTab:currentTab isNewSelection:YES];
1621}
1622
1623- (void)viewWillDisappear:(BOOL)animated {
1624 self.viewVisible = NO;
1625 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191626 [self updateBroadcastState];
sdefresnee65fd872016-12-19 13:38:131627 [[_model currentTab] wasHidden];
1628 [_bookmarkInteractionController dismissSnackbar];
Rohit Rao755c37b2017-11-10 14:05:521629 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131630 _infoBarContainer->SuspendInfobars();
1631 }
1632 [super viewWillDisappear:animated];
1633}
1634
1635- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1636 duration:(NSTimeInterval)duration {
1637 [super willRotateToInterfaceOrientation:orient duration:duration];
1638 [self dismissPopups];
1639 [self reshowFindBarIfNeededWithCoordinator:nil];
1640}
1641
1642- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1643 [super didRotateFromInterfaceOrientation:orient];
1644
1645 // This reinitializes the toolbar, including updating the Overlay View,
1646 // if there is one.
1647 [self updateToolbar];
Mark Cogan80aa28d2017-11-30 13:11:341648 [self infoBarContainerStateDidChangeAnimated:NO];
sdefresnee65fd872016-12-19 13:38:131649}
1650
1651- (BOOL)prefersStatusBarHidden {
1652 return self.hideStatusBar;
1653}
1654
1655// Called when in the foreground and the OS needs more memory. Release as much
1656// as possible.
1657- (void)didReceiveMemoryWarning {
1658 // Releases the view if it doesn't have a superview.
1659 [super didReceiveMemoryWarning];
1660
1661 // Release any cached data, images, etc that aren't in use.
1662 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1663 // but since the FaviconCache uses obj-c in the header, it can't be included
1664 // there.
1665 if (_browserState) {
1666 FaviconLoader* loader =
1667 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1668 _browserState);
1669 if (loader)
1670 loader->PurgeCache();
1671 }
1672
1673 if (![self isViewLoaded]) {
1674 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1675 // as the BrowserViewController.
1676 self.contentArea = nil;
1677 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271678 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131679 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271680 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421681 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461682 _toolbarCoordinator = nil;
Kurt Horimotoea429dd2017-11-28 02:24:301683 [_toolbarUIUpdater stopUpdating];
1684 _toolbarUIUpdater = nil;
stkhapuginc9eee7b2017-04-10 15:49:271685 _toolbarModelDelegate = nil;
1686 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031687 [self.tabStripCoordinator stop];
1688 self.tabStripCoordinator = nil;
1689 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271690 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131691 }
1692}
1693
1694- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1695 [super traitCollectionDidChange:previousTraitCollection];
1696 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1697 // because in some cases the presented view controller isn't a child of the
1698 // BVC in the view controller hierarchy (some intervening object isn't a
1699 // view controller).
1700 [self.presentedViewController
1701 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131702 // Update voice search bar visibility.
1703 [self updateVoiceSearchBarVisibilityAnimated:NO];
1704}
1705
1706- (void)viewWillTransitionToSize:(CGSize)size
1707 withTransitionCoordinator:
1708 (id<UIViewControllerTransitionCoordinator>)coordinator {
1709 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1710 [self dismissPopups];
1711 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1712}
1713
sdefresnee65fd872016-12-19 13:38:131714- (void)dismissViewControllerAnimated:(BOOL)flag
1715 completion:(void (^)())completion {
Rohit Rao685807a52017-11-10 20:50:111716 // It is an error to call this method when no VC is being presented.
1717 DCHECK(!TabSwitcherPresentsBVCEnabled() || self.presentedViewController);
1718
Rohit Raoa668c022017-11-08 00:04:441719 // Some calling code invokes |dismissViewControllerAnimated:completion:|
1720 // multiple times. When the BVC is displayed using VC containment, multiple
1721 // calls are effectively idempotent because only the first call has any effect
1722 // and subsequent calls do nothing. However, when the BVC is presented,
1723 // subsequent calls end up dismissing the BVC itself. This is never what we
Rohit Rao685807a52017-11-10 20:50:111724 // want, so check for this case and return early. It is not enough to check
1725 // |self.dismissingModal| because some dismissals do not go through
1726 // -[BrowserViewController dismissViewControllerAnimated:completion:|.
Rohit Raoa668c022017-11-08 00:04:441727 // TODO(crbug.com/782338): Fix callers and remove this early return.
Rohit Rao685807a52017-11-10 20:50:111728 if (TabSwitcherPresentsBVCEnabled() &&
1729 (self.dismissingModal || self.presentedViewController.isBeingDismissed)) {
Rohit Raoa668c022017-11-08 00:04:441730 return;
1731 }
1732
sdefresnee65fd872016-12-19 13:38:131733 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271734 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131735 [super dismissViewControllerAnimated:flag
1736 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271737 BrowserViewController* strongSelf = weakSelf;
Mark Cogan5bd86ba2017-12-28 14:32:381738 strongSelf.dismissingModal = NO;
1739 strongSelf.dialogPresenterDelegateIsPresenting =
1740 NO;
sdefresnee65fd872016-12-19 13:38:131741 if (completion)
1742 completion();
Mark Cogan5bd86ba2017-12-28 14:32:381743 [strongSelf.dialogPresenter tryToPresent];
sdefresnee65fd872016-12-19 13:38:131744 }];
1745}
1746
1747- (void)presentViewController:(UIViewController*)viewControllerToPresent
1748 animated:(BOOL)flag
1749 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271750 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131751 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1752 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1753 // presented, to show a temporary view of the launch screen and then remove it
1754 // when the controller for the FRE has been presented. This fix should be
1755 // removed when the FRE startup code is rewritten.
1756 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1757 experimental_flags::AlwaysDisplayFirstRun()) &&
1758 !tests_hook::DisableFirstRun();
1759 // These if statements check that |presentViewController| is being called for
1760 // the FRE case.
1761 if (firstRunLaunch &&
1762 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1763 UINavigationController* navController =
1764 base::mac::ObjCCastStrict<UINavigationController>(
1765 viewControllerToPresent);
1766 if ([navController.topViewController
1767 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1768 self.hideStatusBar = YES;
1769
1770 // Load view from Launch Screen and add it to window.
1771 NSBundle* mainBundle = base::mac::FrameworkBundle();
1772 NSArray* topObjects =
1773 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1774 UIViewController* launchScreenController =
1775 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1776 // |launchScreenView| is loaded as an autoreleased object, and is retained
1777 // by the |completion| block below.
1778 UIView* launchScreenView = launchScreenController.view;
1779 launchScreenView.userInteractionEnabled = NO;
1780 launchScreenView.frame = self.view.window.bounds;
1781 [self.view.window addSubview:launchScreenView];
1782
1783 // Replace the completion handler sent to the superclass with one which
1784 // removes |launchScreenView| and resets the status bar. If |completion|
1785 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271786 __weak BrowserViewController* weakSelf = self;
1787 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131788 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271789 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131790 if (completion)
1791 completion();
stkhapuginc9eee7b2017-04-10 15:49:271792 };
sdefresnee65fd872016-12-19 13:38:131793 }
1794 }
1795
Mark Cogan5bd86ba2017-12-28 14:32:381796 self.dialogPresenterDelegateIsPresenting = YES;
1797 if ([self.sideSwipeController inSwipe]) {
1798 [self.sideSwipeController resetContentView];
justincohen7e61cd92016-12-24 00:38:171799 }
sdefresnee65fd872016-12-19 13:38:131800
1801 [super presentViewController:viewControllerToPresent
1802 animated:flag
1803 completion:finalCompletionHandler];
1804}
1805
Mark Cogan80aa28d2017-11-30 13:11:341806- (BOOL)shouldAutorotate {
Kurt Horimoto3a449ce2017-12-27 18:22:141807 if (self.presentedViewController.beingPresented ||
1808 self.presentedViewController.beingDismissed) {
1809 // Don't rotate while a presentation or dismissal animation is occurring.
Mark Cogan80aa28d2017-11-30 13:11:341810 return NO;
Mark Cogan5bd86ba2017-12-28 14:32:381811 } else if (_sideSwipeController &&
1812 ![self.sideSwipeController shouldAutorotate]) {
Mark Cogan80aa28d2017-11-30 13:11:341813 // Don't auto rotate if side swipe controller view says not to.
1814 return NO;
1815 } else {
1816 return [super shouldAutorotate];
1817 }
1818}
1819
Mark Cogan849244ee2017-12-29 15:57:191820- (UIStatusBarStyle)preferredStatusBarStyle {
1821 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
1822 : UIStatusBarStyleDefault;
1823}
1824
Mark Cogan776e0282018-01-02 09:00:061825#pragma mark - ** Private BVC Methods **
1826
Mark Cogan776e0282018-01-02 09:00:061827#pragma mark - Private Methods: BVC Initialization
sdefresnee65fd872016-12-19 13:38:131828
1829- (void)updateWithTabModel:(TabModel*)model
1830 browserState:(ios::ChromeBrowserState*)browserState {
1831 DCHECK(model);
1832 DCHECK(browserState);
1833 DCHECK(!_model);
1834 DCHECK(!_browserState);
1835 _browserState = browserState;
1836 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271837 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381838
sdefresnee65fd872016-12-19 13:38:131839 [_model addObserver:self];
1840
1841 if (!_isOffTheRecord) {
1842 [DefaultIOSWebViewFactory
1843 registerWebViewFactory:[ChromeWebViewFactory class]];
1844 }
1845 NSUInteger count = [_model count];
1846 for (NSUInteger index = 0; index < count; ++index)
1847 [self installDelegatesForTab:[_model tabAtIndex:index]];
1848
gambardbdc07cc2017-02-03 16:43:111849 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121850 _browserState->GetRequestContext());
Gauthier Ambard929699412018-01-02 10:05:411851 self.imageSaver = [[ImageSaver alloc] initWithBaseViewController:self];
stkhapuginc9eee7b2017-04-10 15:49:271852 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131853
sdefresnedc432f42017-01-17 14:36:591854 // Register for bookmark changed notification (BookmarkModel may be null
1855 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131856 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591857 if (_bookmarkModel) {
Gauthier Ambard65e949b092017-11-29 08:46:201858 _bookmarkModelBridge.reset(
1859 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
sdefresnedc432f42017-01-17 14:36:591860 }
sdefresnee65fd872016-12-19 13:38:131861}
1862
sdefresnee65fd872016-12-19 13:38:131863- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221864 CGFloat statusBarHeight = StatusBarHeight();
1865 CGRect statusBarFrame =
1866 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1867 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1868 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131869 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221870 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1871 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1872 [_fakeStatusBarView layer].zPosition = 99;
1873 [[self view] addSubview:_fakeStatusBarView];
1874 } else {
1875 // Add a white bar on phone so that the status bar on the NTP is white.
1876 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1877 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131878 }
1879}
1880
1881// Create the UI elements. May or may not have valid browser state & tab model.
1882- (void)buildToolbarAndTabStrip {
1883 DCHECK([self isViewLoaded]);
1884 DCHECK(!_toolbarModelDelegate);
1885
Rohit Rao44f204302017-08-10 14:49:541886 // Initialize the prerender service before creating the toolbar controller.
1887 PrerenderService* prerenderService =
1888 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1889 if (prerenderService) {
1890 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131891 }
1892
1893 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201894 _toolbarModelDelegate.reset(
1895 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131896 _toolbarModelIOS.reset([_dependencyFactory
1897 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
Peter Laurense0b80f12017-11-21 07:52:401898 _toolbarCoordinator = [[LegacyToolbarCoordinator alloc]
1899 initWithBaseViewController:self
1900 toolsMenuConfigurationProvider:self
Kurt Horimoto975327e2017-12-20 01:38:061901 dispatcher:self.dispatcher
1902 browserState:_browserState];
Peter Laurense0b80f12017-11-21 07:52:401903
Mark Cogan5bd86ba2017-12-28 14:32:381904 self.sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
Peter Laurense0b80f12017-11-21 07:52:401905
sczsf1620e52017-10-02 22:54:461906 _toolbarCoordinator.tabModel = _model;
Gauthier Ambard82c8cc52017-10-26 15:59:051907 [_toolbarCoordinator
sczs32cfde82017-11-14 20:43:221908 setToolbarController:
1909 [_dependencyFactory
1910 newToolbarControllerWithDelegate:self
1911 urlLoader:self
1912 dispatcher:self.dispatcher]];
sczsf1620e52017-10-02 22:54:461913 [_dispatcher startDispatchingToTarget:_toolbarCoordinator
justincohen75011c32017-04-28 16:31:391914 forProtocol:@protocol(OmniboxFocuser)];
sczsf1620e52017-10-02 22:54:461915 [_toolbarCoordinator setTabCount:[_model count]];
Kurt Horimoto06b94252017-12-08 19:45:591916 [_toolbarCoordinator start];
Kurt Horimotoe9b6002c2017-12-04 23:19:191917 [self updateBroadcastState];
stkhapuginc9eee7b2017-04-10 15:49:271918 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461919 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:051920 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131921
sdefresnee65fd872016-12-19 13:38:131922 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031923 self.tabStripCoordinator =
1924 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1925 self.tabStripCoordinator.browserState = _browserState;
1926 self.tabStripCoordinator.dispatcher = _dispatcher;
1927 self.tabStripCoordinator.tabModel = _model;
1928 self.tabStripCoordinator.presentationProvider = self;
1929 self.tabStripCoordinator.animationWaitDuration =
Kurt Horimoto62e97c72017-11-03 19:51:471930 kLegacyFullscreenControllerToolbarAnimationDuration;
edchinf5150c682017-09-18 02:50:031931 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131932 }
1933
1934 // Create infobar container.
1935 if (!_infoBarContainerDelegate) {
1936 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1937 _infoBarContainer.reset(
1938 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1939 }
1940}
1941
Jean-François Geyelined4cde72017-10-11 11:34:501942- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:111943 NSLayoutYAxisAnchor* topAnchor;
1944 // On iPad, the toolbar is underneath the tab strip.
1945 // On iPhone, it is underneath the top of the screen.
1946 if (IsIPadIdiom()) {
1947 topAnchor = self.tabStripView.bottomAnchor;
1948 } else {
1949 topAnchor = [self view].topAnchor;
1950 }
1951
Gauthier Ambard100670f72017-10-27 09:54:271952 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501953
Justin Cohen9fe9ef672017-12-01 20:37:431954 self.toolbarOffsetConstraint =
1955 [_toolbarCoordinator.toolbarViewController.view.topAnchor
1956 constraintEqualToAnchor:topAnchor];
Jean-François Geyelined4cde72017-10-11 11:34:501957 [NSLayoutConstraint activateConstraints:@[
Justin Cohen9fe9ef672017-12-01 20:37:431958 self.toolbarOffsetConstraint,
sczs42f7f7482017-11-08 01:13:271959 [_toolbarCoordinator.toolbarViewController.view.leadingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501960 constraintEqualToAnchor:[self view].leadingAnchor],
sczs42f7f7482017-11-08 01:13:271961 [_toolbarCoordinator.toolbarViewController.view.trailingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501962 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501963 ]];
1964 [[self view] layoutIfNeeded];
1965}
1966
sdefresnee65fd872016-12-19 13:38:131967// Enable functionality that only makes sense if the views are loaded and
1968// both browser state and tab model are valid.
1969- (void)addUIFunctionalityForModelAndBrowserState {
1970 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051971 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131972 DCHECK(_model);
1973 DCHECK([self isViewLoaded]);
1974
1975 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1976
Rohit Raoaf46af92017-08-10 12:52:301977 infobars::InfoBarManager* infoBarManager = nullptr;
1978 if (_model.currentTab) {
1979 DCHECK(_model.currentTab.webState);
1980 infoBarManager =
1981 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1982 }
sdefresnee65fd872016-12-19 13:38:131983 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1984
sczsdd860eba2017-08-10 01:55:381985 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431986 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1987 initWithBaseViewController:self];
1988 _activityServiceCoordinator.dispatcher = _dispatcher;
1989 _activityServiceCoordinator.tabModel = _model;
1990 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:461991 _activityServiceCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051992 [_toolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:431993 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:431994
Rohit Raocda0a992017-08-16 15:37:111995 _qrScannerCoordinator =
1996 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
1997 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:051998 _qrScannerCoordinator.loadProvider =
1999 [_toolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:112000 _qrScannerCoordinator.presentationProvider = self;
2001
Kurt Horimoto238ae692017-12-22 23:55:472002 _tabHistoryCoordinator = [[LegacyTabHistoryCoordinator alloc]
2003 initWithBaseViewController:self
2004 browserState:_browserState];
sczsdd860eba2017-08-10 01:55:382005 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:462006 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:052007 [_toolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:382008 _tabHistoryCoordinator.tabModel = _model;
2009 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:462010 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard82c8cc52017-10-26 15:59:052011 [_toolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:382012
sczs6ae47ad2017-09-06 17:26:532013 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:202014 _sadTabCoordinator.baseViewController = self;
2015 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:532016
sczs281fbdc22017-12-20 20:59:062017 // If there are any existing SadTabHelpers in |_model|, update the helpers
2018 // delegate with the new |_sadTabCoordinator|.
2019 for (NSUInteger i = 0; i < _model.count; i++) {
2020 SadTabTabHelper* sadTabHelper =
2021 SadTabTabHelper::FromWebState([_model tabAtIndex:i].webState);
2022 DCHECK(sadTabHelper);
2023 if (sadTabHelper) {
2024 sadTabHelper->SetDelegate(_sadTabCoordinator);
2025 }
2026 }
2027
Kurt Horimoto084d73b2017-12-22 23:50:462028 _pageInfoCoordinator = [[PageInfoLegacyCoordinator alloc]
2029 initWithBaseViewController:self
2030 browserState:_browserState];
Gregory Chatzinoffdf93d692017-09-09 01:32:272031 _pageInfoCoordinator.dispatcher = _dispatcher;
2032 _pageInfoCoordinator.loader = self;
2033 _pageInfoCoordinator.presentationProvider = self;
2034 _pageInfoCoordinator.tabModel = _model;
2035
Louis Romerod11747a2017-10-20 20:10:352036 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
2037 _externalSearchCoordinator.dispatcher = _dispatcher;
2038
mathp9b4c11d2017-07-06 20:24:132039 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272040 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132041 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202042 browserState:_browserState
2043 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052044 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192045 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132046 }
2047}
2048
2049// Set the frame for the various views. View must be loaded.
Justin Cohen4eeada32017-11-13 18:21:282050- (void)setUpViewLayout:(BOOL)initialLayout {
sdefresnee65fd872016-12-19 13:38:132051 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132052 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
Mark Cogan849244ee2017-12-29 15:57:192053 CGFloat minY = self.headerOffset;
sdefresnee65fd872016-12-19 13:38:132054
Justin Cohenb3170c32017-09-19 01:55:222055 // Update the fake toolbar background height.
2056 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2057 fakeStatusBarFrame.size.height = StatusBarHeight();
2058 _fakeStatusBarView.frame = fakeStatusBarFrame;
2059
edchinf5150c682017-09-18 02:50:032060 if (self.tabStripView) {
2061 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:132062 }
2063
2064 // Position the toolbar next, either at the top of the browser view or
2065 // directly under the tabstrip.
Justin Cohen4eeada32017-11-13 18:21:282066 if (initialLayout)
2067 [self addChildViewController:_toolbarCoordinator.toolbarViewController];
sczs42f7f7482017-11-08 01:13:272068 CGRect toolbarFrame = _toolbarCoordinator.toolbarViewController.view.frame;
sdefresnee65fd872016-12-19 13:38:132069 toolbarFrame.origin = CGPointMake(0, minY);
2070 toolbarFrame.size.width = widthOfView;
Justin Cohenba27610e2017-11-08 19:34:452071 if (!IsSafeAreaCompatibleToolbarEnabled()) {
sczs42f7f7482017-11-08 01:13:272072 [_toolbarCoordinator.toolbarViewController.view setFrame:toolbarFrame];
Jean-François Geyelined4cde72017-10-11 11:34:502073 }
sdefresnee65fd872016-12-19 13:38:132074
2075 // Place the infobar container above the content area.
2076 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
Justin Cohen4eeada32017-11-13 18:21:282077 if (initialLayout)
2078 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
sdefresnee65fd872016-12-19 13:38:132079
Gauthier Ambard087e3572017-12-20 12:54:472080 // Place the toolbar controller above the infobar container and adds the
Gauthier Ambard470c50f2017-12-21 07:55:292081 // layout guides.
Gauthier Ambard087e3572017-12-20 12:54:472082 if (initialLayout) {
Justin Cohen4eeada32017-11-13 18:21:282083 [[self view] insertSubview:_toolbarCoordinator.toolbarViewController.view
2084 aboveSubview:infoBarContainerView];
Gauthier Ambard087e3572017-12-20 12:54:472085 AddNamedGuide(kOmniboxGuide, self.view);
Gauthier Ambard470c50f2017-12-21 07:55:292086 AddNamedGuide(kBackButtonGuide, self.view);
2087 AddNamedGuide(kForwardButtonGuide, self.view);
Gauthier Ambard087e3572017-12-20 12:54:472088 }
sdefresnee65fd872016-12-19 13:38:132089 minY += CGRectGetHeight(toolbarFrame);
Justin Cohen4eeada32017-11-13 18:21:282090 if (initialLayout)
2091 [_toolbarCoordinator.toolbarViewController
2092 didMoveToParentViewController:self];
sdefresnee65fd872016-12-19 13:38:132093
2094 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
2095 // content slightly.
sczs8c837782017-10-03 02:57:242096 minY -= 0.0;
sdefresnee65fd872016-12-19 13:38:132097
2098 // Adjust the content area to be under the toolbar, for fullscreen or below
2099 // the toolbar is not fullscreen.
2100 CGRect contentFrame = [_contentArea frame];
2101 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222102 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2103 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132104 [_contentArea setFrame:contentFrame];
2105
2106 // Adjust the infobar container to be either at the bottom of the screen
2107 // (iPhone) or on the lower toolbar edge (iPad).
2108 CGRect infoBarFrame = contentFrame;
2109 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2110 infoBarFrame.size.height = 0;
2111 [infoBarContainerView setFrame:infoBarFrame];
2112
2113 // Attach the typing shield to the content area but have it hidden.
Mark Cogan5bd86ba2017-12-28 14:32:382114 [self.typingShield setFrame:[_contentArea frame]];
Justin Cohen41b1f382017-12-04 15:22:082115 if (initialLayout) {
Mark Cogan5bd86ba2017-12-28 14:32:382116 [[self view] insertSubview:self.typingShield aboveSubview:_contentArea];
2117 [self.typingShield setHidden:YES];
Justin Cohen41b1f382017-12-04 15:22:082118 }
sdefresnee65fd872016-12-19 13:38:132119}
2120
sdefresnee65fd872016-12-19 13:38:132121- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2122 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442123 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132124
kkhorimotoa44349c12017-04-12 23:02:122125 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132126 // Hide findbar. |updateToolbar| will restore the findbar later.
2127 [self hideFindBarWithAnimation:NO];
2128
2129 // Make new content visible, resizing it first as the orientation may
2130 // have changed from the last time it was displayed.
2131 [[tab view] setFrame:_contentArea.bounds];
2132 [_contentArea displayContentView:[tab view]];
2133 }
2134 [self updateToolbar];
2135
2136 if (newSelection)
sczsf1620e52017-10-02 22:54:462137 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132138
2139 // Notify the Tab that it was displayed.
2140 [tab wasShown];
2141}
2142
2143- (void)initializeBookmarkInteractionController {
2144 if (_bookmarkInteractionController)
2145 return;
edchinbb8ba892017-09-12 15:44:032146 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2147 initWithBrowserState:_browserState
2148 loader:self
2149 parentController:self
2150 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132151}
2152
Mark Cogan776e0282018-01-02 09:00:062153- (void)setOverScrollActionControllerToStaticNativeContent:
2154 (StaticHtmlNativeContent*)nativeContent {
2155 if (!IsIPadIdiom()) {
2156 OverscrollActionsController* controller =
2157 [[OverscrollActionsController alloc]
2158 initWithScrollView:[nativeContent scrollView]];
2159 [controller setDelegate:self];
2160 OverscrollStyle style = _isOffTheRecord
2161 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
2162 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
2163 controller.style = style;
2164 nativeContent.overscrollActionsController = controller;
2165 }
2166}
2167
2168#pragma mark - Private Methods: UI Configuration, update and Layout
2169
sdefresnee65fd872016-12-19 13:38:132170// Update the state of back and forward buttons, hiding the forward button if
2171// there is nowhere to go. Assumes the model's current tab is up to date.
2172- (void)updateToolbar {
2173 // If the BVC has been partially torn down for low memory, wait for the
2174 // view rebuild to handle toolbar updates.
2175 if (!(_toolbarModelIOS && _browserState))
2176 return;
2177
2178 Tab* tab = [_model currentTab];
2179 if (![tab navigationManager])
2180 return;
sczsf1620e52017-10-02 22:54:462181 [_toolbarCoordinator updateToolbarState];
2182 [_toolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132183
Sylvain Defresnef5d2d952017-11-14 11:15:312184 if (_insertedTabWasPrerenderedTab && !_toolbarModelIOS->IsLoading())
sczsf1620e52017-10-02 22:54:462185 [_toolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132186
rohitrao005a6432017-03-16 20:52:422187 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2188 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132189 [self showFindBarWithAnimation:NO
2190 selectText:YES
2191 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362192 }
sdefresnee65fd872016-12-19 13:38:132193
2194 // Hide the toolbar if displaying phone NTP.
2195 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552196 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132197 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552198 if (item) {
2199 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132200 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2201 hideToolbar = isNTP && !_isOffTheRecord &&
sczsf1620e52017-10-02 22:54:462202 ![_toolbarCoordinator isOmniboxFirstResponder] &&
2203 ![_toolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132204 }
sczs42f7f7482017-11-08 01:13:272205 [_toolbarCoordinator.toolbarViewController.view setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132206 }
2207}
2208
Kurt Horimotoe9b6002c2017-12-04 23:19:192209- (void)updateBroadcastState {
2210 self.broadcasting =
Kurt Horimotoea429dd2017-11-28 02:24:302211 self.active && self.viewVisible && !self.inNewTabAnimation;
Kurt Horimotoea429dd2017-11-28 02:24:302212}
2213
sdefresnee65fd872016-12-19 13:38:132214- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122215 self.dialogPresenter.active =
2216 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132217}
2218
2219- (void)dismissPopups {
Peter Laurense0b80f12017-11-21 07:52:402220 [self.dispatcher dismissToolsMenu];
Gregory Chatzinoffdf93d692017-09-09 01:32:272221 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282222 [_tabHistoryCoordinator dismissHistoryPopup];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262223 [self.tabTipBubblePresenter dismissAnimated:NO];
2224 [self.incognitoTabTipBubblePresenter dismissAnimated:NO];
Cooper Knaak33f9f402017-08-09 18:04:382225}
2226
Mark Cogan776e0282018-01-02 09:00:062227- (BOOL)isTabScrolledToTop:(Tab*)tab {
2228 CGPoint scrollOffset =
2229 tab.webState->GetWebViewProxy().scrollViewProxy.contentOffset;
2230
2231 // If there is a native controller, use the native controller's scroll offset.
2232 id nativeController = [self nativeControllerForTab:tab];
2233 if ([nativeController conformsToProtocol:@protocol(CRWNativeContent)] &&
2234 [nativeController respondsToSelector:@selector(scrollOffset)]) {
2235 scrollOffset = [nativeController scrollOffset];
2236 }
2237 return CGPointEqualToPoint(scrollOffset, CGPointZero);
2238}
2239
2240- (UIView*)footerView {
2241 return _voiceSearchBar;
2242}
2243
2244- (CGFloat)headerHeightForTab:(Tab*)tab {
2245 id nativeController = [self nativeControllerForTab:tab];
2246 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
2247 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
2248 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
2249 // On iPhone, don't add any header height for ToolbarOwner native
2250 // controllers when they're displaying their own toolbar.
2251 return 0;
2252 }
2253
2254 NSArray<HeaderDefinition*>* views = [self headerViews];
2255
2256 CGFloat height = self.headerOffset;
2257 for (HeaderDefinition* header in views) {
2258 if (header.view && header.behaviour == Hideable) {
2259 height += CGRectGetHeight([header.view frame]) -
2260 header.heightAdjustement - header.inset;
2261 }
2262 }
2263
2264 return height - StatusBarHeight();
2265}
2266
2267- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
2268 atOffset:(CGFloat)headerOffset {
2269 CGFloat height = self.headerOffset;
2270 for (HeaderDefinition* header in headers) {
2271 CGFloat yOrigin = height - headerOffset - header.inset;
2272 // Make sure the toolbarView's constraints are also updated. Leaving the
2273 // -setFrame call to minimize changes in this CL -- otherwise the way
2274 // toolbar_view manages it's alpha changes would also need to be updated.
2275 // TODO(crbug.com/778822): This can be cleaned up when the new fullscreen
2276 // is enabled.
2277 if (IsSafeAreaCompatibleToolbarEnabled() &&
2278 header.view == _toolbarCoordinator.toolbarViewController.view &&
2279 !IsIPadIdiom()) {
2280 self.toolbarOffsetConstraint.constant = yOrigin;
2281 }
2282 CGRect frame = [header.view frame];
2283 frame.origin.y = yOrigin;
2284 [header.view setFrame:frame];
2285 if (header.behaviour != Overlap)
2286 height += CGRectGetHeight(frame);
2287 }
2288}
2289
2290- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
2291 CGRect frame = [_contentArea frame];
2292 if (!fullScreen) {
2293 // Changing the origin here is unnecessary, it's set in page_animation_util.
2294 frame.size.height -= self.headerHeight;
2295 }
2296
2297 CGFloat shortAxis = frame.size.width;
2298 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
2299 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
2300 CGFloat aspectRatio = frame.size.height / frame.size.width;
2301 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
2302 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
2303 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
2304 CGRect cardFrame = {frame.origin, cardSize};
2305
2306 CardView* card =
2307 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
2308 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
2309 : CardCloseButtonSide::LEADING;
2310 [_contentArea addSubview:card];
2311 return card;
2312}
2313
2314#pragma mark - Private Methods: Showing and Dismissing Child UI
2315
2316- (void)showAllBookmarks {
2317 DCHECK(self.visible || self.dismissingModal);
2318 GURL URL(kChromeUIBookmarksURL);
2319 Tab* tab = [_model currentTab];
2320 web::NavigationManager::WebLoadParams params(URL);
2321 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
2322 [tab navigationManager]->LoadURLWithParams(params);
2323}
2324
2325- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
2326 DCHECK(self.visible || self.dismissingModal);
2327 GURL url(kChromeUINewTabURL);
2328 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
2329 if (fragment != "") {
2330 GURL::Replacements replacement;
2331 replacement.SetRefStr(fragment);
2332 url = url.ReplaceComponents(replacement);
2333 }
2334 Tab* tab = [_model currentTab];
2335 web::NavigationManager::WebLoadParams params(url);
2336 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
2337 [tab navigationManager]->LoadURLWithParams(params);
2338}
2339
2340- (void)dismissRateThisAppDialog {
2341 if (_rateThisAppDialog) {
2342 base::RecordAction(base::UserMetricsAction(
2343 "IOSRateThisAppDialogDismissedProgramatically"));
2344 [_rateThisAppDialog dismiss];
2345 _rateThisAppDialog = nil;
2346 }
2347}
2348
2349#pragma mark - Private Methods: Bubble views
2350
Cooper Knaakd0a974cd2017-08-10 18:05:472351- (BubbleViewControllerPresenter*)
2352bubblePresenterForFeature:(const base::Feature&)feature
2353 direction:(BubbleArrowDirection)direction
2354 alignment:(BubbleAlignment)alignment
2355 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212356 DCHECK(self.browserState);
2357 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382358 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472359 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382360 }
2361 // Capture |weakSelf| instead of the feature engagement tracker object
2362 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2363 // the feature engagement tracker will remain pointing to invalid memory if
2364 // its owner (the ChromeBrowserState) is deallocated.
2365 __weak BrowserViewController* weakSelf = self;
2366 void (^dismissalCallback)(void) = ^() {
2367 BrowserViewController* strongSelf = weakSelf;
2368 if (strongSelf) {
2369 feature_engagement::TrackerFactory::GetForBrowserState(
2370 strongSelf.browserState)
2371 ->Dismissed(feature);
2372 }
2373 };
2374
Cooper Knaakd0a974cd2017-08-10 18:05:472375 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382376 [[BubbleViewControllerPresenter alloc] initWithText:text
2377 arrowDirection:direction
2378 alignment:alignment
2379 dismissalCallback:dismissalCallback];
2380
Cooper Knaakd0a974cd2017-08-10 18:05:472381 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132382}
2383
Cooper Knaak120cee5e2017-08-10 20:57:002384- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212385 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002386 // If the tab tip bubble has already been presented and the user is still
2387 // considered engaged, it can't be overwritten or set to |nil| or else it will
2388 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2389 // can be safely overwritten or set to |nil|.
2390 if (!self.tabTipBubblePresenter.isUserEngaged) {
2391 __weak BrowserViewController* weakSelf = self;
2392 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2393 [weakSelf presentNewTabTipBubble];
2394 };
2395
2396 // Because the new tab tip occurs on startup, the feature engagement
2397 // tracker's database is not guaranteed to be loaded by this time. For the
2398 // bubble to appear properly, a callback is used to guarantee the event data
2399 // is loaded before the check to see if the promotion should be displayed.
2400 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2401 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2402 }
2403}
2404
2405- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212406 DCHECK(self.browserState);
Gregory Chatzinofff192d5d2017-12-09 00:26:072407 // If the BVC is not visible, do not present the bubble.
2408 if (!self.viewVisible)
2409 return;
Gregory Chatzinoff876956212017-12-18 22:33:232410 // Do not present the bubble if there is no current tab or if the current tab
2411 // is the NTP.
Gregory Chatzinofff192d5d2017-12-09 00:26:072412 Tab* currentTab = [self.tabModel currentTab];
2413 if (!currentTab)
2414 return;
2415 if (currentTab.webState->GetVisibleURL() == kChromeUINewTabURL)
2416 return;
Gregory Chatzinoffe205f44642017-12-19 17:54:032417
2418 // Do not present the bubble if the tab is not scrolled to the top.
2419 if (![self isTabScrolledToTop:currentTab])
Gregory Chatzinoff876956212017-12-18 22:33:232420 return;
Gregory Chatzinofff192d5d2017-12-09 00:26:072421
Cooper Knaak120cee5e2017-08-10 20:57:002422 NSString* text =
2423 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2424 CGPoint tabSwitcherAnchor;
2425 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032426 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002427 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032428 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002429 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2430 } else {
sczsf1620e52017-10-02 22:54:462431 DCHECK([_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002432 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
sczsf1620e52017-10-02 22:54:462433 tabSwitcherAnchor = [_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002434 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2435 }
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262436
Cooper Knaake963d6702017-08-11 21:03:112437 // If the feature engagement tracker does not consider it valid to display
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262438 // the new tab tip, then end early to prevent the potential reassignment
2439 // of the existing |tabTipBubblePresenter| to nil.
2440 BubbleViewControllerPresenter* presenter =
Cooper Knaak120cee5e2017-08-10 20:57:002441 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2442 direction:BubbleArrowDirectionUp
2443 alignment:BubbleAlignmentTrailing
2444 text:text];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262445 if (!presenter)
2446 return;
2447
2448 self.tabTipBubblePresenter = presenter;
2449
Cooper Knaak120cee5e2017-08-10 20:57:002450 [self.tabTipBubblePresenter presentInViewController:self
2451 view:self.view
2452 anchorPoint:tabSwitcherAnchor];
2453}
2454
Helen Yang9175bd52017-08-12 00:28:402455- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212456 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402457 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2458 // user is still considered engaged.
2459 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2460 __weak BrowserViewController* weakSelf = self;
2461 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2462 [weakSelf presentNewIncognitoTabTipBubble];
2463 };
2464
2465 // Use a callback in case the new incognito tab tip should be shown on
2466 // startup. This ensures that the tracker's database will be fully loaded
2467 // before checking if the promotion should be displayed.
2468 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2469 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2470 }
2471}
2472
2473- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212474 DCHECK(self.browserState);
sczsf1620e52017-10-02 22:54:462475 DCHECK([_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402476 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
Gregory Chatzinofff192d5d2017-12-09 00:26:072477 // If the BVC is not visible, do not present the bubble.
2478 if (!self.viewVisible)
2479 return;
2480
Gregory Chatzinoff876956212017-12-18 22:33:232481 // Do not present the bubble if there is no current tab.
2482 Tab* currentTab = [self.tabModel currentTab];
2483 if (!currentTab)
2484 return;
Gregory Chatzinoffe205f44642017-12-19 17:54:032485
2486 // Do not present the bubble if the tab is not scrolled to the top.
2487 if (![self isTabScrolledToTop:currentTab])
Gregory Chatzinoff876956212017-12-18 22:33:232488 return;
2489
Helen Yang9175bd52017-08-12 00:28:402490 NSString* text = l10n_util::GetNSStringWithFixup(
2491 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
sczsf1620e52017-10-02 22:54:462492 CGPoint toolsButtonAnchor = [_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402493 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262494
2495 // If the feature engagement tracker does not consider it valid to display
2496 // the incognito tab tip, then end early to prevent the potential reassignment
2497 // of the existing |incognitoTabTipBubblePresenter| to nil.
2498 BubbleViewControllerPresenter* presenter =
Helen Yang9175bd52017-08-12 00:28:402499 [self bubblePresenterForFeature:feature_engagement::
2500 kIPHNewIncognitoTabTipFeature
2501 direction:BubbleArrowDirectionUp
2502 alignment:BubbleAlignmentTrailing
2503 text:text];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262504 if (!presenter)
2505 return;
2506
2507 self.incognitoTabTipBubblePresenter = presenter;
2508
Helen Yang9175bd52017-08-12 00:28:402509 [self.incognitoTabTipBubblePresenter
2510 presentInViewController:self
2511 view:self.view
2512 anchorPoint:toolsButtonAnchor];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262513 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402514}
2515
Mark Cogan776e0282018-01-02 09:00:062516#pragma mark - Private Methods: Find Bar UI
Gregory Chatzinoffe205f44642017-12-19 17:54:032517
Mark Cogan776e0282018-01-02 09:00:062518- (void)hideFindBarWithAnimation:(BOOL)animate {
2519 [_findBarController hideFindBarView:animate];
Gregory Chatzinoffe205f44642017-12-19 17:54:032520}
2521
Mark Cogan776e0282018-01-02 09:00:062522- (void)showFindBarWithAnimation:(BOOL)animate
2523 selectText:(BOOL)selectText
2524 shouldFocus:(BOOL)shouldFocus {
2525 DCHECK(_findBarController);
2526 Tab* tab = [_model currentTab];
2527 DCHECK(tab);
2528 CRWWebController* webController = tab.webController;
Mark Cogan849244ee2017-12-29 15:57:192529
Mark Cogan776e0282018-01-02 09:00:062530 CGRect referenceFrame = CGRectZero;
2531 if (IsIPadIdiom()) {
2532 referenceFrame = webController.visibleFrame;
2533 referenceFrame.origin.y -= kIPadFindBarOverlap;
Mark Cogan849244ee2017-12-29 15:57:192534 } else {
Mark Cogan776e0282018-01-02 09:00:062535 referenceFrame = _contentArea.frame;
Mark Cogan849244ee2017-12-29 15:57:192536 }
2537
Mark Cogan776e0282018-01-02 09:00:062538 CGRect omniboxFrame = [_toolbarCoordinator visibleOmniboxFrame];
2539 [_findBarController addFindBarView:animate
2540 intoView:self.view
2541 withFrame:referenceFrame
2542 alignWithFrame:omniboxFrame
2543 selectText:selectText];
2544 [self updateFindBar:YES shouldFocus:shouldFocus];
Mark Cogan849244ee2017-12-29 15:57:192545}
2546
Mark Cogan776e0282018-01-02 09:00:062547- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
2548 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
2549 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
2550 // in a crash.
2551 if (![_model currentTab]) {
2552 return;
2553 }
2554 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
2555 if (helper && helper->IsFindUIActive()) {
2556 if (initialUpdate && !_isOffTheRecord) {
2557 helper->RestoreSearchTerm();
Mark Cogan849244ee2017-12-29 15:57:192558 }
Mark Cogan776e0282018-01-02 09:00:062559
2560 [self setFramesForHeaders:[self headerViews]
2561 atOffset:[self currentHeaderOffset]];
2562 [_findBarController updateView:helper->GetFindResult()
2563 initialUpdate:initialUpdate
2564 focusTextfield:shouldFocus];
2565 } else {
2566 [self hideFindBarWithAnimation:YES];
Mark Cogan849244ee2017-12-29 15:57:192567 }
2568}
2569
Mark Cogan776e0282018-01-02 09:00:062570- (void)reshowFindBarIfNeededWithCoordinator:
2571 (id<UIViewControllerTransitionCoordinator>)coordinator {
2572 if (![_findBarController isFindInPageShown])
2573 return;
2574
2575 // Record focused state.
2576 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
2577
2578 [self hideFindBarWithAnimation:NO];
2579
2580 __weak BrowserViewController* weakSelf = self;
2581 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) =
2582 ^(id<UIViewControllerTransitionCoordinatorContext> context) {
2583 BrowserViewController* strongSelf = weakSelf;
2584 if (strongSelf)
2585 [strongSelf showFindBarWithAnimation:NO
2586 selectText:NO
2587 shouldFocus:isFocusedBeforeReshow];
2588 };
2589
2590 BOOL enqueued =
2591 [coordinator animateAlongsideTransition:nil completion:completion];
2592 if (!enqueued) {
2593 completion(nil);
2594 }
2595}
2596
2597#pragma mark - Private Methods: Alerts
2598
2599- (void)showErrorAlertWithStringTitle:(NSString*)title
2600 message:(NSString*)message {
2601 // Dismiss current alert.
2602 [_alertCoordinator stop];
2603
2604 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
2605 message:message
2606 viewController:self];
2607 [_alertCoordinator start];
2608}
2609
2610- (void)showSnackbar:(NSString*)text {
2611 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
2612 message.accessibilityLabel = text;
2613 message.duration = 2.0;
2614 message.category = kBrowserViewControllerSnackbarCategory;
2615 [self.dispatcher showSnackbarMessage:message];
2616}
2617
2618#pragma mark - Private Methods: Tap handling
sdefresnee65fd872016-12-19 13:38:132619
Mark Cogandfcdea72017-07-18 13:47:382620- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032621 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2622 _lastTapPoint = CGPointZero;
2623 } else {
2624 _lastTapPoint =
2625 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132626 }
Mark Cogane01ebce2017-07-12 19:31:032627 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132628}
2629
2630- (CGPoint)lastTapPoint {
2631 if (CACurrentMediaTime() - _lastTapTime < 1) {
2632 return _lastTapPoint;
2633 }
2634 return CGPointZero;
2635}
2636
2637- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2638 UIView* view = gestureRecognizer.view;
2639 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2640 _lastTapPoint =
2641 [[view superview] convertPoint:viewCoordinate toView:self.view];
2642 _lastTapTime = CACurrentMediaTime();
2643}
2644
Mark Cogan776e0282018-01-02 09:00:062645#pragma mark - Private Methods: Tab creation and selection
sdefresnee65fd872016-12-19 13:38:132646
2647// Called when either a tab finishes loading or when a tab with finished content
2648// is added directly to the model via pre-rendering.
2649- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2650 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2651
2652 // Persist the session on a delay.
2653 [_model saveSessionImmediately:NO];
2654}
2655
2656- (Tab*)addSelectedTabWithURL:(const GURL&)url
2657 postData:(TemplateURLRef::PostContent*)postData
2658 transition:(ui::PageTransition)transition {
2659 return [self addSelectedTabWithURL:url
2660 postData:postData
2661 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492662 transition:transition
2663 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132664}
2665
sdefresnee65fd872016-12-19 13:38:132666- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2667 postData:(TemplateURLRef::PostContent*)postData
2668 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492669 transition:(ui::PageTransition)transition
2670 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132671 if (position == NSNotFound)
2672 position = [_model count];
2673 DCHECK(position <= [_model count]);
2674
2675 web::NavigationManager::WebLoadParams params(URL);
2676 params.transition_type = transition;
2677 if (postData) {
2678 // Extract the content type and post params from |postData| and add them
2679 // to the load params.
2680 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2681 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2682 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172683 params.post_data.reset(data);
2684 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132685 }
Olivier Robind508a5632017-07-19 16:29:492686
2687 if (tabAddedCompletion) {
2688 if (self.foregroundTabWasAddedCompletionBlock) {
2689 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2690 self.foregroundTabWasAddedCompletionBlock;
2691 self.foregroundTabWasAddedCompletionBlock = ^{
2692 oldForegroundTabWasAddedCompletionBlock();
2693 tabAddedCompletion();
2694 };
2695 } else {
2696 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2697 }
2698 }
2699
sdefresnea6395912017-03-01 01:14:352700 Tab* tab = [_model insertTabWithLoadParams:params
2701 opener:nil
2702 openedByDOM:NO
2703 atIndex:position
2704 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132705 return tab;
2706}
2707
sdefresnee65fd872016-12-19 13:38:132708- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322709 web::WebState* webState = tab.webState;
2710 if (!webState)
2711 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392712 web::NavigationItem* visibleItem =
2713 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322714 if (!visibleItem)
2715 return NO;
2716 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132717}
2718
sdefresnee65fd872016-12-19 13:38:132719- (UIImageView*)pageOpenCloseAnimationView {
2720 CGRect frame = [_contentArea bounds];
2721
Mark Cogan849244ee2017-12-29 15:57:192722 frame.size.height = frame.size.height - self.headerHeight;
2723 frame.origin.y = self.headerHeight;
sdefresnee65fd872016-12-19 13:38:132724
stkhapuginf58b10d02017-04-10 13:36:172725 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132726 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2727 pageView.center = center;
2728
2729 pageView.backgroundColor = [UIColor whiteColor];
2730 return pageView;
2731}
2732
2733- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142734 // Unregistration happens when the Tab is removed from the TabModel.
Sylvain Defresnef5d2d952017-11-14 11:15:312735 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
2736
2737 // There should be no pre-rendered Tabs in TabModel.
2738 PrerenderService* prerenderService =
2739 PrerenderServiceFactory::GetForBrowserState(_browserState);
2740 DCHECK(!prerenderService ||
2741 !prerenderService->IsWebStatePrerendered(tab.webState));
edchincd32fdf2017-10-25 12:45:452742
Sylvain Defresne17b8aa42017-12-21 16:17:172743 SnapshotTabHelper::FromWebState(tab.webState)->SetDelegate(tab);
2744
edchincd32fdf2017-10-25 12:45:452745 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2746 if (PasswordTabHelper* passwordTabHelper =
2747 PasswordTabHelper::FromWebState(tab.webState)) {
edchin6941b8492017-12-01 18:59:592748 passwordTabHelper->SetBaseViewController(self);
edchincd32fdf2017-10-25 12:45:452749 passwordTabHelper->SetDispatcher(self.dispatcher);
2750 passwordTabHelper->SetPasswordControllerDelegate(self);
2751 }
2752
sdefresnee65fd872016-12-19 13:38:132753 tab.dialogDelegate = self;
2754 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132755 tab.passKitDialogProvider = self;
Kurt Horimotoa5a922a2017-11-07 00:21:092756 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472757 tab.legacyFullscreenControllerDelegate = self;
Kurt Horimoto803840622017-10-28 01:20:372758 }
sdefresnee65fd872016-12-19 13:38:132759 if (!IsIPadIdiom()) {
2760 tab.overscrollActionsControllerDelegate = self;
2761 }
olivierrobin9ce77b82017-01-12 17:29:192762 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132763 tab.tabSnapshottingDelegate = self;
2764 // Install the proper CRWWebController delegates.
2765 tab.webController.nativeProvider = self;
2766 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512767 // BrowserViewController presents SKStoreKitViewController on behalf of a
2768 // tab.
2769 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2770 if (tabHelper)
2771 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132772 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532773 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292774 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532775 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292776 }
Sylvain Defresnecacc3a52017-09-12 13:51:042777 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592778 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572779 NetExportTabHelper::CreateForWebState(tab.webState, self);
Mike Dougherty4620cf8e2017-10-31 23:37:092780 CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
Eugene But49a7c572017-12-11 20:54:152781 PassKitTabHelper::CreateForWebState(tab.webState, _passKitCoordinator);
edchincd32fdf2017-10-25 12:45:452782
Mark Coganca30df62017-11-20 14:29:112783 // The language detection helper accepts a callback from the translate
2784 // client, so must be created after it.
2785 // This will explode if the webState doesn't have a JS injection manager
2786 // (this only comes up in unit tests), so check for that and bypass the
2787 // init of the translation helpers if needed.
2788 // TODO(crbug.com/785238): Remove the need for this check.
2789 if (tab.webState->GetJSInjectionReceiver()) {
2790 ChromeIOSTranslateClient::CreateForWebState(tab.webState,
2791 _languageSelectionCoordinator);
2792 language::IOSLanguageDetectionTabHelper::CreateForWebState(
2793 tab.webState,
2794 ChromeIOSTranslateClient::FromWebState(tab.webState)
2795 ->GetTranslateDriver()
2796 ->CreateLanguageDetectionCallback(),
2797 UrlLanguageHistogramFactory::GetForBrowserState(self.browserState));
2798 }
2799
edchincd32fdf2017-10-25 12:45:452800 if (AccountConsistencyService* accountConsistencyService =
2801 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2802 self.browserState)) {
2803 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552804 }
sdefresnee65fd872016-12-19 13:38:132805}
2806
sdefresne49cf2862017-03-15 13:46:142807- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112808 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
edchincd32fdf2017-10-25 12:45:452809
2810 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2811 if (PasswordTabHelper* passwordTabHelper =
Sylvain Defresne17b8aa42017-12-21 16:17:172812 PasswordTabHelper::FromWebState(tab.webState)) {
edchincd32fdf2017-10-25 12:45:452813 passwordTabHelper->SetDispatcher(nil);
Sylvain Defresne17b8aa42017-12-21 16:17:172814 }
edchincd32fdf2017-10-25 12:45:452815
sdefresne49cf2862017-03-15 13:46:142816 tab.dialogDelegate = nil;
2817 tab.snapshotOverlayProvider = nil;
2818 tab.passKitDialogProvider = nil;
Kurt Horimotoa5a922a2017-11-07 00:21:092819 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472820 tab.legacyFullscreenControllerDelegate = nil;
Kurt Horimoto803840622017-10-28 01:20:372821 }
sdefresne49cf2862017-03-15 13:46:142822 if (!IsIPadIdiom()) {
2823 tab.overscrollActionsControllerDelegate = nil;
2824 }
2825 tab.tabHeadersDelegate = nil;
2826 tab.tabSnapshottingDelegate = nil;
2827 tab.webController.nativeProvider = nil;
2828 tab.webController.swipeRecognizerProvider = nil;
2829 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2830 if (tabHelper)
2831 tabHelper->SetLauncher(nil);
2832 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452833 if (AccountConsistencyService* accountConsistencyService =
2834 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2835 self.browserState)) {
2836 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2837 }
Sylvain Defresne17b8aa42017-12-21 16:17:172838
2839 SnapshotTabHelper::FromWebState(tab.webState)->SetDelegate(nil);
sdefresne49cf2862017-03-15 13:46:142840}
2841
Gauthier Ambard64396902017-12-08 10:14:582842- (void)tabSelected:(Tab*)tab notifyToolbar:(BOOL)notifyToolbar {
sdefresnee65fd872016-12-19 13:38:132843 DCHECK(tab);
2844
2845 // Ignore changes while the tab stack view is visible (or while suspended).
2846 // The display will be refreshed when this view becomes active again.
2847 if (!self.visible || ![_model webUsageEnabled])
2848 return;
2849
Gauthier Ambard64396902017-12-08 10:14:582850 [self displayTab:tab isNewSelection:notifyToolbar];
sdefresnee65fd872016-12-19 13:38:132851
kkhorimotoa44349c12017-04-12 23:02:122852 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132853 // Now that the new tab has been displayed, return to normal. Rather than
2854 // keep a reference to the previous tab, just turn off preview mode for all
2855 // tabs (since doing so is a no-op for the tabs that don't have it set).
2856 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272857 for (Tab* tab in _model) {
Sylvain Defresne448351332017-12-27 10:38:362858 PagePlaceholderTabHelper::FromWebState(tab.webState)
2859 ->CancelPlaceholderForNextNavigation();
sdefresnee65fd872016-12-19 13:38:132860 }
2861 }
2862}
2863
Mark Cogan776e0282018-01-02 09:00:062864- (id)nativeControllerForTab:(Tab*)tab {
2865 id nativeController = tab.webController.nativeController;
2866 return nativeController ? nativeController : _temporaryNativeController;
2867}
2868
Mark Cogan776e0282018-01-02 09:00:062869#pragma mark - Private Methods: Voice Search
2870
2871- (void)ensureVoiceSearchControllerCreated {
2872 if (!_voiceSearchController) {
2873 VoiceSearchProvider* provider =
2874 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2875 if (provider) {
2876 _voiceSearchController =
2877 provider->CreateVoiceSearchController(_browserState);
2878 _voiceSearchController->SetDelegate(
2879 [_toolbarCoordinator voiceSearchDelegate]);
2880 }
2881 }
2882}
2883
2884- (void)ensureVoiceSearchBarCreated {
2885 if (_voiceSearchBar)
2886 return;
2887
2888 CGFloat width = CGRectGetWidth([[self view] bounds]);
2889 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2890 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
2891 _voiceSearchBar = ios::GetChromeBrowserProvider()
2892 ->GetVoiceSearchProvider()
2893 ->BuildVoiceSearchBar(frame, self.dispatcher);
2894 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2895 [_voiceSearchBar setHidden:YES];
2896 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2897 UIViewAutoresizingFlexibleWidth];
2898 [self.view insertSubview:_voiceSearchBar
2899 belowSubview:_infoBarContainer->view()];
2900}
2901
2902- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2903 // Voice search bar exists and is shown/hidden.
2904 BOOL show = self.shouldShowVoiceSearchBar;
2905 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
2906 return;
2907
2908 // Voice search bar doesn't exist and thus is not visible.
2909 if (!_voiceSearchBar && !show)
2910 return;
2911
2912 if (animated)
2913 [_voiceSearchBar animateToBecomeVisible:show];
2914 else
2915 _voiceSearchBar.hidden = !show;
2916}
2917
2918#pragma mark - Private Methods: Reading List
2919
2920- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
2921 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
2922
2923 ReadingListModel* readingModel =
2924 ReadingListModelFactory::GetForBrowserState(_browserState);
2925 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
2926 reading_list::ADDED_VIA_CURRENT_APP);
2927
2928 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
2929 [self showSnackbar:l10n_util::GetNSString(
2930 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
2931}
2932
2933#pragma mark - ** Protocol Implementations and Helpers **
2934
sdefresnee65fd872016-12-19 13:38:132935#pragma mark - SnapshotOverlayProvider methods
2936
2937- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2938 NSMutableArray* overlays = [NSMutableArray array];
2939 if (![_model webUsageEnabled]) {
2940 return overlays;
2941 }
2942 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2943 if (voiceSearchView) {
2944 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272945 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132946 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272947 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132948 [overlays addObject:voiceSearchOverlay];
2949 }
2950 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2951 if (infoBarView) {
2952 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272953 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132954 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272955 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132956 [overlays addObject:infoBarOverlay];
2957 }
2958 return overlays;
2959}
2960
Mark Cogan849244ee2017-12-29 15:57:192961#pragma mark - SnapshotOverlayProvider helpers
sdefresnee65fd872016-12-19 13:38:132962
Mark Cogan849244ee2017-12-29 15:57:192963// Provides a view that encompasses currently displayed infobar(s) or nil
2964// if no infobar is presented.
sdefresnee65fd872016-12-19 13:38:132965- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2966 if (IsIPadIdiom()) {
2967 // Not using overlays on iPad because the content is pushed down by
2968 // infobar and the transition between snapshot and fresh page can
2969 // cause both snapshot and real infobars to appear at the same time.
2970 return nil;
2971 }
2972 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302973 if (currentTab && tab == currentTab) {
2974 DCHECK(currentTab.webState);
2975 infobars::InfoBarManager* infoBarManager =
2976 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132977 if (infoBarManager->infobar_count() > 0) {
2978 DCHECK(_infoBarContainer);
2979 return _infoBarContainer->view();
2980 }
2981 }
2982 return nil;
2983}
2984
Mark Cogan849244ee2017-12-29 15:57:192985// Returns a vertical infobar offset relative to the tab content.
sdefresnee65fd872016-12-19 13:38:132986- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272987 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132988 // There is no UI representation for non-current tabs or there is
2989 // no _infoBarContainer instantiated yet.
2990 // Return offset outside of tab.
2991 return CGRectGetMaxY(self.view.frame);
2992 } else if (IsIPadIdiom()) {
2993 // The infobars on iPad are display at the top of a tab.
2994 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2995 } else {
2996 // The infobars on iPhone are displayed at the bottom of a tab.
2997 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2998 return CGRectGetMaxY(visibleFrame) -
2999 CGRectGetHeight(_infoBarContainer->view().frame);
3000 }
3001}
3002
Mark Cogan849244ee2017-12-29 15:57:193003// Provides a view that encompasses the voice search bar if it's displayed or
3004// nil if the voice search bar isn't displayed.
sdefresnee65fd872016-12-19 13:38:133005- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
3006 Tab* currentTab = [_model currentTab];
3007 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
3008 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
3009 return _voiceSearchBar;
3010 }
3011 return nil;
3012}
3013
Mark Cogan849244ee2017-12-29 15:57:193014// Returns a vertical voice search bar offset relative to the tab content.
sdefresnee65fd872016-12-19 13:38:133015- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
3016 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
3017 // There is no UI representation for non-current tabs or there is
3018 // no visible voice search. Return offset outside of tab.
3019 return CGRectGetMaxY(self.view.frame);
3020 } else {
3021 // The voice search bar on iPhone is displayed at the bottom of a tab.
3022 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
3023 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
3024 }
3025}
3026
sdefresnee65fd872016-12-19 13:38:133027#pragma mark - PassKitDialogProvider methods
3028
3029- (void)presentPassKitDialog:(NSData*)data {
3030 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:273031 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:133032 if (data)
stkhapuginc9eee7b2017-04-10 15:49:273033 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:133034 if (error || !data) {
3035 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:303036 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:133037 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:303038 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:133039 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
3040 // NULL, replace if with DCHECK).
3041 if (infoBarManager)
3042 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
3043 }
3044 } else {
3045 PKAddPassesViewController* passKitViewController =
3046 [_dependencyFactory newPassKitViewControllerForPass:pass];
3047 if (passKitViewController) {
3048 [self presentViewController:passKitViewController
3049 animated:YES
3050 completion:^{
3051 }];
3052 }
3053 }
3054}
3055
Tomasz Garbusb844e992017-09-29 12:44:553056#pragma mark - PasswordControllerDelegate methods
3057
3058- (BOOL)displaySignInNotification:(UIViewController*)viewController
3059 fromTabId:(NSString*)tabId {
3060 // Check if the call comes from currently visible tab.
3061 if ([tabId isEqual:[_model currentTab].tabId]) {
3062 [self addChildViewController:viewController];
3063 [self.view addSubview:viewController.view];
3064 [viewController didMoveToParentViewController:self];
3065 return YES;
3066 } else {
3067 return NO;
3068 }
3069}
3070
sdefresnee65fd872016-12-19 13:38:133071#pragma mark - CRWWebStateDelegate methods.
3072
eugenebut75a06fa72017-01-09 17:09:553073- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:513074 createNewWebStateForURL:(const GURL&)URL
3075 openerURL:(const GURL&)openerURL
3076 initiatedByUser:(BOOL)initiatedByUser {
3077 // Check if requested web state is a popup and block it if necessary.
3078 if (!initiatedByUser) {
3079 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
3080 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:103081 // It's possible for a page to inject a popup into a window created via
3082 // window.open before its initial load is committed. Rather than relying
3083 // on the last committed or pending NavigationItem's referrer policy, just
3084 // use ReferrerPolicyDefault.
3085 // TODO(crbug.com/719993): Update this to a more appropriate referrer
3086 // policy once referrer policies are correctly recorded in
3087 // NavigationItems.
3088 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:513089 helper->HandlePopup(URL, referrer);
3090 return nil;
3091 }
3092 }
3093
3094 // Requested web state should not be blocked from opening.
3095 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
Sylvain Defresne17b8aa42017-12-21 16:17:173096 SnapshotTabHelper::FromWebState(currentTab.webState)
3097 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
eugenebut275f5892017-03-09 22:20:513098
3099 // Tabs open by DOM are always renderer initiated.
3100 web::NavigationManager::WebLoadParams params(GURL{});
3101 params.transition_type = ui::PAGE_TRANSITION_LINK;
3102 params.is_renderer_initiated = true;
3103 Tab* childTab = [[self tabModel]
3104 insertTabWithLoadParams:params
3105 opener:currentTab
3106 openedByDOM:YES
3107 atIndex:TabModelConstants::kTabPositionAutomatically
3108 inBackground:NO];
3109 return childTab.webState;
3110}
3111
eugenebutb46b2122017-03-14 02:43:263112- (void)closeWebState:(web::WebState*)webState {
3113 // Only allow a web page to close itself if it was opened by DOM, or if there
3114 // are no navigation items.
3115 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:583116 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:263117
3118 if (![self tabModel])
3119 return;
3120
3121 NSUInteger index = [[self tabModel] indexOfTab:tab];
3122 if (index != NSNotFound)
3123 [[self tabModel] closeTabAtIndex:index];
3124}
3125
eugenebut275f5892017-03-09 22:20:513126- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:553127 openURLWithParams:(const web::WebState::OpenURLParams&)params {
3128 switch (params.disposition) {
3129 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
3130 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
3131 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:353132 insertTabWithURL:params.url
3133 referrer:params.referrer
3134 transition:params.transition
3135 opener:LegacyTabHelper::GetTabForWebState(webState)
3136 openedByDOM:NO
3137 atIndex:TabModelConstants::kTabPositionAutomatically
3138 inBackground:(params.disposition ==
3139 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:553140 return tab.webState;
3141 }
3142 case WindowOpenDisposition::CURRENT_TAB: {
3143 web::NavigationManager::WebLoadParams loadParams(params.url);
3144 loadParams.referrer = params.referrer;
3145 loadParams.transition_type = params.transition;
3146 loadParams.is_renderer_initiated = params.is_renderer_initiated;
3147 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
3148 return webState;
3149 }
eugenebutd0984e82017-02-22 23:47:513150 case WindowOpenDisposition::NEW_POPUP: {
3151 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:353152 insertTabWithURL:params.url
3153 referrer:params.referrer
3154 transition:params.transition
3155 opener:LegacyTabHelper::GetTabForWebState(webState)
3156 openedByDOM:YES
3157 atIndex:TabModelConstants::kTabPositionAutomatically
3158 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:513159 return tab.webState;
3160 }
eugenebut75a06fa72017-01-09 17:09:553161 default:
3162 NOTIMPLEMENTED();
3163 return nullptr;
3164 };
3165}
3166
Mike Dougherty4e6b3a32017-08-23 18:49:213167- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:133168 handleContextMenu:(const web::ContextMenuParams&)params {
3169 // Prevent context menu from displaying for a tab which is no longer the
3170 // current one.
3171 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:213172 return;
sdefresnee65fd872016-12-19 13:38:133173 }
3174
3175 // No custom context menu if no valid url is available in |params|.
3176 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:213177 return;
sdefresnee65fd872016-12-19 13:38:133178 }
3179
3180 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:133181
stkhapuginc9eee7b2017-04-10 15:49:273182 _contextMenuCoordinator =
3183 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
3184 params:params];
sdefresnee65fd872016-12-19 13:38:133185
3186 NSString* title = nil;
3187 ProceduralBlock action = nil;
3188
stkhapuginc9eee7b2017-04-10 15:49:273189 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:133190 GURL link = params.link_url;
3191 bool isLink = link.is_valid();
3192 GURL imageUrl = params.src_url;
3193 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:193194 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:133195
3196 if (isLink) {
3197 if (link.SchemeIs(url::kJavaScriptScheme)) {
3198 // Open
3199 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
3200 action = ^{
3201 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
3202 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
3203 };
3204 [_contextMenuCoordinator addItemWithTitle:title action:action];
3205 }
3206
3207 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193208 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133209
sdefresnee65fd872016-12-19 13:38:133210 // Open in New Tab.
3211 title = l10n_util::GetNSStringWithFixup(
3212 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
3213 action = ^{
3214 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003215 // The "New Tab" item in the context menu opens a new tab in the current
3216 // browser state. |isOffTheRecord| indicates whether or not the current
3217 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:133218 [weakSelf webPageOrderedOpen:link
3219 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003220 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:133221 inBackground:YES
3222 appendTo:kCurrentTab];
3223 };
3224 [_contextMenuCoordinator addItemWithTitle:title action:action];
3225 if (!_isOffTheRecord) {
3226 // Open in Incognito Tab.
3227 title = l10n_util::GetNSStringWithFixup(
3228 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
3229 action = ^{
3230 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
3231 [weakSelf webPageOrderedOpen:link
3232 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133233 inIncognito:YES
3234 inBackground:NO
3235 appendTo:kCurrentTab];
3236 };
3237 [_contextMenuCoordinator addItemWithTitle:title action:action];
3238 }
olivierrobin51d4cf42017-01-17 13:32:353239 }
gambard65d69152017-03-23 17:44:223240 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:353241 NSString* innerText = params.link_text;
3242 if ([innerText length] > 0) {
3243 // Add to reading list.
3244 title = l10n_util::GetNSStringWithFixup(
3245 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
3246 action = ^{
3247 Record(ACTION_READ_LATER, isImage, isLink);
3248 [weakSelf addToReadingListURL:link title:innerText];
3249 };
3250 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:533251 }
sdefresnee65fd872016-12-19 13:38:133252 }
3253 // Copy Link.
3254 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
3255 action = ^{
3256 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:283257 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:133258 };
3259 [_contextMenuCoordinator addItemWithTitle:title action:action];
3260 }
3261 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193262 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133263 // Save Image.
gambard98b4ddf2017-04-18 07:14:053264 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:133265 action = ^{
3266 Record(ACTION_SAVE_IMAGE, isImage, isLink);
3267 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
3268 };
3269 [_contextMenuCoordinator addItemWithTitle:title action:action];
3270 // Open Image.
3271 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
3272 action = ^{
3273 Record(ACTION_OPEN_IMAGE, isImage, isLink);
3274 [weakSelf loadURL:imageUrl
3275 referrer:referrer
3276 transition:ui::PAGE_TRANSITION_LINK
3277 rendererInitiated:YES];
3278 };
3279 [_contextMenuCoordinator addItemWithTitle:title action:action];
3280 // Open Image In New Tab.
3281 title = l10n_util::GetNSStringWithFixup(
3282 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
3283 action = ^{
3284 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
3285 [weakSelf webPageOrderedOpen:imageUrl
3286 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133287 inBackground:true
3288 appendTo:kCurrentTab];
3289 };
3290 [_contextMenuCoordinator addItemWithTitle:title action:action];
3291
3292 TemplateURLService* service =
3293 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103294 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133295 if (defaultURL && !defaultURL->image_url().empty() &&
3296 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3297 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3298 defaultURL->short_name());
3299 action = ^{
3300 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3301 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3302 };
3303 [_contextMenuCoordinator addItemWithTitle:title action:action];
3304 }
3305 }
3306
3307 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133308}
3309
eugenebutb739bdc2017-01-25 06:32:483310- (void)webState:(web::WebState*)webState
3311 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3312 // Display the action sheet with the arrow pointing at the top center of the
3313 // web contents.
sdefresne0452a9d2017-02-09 15:33:283314 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483315 UIView* view = webState->GetView();
3316 CGPoint dialogLocation =
3317 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283318 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033319 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173320 helper->PresentDialog(dialogLocation,
3321 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053322 handler(shouldContinue);
3323 }));
eugenebutb739bdc2017-01-25 06:32:483324}
3325
sdefresnee65fd872016-12-19 13:38:133326- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3327 (web::WebState*)webState {
3328 return _javaScriptDialogPresenter.get();
3329}
3330
eugenebut63232102017-01-19 16:19:403331- (void)webState:(web::WebState*)webState
3332 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3333 proposedCredential:(NSURLCredential*)proposedCredential
3334 completionHandler:(void (^)(NSString* username,
3335 NSString* password))handler {
3336 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3337 proposedCredential:proposedCredential
3338 webState:webState
3339 completionHandler:handler];
3340}
3341
Mark Cogan776e0282018-01-02 09:00:063342#pragma mark - CRWWebStateDelegate helpers
3343
3344// Evaluates Javascript asynchronously using the current page context.
3345- (void)openJavascript:(NSString*)javascript {
3346 DCHECK(javascript);
3347 javascript = [javascript stringByRemovingPercentEncoding];
3348 web::WebState* webState = [[_model currentTab] webState];
3349 if (webState) {
3350 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
3351 }
3352}
3353
3354// Performs a search with the image at the given url. The referrer is used to
3355// download the image.
3356- (void)searchByImageAtURL:(const GURL&)url
3357 referrer:(const web::Referrer)referrer {
3358 DCHECK(url.is_valid());
3359 __weak BrowserViewController* weakSelf = self;
3360 const GURL image_source_url = url;
3361 image_fetcher::IOSImageDataFetcherCallback callback =
3362 ^(NSData* data, const image_fetcher::RequestMetadata& metadata) {
3363 DCHECK(data);
3364 dispatch_async(dispatch_get_main_queue(), ^{
3365 [weakSelf searchByImageData:data atURL:image_source_url];
3366 });
3367 };
3368 _imageFetcher->FetchImageDataWebpDecoded(
3369 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3370 web::PolicyForNavigation(url, referrer));
3371}
3372
3373// Performs a search using |data| and |imageURL| as inputs.
3374- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3375 NSData* imageData = data;
3376 UIImage* image = [UIImage imageWithData:imageData];
3377 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3378 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3379 // kSearchByImageMaxImageHeight).
3380 if (image &&
3381 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3382 (image.size.width > kSearchByImageMaxImageWidth ||
3383 image.size.height > kSearchByImageMaxImageHeight)) {
3384 CGSize newImageSize =
3385 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3386 image = [image gtm_imageByResizingToSize:newImageSize
3387 preserveAspectRatio:YES
3388 trimToFit:NO];
3389 imageData = UIImageJPEGRepresentation(image, 1.0);
3390 }
3391
3392 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3393 std::string byteString(bytes, [imageData length]);
3394
3395 TemplateURLService* templateUrlService =
3396 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
3397 const TemplateURL* defaultURL =
3398 templateUrlService->GetDefaultSearchProvider();
3399 DCHECK(!defaultURL->image_url().empty());
3400 DCHECK(defaultURL->image_url_ref().IsValid(
3401 templateUrlService->search_terms_data()));
3402 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3403 search_args.image_url = imageURL;
3404 search_args.image_thumbnail_content = byteString;
3405
3406 // Generate the URL and populate |post_content| with the content type and
3407 // HTTP body for the request.
3408 TemplateURLRef::PostContent post_content;
3409 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3410 search_args, templateUrlService->search_terms_data(), &post_content));
3411 [self addSelectedTabWithURL:result
3412 postData:&post_content
3413 transition:ui::PAGE_TRANSITION_TYPED];
3414}
3415
3416// Saves the image at the given URL on the system's album. The referrer is used
3417// to download the image.
3418- (void)saveImageAtURL:(const GURL&)url
3419 referrer:(const web::Referrer&)referrer {
3420 DCHECK(url.is_valid());
3421
Gauthier Ambard929699412018-01-02 10:05:413422 image_fetcher::IOSImageDataFetcherCallback callback =
3423 ^(NSData* data, const image_fetcher::RequestMetadata& metadata) {
3424 [self.imageSaver saveImageData:data withMetadata:metadata];
3425 };
Mark Cogan776e0282018-01-02 09:00:063426 _imageFetcher->FetchImageDataWebpDecoded(
3427 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3428 web::PolicyForNavigation(url, referrer));
3429}
3430
Kurt Horimoto62e97c72017-11-03 19:51:473431#pragma mark - LegacyFullscreenControllerDelegate methods
sdefresnee65fd872016-12-19 13:38:133432
Mark Cogan849244ee2017-12-29 15:57:193433// TODO(crbug.com/798064): Remove these methods and their helpers once the
3434// fullscreen migration is complete.
[email protected]a08e2bfb2017-11-24 19:16:453435- (void)redrawHeader {
3436 for (HeaderDefinition* header in self.headerViews) {
3437 [header.view setNeedsLayout];
3438 }
3439}
3440
Mark Cogan849244ee2017-12-29 15:57:193441- (CGFloat)headerHeightForLegacyFullscreen {
3442 return self.headerHeight;
sdefresnee65fd872016-12-19 13:38:133443}
3444
3445- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193446 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133447}
3448
3449- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453450 NSArray<HeaderDefinition*>* headers = [self headerViews];
3451 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133452 return 0.0;
3453
3454 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3455 // API documentation.
Sylvain Defresnef5d2d952017-11-14 11:15:313456 if (_insertedTabWasPrerenderedTab)
Mark Cogan849244ee2017-12-29 15:57:193457 return self.headerHeight;
sdefresnee65fd872016-12-19 13:38:133458
3459 UIView* topHeader = headers[0].view;
Mark Cogan849244ee2017-12-29 15:57:193460 return -(topHeader.frame.origin.y - self.headerOffset);
sdefresnee65fd872016-12-19 13:38:133461}
3462
Kurt Horimoto62e97c72017-11-03 19:51:473463- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133464 drawHeaderViewFromOffset:(CGFloat)headerOffset
3465 animate:(BOOL)animate {
Mark Cogan5bd86ba2017-12-28 14:32:383466 if ([self.sideSwipeController inSwipe])
sdefresnee65fd872016-12-19 13:38:133467 return;
3468
3469 CGRect footerFrame = CGRectZero;
3470 UIView* footer = nil;
3471 // Only animate the voice search bar if the tab is a voice search results tab.
3472 if ([_model currentTab].isVoiceSearchResultsTab) {
3473 footer = [self footerView];
3474 footerFrame = footer.frame;
3475 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3476 }
3477
stkhapugin952ecef2017-04-11 12:11:453478 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133479 void (^block)(void) = ^{
3480 [self setFramesForHeaders:headers atOffset:headerOffset];
3481 footer.frame = footerFrame;
3482 };
3483 void (^completion)(BOOL) = ^(BOOL finished) {
3484 [self fullScreenController:fullScreenController
3485 headerAnimationCompleted:finished
3486 offset:headerOffset];
3487 };
3488 if (animate) {
Kurt Horimoto62e97c72017-11-03 19:51:473489 [UIView
3490 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3491 delay:0.0
3492 options:UIViewAnimationOptionBeginFromCurrentState
3493 animations:block
3494 completion:completion];
sdefresnee65fd872016-12-19 13:38:133495 } else {
3496 block();
3497 completion(YES);
3498 }
3499}
3500
Kurt Horimoto62e97c72017-11-03 19:51:473501- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133502 drawHeaderViewFromOffset:(CGFloat)headerOffset
3503 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3504 changeTopContentPadding:(BOOL)changeTopContentPadding
3505 scrollingToOffset:(CGFloat)contentOffset {
3506 DCHECK(webViewProxy);
Mark Cogan5bd86ba2017-12-28 14:32:383507 if ([self.sideSwipeController inSwipe])
sdefresnee65fd872016-12-19 13:38:133508 return;
3509
3510 CGRect footerFrame;
3511 UIView* footer = nil;
3512 // Only animate the voice search bar if the tab is a voice search results tab.
3513 if ([_model currentTab].isVoiceSearchResultsTab) {
3514 footer = [self footerView];
3515 footerFrame = footer.frame;
3516 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3517 }
3518
stkhapugin952ecef2017-04-11 12:11:453519 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133520 void (^block)(void) = ^{
3521 [self setFramesForHeaders:headers atOffset:headerOffset];
3522 footer.frame = footerFrame;
3523 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3524 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3525 if (changeTopContentPadding)
3526 webViewProxy.topContentPadding = contentOffset;
3527 };
3528 void (^completion)(BOOL) = ^(BOOL finished) {
3529 [self fullScreenController:fullScreenController
3530 headerAnimationCompleted:finished
3531 offset:headerOffset];
3532 };
3533
Kurt Horimoto62e97c72017-11-03 19:51:473534 [UIView
3535 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3536 delay:0.0
3537 options:UIViewAnimationOptionBeginFromCurrentState
3538 animations:block
3539 completion:completion];
sdefresnee65fd872016-12-19 13:38:133540}
3541
Mark Cogan849244ee2017-12-29 15:57:193542#pragma mark - LegacyFullscreenControllerDelegate helpers
sdefresnee65fd872016-12-19 13:38:133543
Mark Cogan849244ee2017-12-29 15:57:193544// Returns the y coordinate for the footer's frame when animating the footer
3545// in/out of fullscreen.
3546- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3547 UIView* footer = [self footerView];
3548 CGFloat headerHeight = [self headerHeight];
3549 if (!footer || headerHeight == 0)
3550 return 0.0;
3551
3552 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3553 CGFloat offset = headerOffset * footerHeight / headerHeight;
3554 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3555}
3556
3557// Called when the animation for setting the header view's offset is finished.
3558// |completed| should indicate if the animation finished completely or was
3559// interrupted. |offset| should indicate the header offset after the animation.
3560// |dragged| should indicate if the header moved due to the user dragging.
3561- (void)fullScreenController:(LegacyFullscreenController*)controller
3562 headerAnimationCompleted:(BOOL)completed
3563 offset:(CGFloat)offset {
3564 if (completed)
3565 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133566}
3567
sdefresnee65fd872016-12-19 13:38:133568#pragma mark - OverscrollActionsControllerDelegate methods.
3569
3570- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053571 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133572 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053573 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383574 [self.dispatcher
3575 openNewTab:[OpenNewTabCommand
3576 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133577 break;
rohitrao922b7111c2017-01-03 14:31:053578 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243579 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133580 break;
Kurt Horimoto4ce19322017-11-28 19:10:473581 case OverscrollAction::REFRESH:
3582 [self reload];
sdefresnee65fd872016-12-19 13:38:133583 break;
rohitrao922b7111c2017-01-03 14:31:053584 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133585 NOTREACHED();
3586 break;
3587 }
3588}
3589
3590- (BOOL)shouldAllowOverscrollActions {
3591 return YES;
3592}
3593
3594- (UIView*)headerView {
sczs42f7f7482017-11-08 01:13:273595 return _toolbarCoordinator.toolbarViewController.view;
sdefresnee65fd872016-12-19 13:38:133596}
3597
3598- (UIView*)toolbarSnapshotView {
sczs42f7f7482017-11-08 01:13:273599 return [_toolbarCoordinator.toolbarViewController.view
3600 snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133601}
3602
3603- (CGFloat)overscrollActionsControllerHeaderInset:
3604 (OverscrollActionsController*)controller {
3605 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
Mark Cogan849244ee2017-12-29 15:57:193606 return self.headerHeight;
sdefresnee65fd872016-12-19 13:38:133607 else
3608 return 0;
3609}
3610
3611- (CGFloat)overscrollHeaderHeight {
Mark Cogan849244ee2017-12-29 15:57:193612 return self.headerHeight + StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:133613}
3614
3615#pragma mark - TabSnapshottingDelegate methods.
3616
Sylvain Defresne0a86dd22017-12-19 14:37:503617- (UIEdgeInsets)snapshotEdgeInsetsForTab:(Tab*)tab {
sdefresnee65fd872016-12-19 13:38:133618 CGFloat headerHeight = [self headerHeightForTab:tab];
3619 id nativeController = [self nativeControllerForTab:tab];
3620 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3621 headerHeight += [nativeController toolbarHeight];
Sylvain Defresne0a86dd22017-12-19 14:37:503622 return UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
sdefresnee65fd872016-12-19 13:38:133623}
3624
Mark Cogan849244ee2017-12-29 15:57:193625#pragma mark - NewTabPageControllerObserver methods.
sdefresnee65fd872016-12-19 13:38:133626
3627- (void)selectedPanelDidChange {
3628 [self updateToolbar];
3629}
3630
3631#pragma mark - CRWNativeContentProvider methods
3632
Mark Cogan849244ee2017-12-29 15:57:193633// TODO(crbug.com/725241): This method is deprecated and should be removed by
3634// switching to DidFinishnavigation.
sdefresnee65fd872016-12-19 13:38:133635- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3636 withError:(NSError*)error
3637 isPost:(BOOL)isPost {
3638 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173639 [[ErrorPageContent alloc] initWithLoader:self
3640 browserState:self.browserState
3641 url:url
3642 error:error
3643 isPost:isPost
3644 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133645 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3646 return errorPageContent;
3647}
3648
3649- (BOOL)hasControllerForURL:(const GURL&)url {
Marti Wong64481ec2017-10-31 03:38:003650 base::StringPiece host = url.host_piece();
olivierrobin5c861c22017-04-07 15:56:453651 if (host == kChromeUIOfflineHost) {
3652 // Only allow offline URL that are fully specified.
3653 return reading_list::IsOfflineURLValid(
3654 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3655 }
sdefresnee65fd872016-12-19 13:38:133656
Justin Cohen8679e852017-08-14 16:35:253657 if (host == kChromeUIBookmarksHost) {
Marti Wong64481ec2017-10-31 03:38:003658 return IsBookmarksHostEnabled();
Justin Cohen8679e852017-08-14 16:35:253659 }
3660
3661 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133662}
3663
olivierrobind43eecb2017-01-27 20:35:263664- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3665 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133666 DCHECK(url.SchemeIs(kChromeUIScheme));
3667
3668 id<CRWNativeContent> nativeController = nil;
Marti Wong64481ec2017-10-31 03:38:003669 base::StringPiece url_host = url.host_piece();
Justin Cohen49715952017-08-22 14:12:193670 if (url_host == kChromeUINewTabHost ||
Marti Wong64481ec2017-10-31 03:38:003671 (url_host == kChromeUIBookmarksHost && IsBookmarksHostEnabled())) {
Gauthier Ambardd8890452017-09-29 12:07:463672 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3673 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3674 if (@available(iOS 11.0, *)) {
3675 safeAreaInset = self.view.safeAreaInsets;
3676 }
3677 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3678
sdefresnee65fd872016-12-19 13:38:133679 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173680 [[NewTabPageController alloc] initWithUrl:url
3681 loader:self
sczsf1620e52017-10-02 22:54:463682 focuser:_toolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173683 ntpObserver:self
3684 browserState:_browserState
3685 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463686 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453687 tabModel:_model
justincohen75011c32017-04-28 16:31:393688 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463689 dispatcher:self.dispatcher
3690 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133691 pageController.swipeRecognizerProvider = self.sideSwipeController;
3692
3693 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233694 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133695
Marti Wong64481ec2017-10-31 03:38:003696 if (IsBookmarksHostEnabled()) {
sdefresnee65fd872016-12-19 13:38:133697 // New Tab Page can have multiple panels. Each panel is addressable
3698 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3699 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3700 // the Bookmark Manager, etc.
3701 // The utility functions NewTabPage::IdentifierFromFragment() and
3702 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3703 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3704 // without changing the URL since the URL may be chrome://bookmarks/#123.
3705 // If the URL is chrome://newtab/, pre-select the panel based on the
3706 // #fragment.
3707 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233708 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133709 : NewTabPage::IdentifierFromFragment(url.ref());
3710 }
3711 [pageController selectPanel:panelType];
3712 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453713 } else if (url_host == kChromeUIOfflineHost &&
3714 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133715 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173716 [[OfflinePageNativeContent alloc] initWithLoader:self
3717 browserState:_browserState
3718 webState:webState
3719 URL:url];
sdefresnee65fd872016-12-19 13:38:133720 [self setOverScrollActionControllerToStaticNativeContent:
3721 staticNativeController];
3722 nativeController = staticNativeController;
3723 } else if (url_host == kChromeUIExternalFileHost) {
3724 // Return an instance of the |ExternalFileController| only if the file is
3725 // still in the sandbox.
3726 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3727 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173728 nativeController =
3729 [[ExternalFileController alloc] initWithURL:url
3730 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133731 }
peterlaurens44615d02017-05-23 20:23:093732 } else if (url_host == kChromeUICrashHost) {
3733 // There is no native controller for kChromeUICrashHost, it is instead
3734 // handled as any other renderer crash by the SadTabTabHelper.
3735 // nativeController must be set to nil to prevent defaulting to a
3736 // PageNotAvailableController.
3737 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133738 } else {
3739 DCHECK(![self hasControllerForURL:url]);
3740 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173741 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133742 }
3743 // If a native controller is vended before its tab is added to the tab model,
3744 // use the temporary key and add it under the new tab's tabId in the
3745 // TabModelObserver callback. This happens:
3746 // - when there is no current tab (occurs when vending the NTP controller for
3747 // the first tab that is opened),
3748 // - when the current tab's url doesn't match |url| (occurs when a native
3749 // controller is opened in a new tab)
3750 // - when the current tab's url matches |url| and there is already a native
3751 // controller of the appropriate type vended to it (occurs when a native
3752 // controller is opened in a new tab from a tab with a matching URL, e.g.
3753 // opening an NTP when an NTP is already displayed in the current tab).
3754 // For normal page loads, history navigations, tab restorations, and crash
3755 // recoveries, the tab will already exist in the tab model and the tabId can
3756 // be used as the native controller key.
3757 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3758 // that native controllers vended here always correspond to the current tab.
3759 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193760 if (!currentTab.webState ||
3761 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443762 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133763 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443764 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133765 }
sdefresnee65fd872016-12-19 13:38:133766 return nativeController;
3767}
3768
edchin2c6af4a2017-12-05 02:16:493769- (id<CRWNativeContent>)controllerForUnhandledContentAtURL:(const GURL&)URL
3770 webState:
3771 (web::WebState*)webState {
Eugene But87a09532017-12-08 20:02:053772 LegacyDownloadManagerController* downloadController =
3773 [[LegacyDownloadManagerController alloc] initWithWebState:webState
edchin2c6af4a2017-12-05 02:16:493774 downloadURL:URL
3775 baseViewController:self];
3776 [downloadController start];
3777 return downloadController;
3778}
3779
sdefresnee65fd872016-12-19 13:38:133780#pragma mark - DialogPresenterDelegate methods
3781
3782- (void)dialogPresenter:(DialogPresenter*)presenter
3783 willShowDialogForWebState:(web::WebState*)webState {
3784 for (Tab* iteratedTab in self.tabModel) {
3785 if ([iteratedTab webState] == webState) {
3786 self.tabModel.currentTab = iteratedTab;
3787 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3788 break;
3789 }
3790 }
3791}
3792
Kurt Horimoto06b94252017-12-08 19:45:593793#pragma mark - FullscreenUIElement methods
3794
3795- (void)updateForFullscreenProgress:(CGFloat)progress {
3796 [self updateHeadersForFullscreenProgress:progress];
3797 [self updateFootersForFullscreenProgress:progress];
3798 [self updateContentViewTopPaddingForFullscreenProgress:progress];
3799}
3800
3801- (void)updateForFullscreenEnabled:(BOOL)enabled {
3802 if (!enabled)
3803 [self updateForFullscreenProgress:1.0];
3804}
3805
3806- (void)finishFullscreenScrollWithAnimator:
3807 (FullscreenScrollEndAnimator*)animator {
3808 BOOL showingToolbar = animator.finalProgress > animator.startProgress;
3809 CGFloat finalProgress = animator.finalProgress;
3810 // WKWebView does not re-render its content until its model layer's bounds
3811 // have been updated at the end of the animation. If the animator is going
3812 // to hide the toolbar, update the content view's top padding early so that
3813 // content is correctly rendered behind the toolbar that's being animated
3814 // away.
3815 if (!showingToolbar)
3816 [self updateContentViewTopPaddingForFullscreenProgress:finalProgress];
3817 [animator addAnimations:^{
3818 [self updateHeadersForFullscreenProgress:finalProgress];
3819 [self updateFootersForFullscreenProgress:finalProgress];
3820 }];
3821 // If the toolbar is being animated to become visible, update the content view
3822 // top padding in the completion block so that fixed-position elements can be
3823 // properly laid out in the new viewport.
3824 if (showingToolbar) {
3825 __weak FullscreenScrollEndAnimator* weakAnimator = animator;
3826 [animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
3827 [self updateContentViewTopPaddingForFullscreenProgress:
3828 [weakAnimator progressForAnimatingPosition:finalPosition]];
3829 }];
3830 }
3831}
3832
3833#pragma mark - FullscreenUIElement helpers
3834
3835// Translates the header views up and down according to |progress|, where a
3836// progress of 1.0 fully shows the headers and a progress of 0.0 fully hides
3837// them.
3838- (void)updateHeadersForFullscreenProgress:(CGFloat)progress {
3839 [self setFramesForHeaders:[self headerViews]
3840 atOffset:(1.0 - progress) * [self toolbarHeight]];
3841}
3842
3843// Translates the footer view up and down according to |progress|, where a
3844// progress of 1.0 fully shows the footer and a progress of 0.0 fully hides it.
3845- (void)updateFootersForFullscreenProgress:(CGFloat)progress {
3846 if (![_model currentTab].isVoiceSearchResultsTab)
3847 return;
3848
3849 UIView* footerView = [self footerView];
3850 DCHECK(footerView);
3851 CGRect frame = footerView.frame;
3852 frame.origin.y = CGRectGetMaxY(footerView.superview.bounds) -
3853 progress * CGRectGetHeight(frame);
3854 footerView.frame = frame;
3855}
3856
3857// Updates the top padding of the web view proxy. This either resets the frame
3858// of the WKWebView or the contentInsets of the WKWebView's UIScrollView,
3859// depending on the the proxy's |shouldUseInsetForTopPadding| property.
3860- (void)updateContentViewTopPaddingForFullscreenProgress:(CGFloat)progress {
3861 if (self.currentWebState) {
3862 self.currentWebState->GetWebViewProxy().topContentPadding =
3863 progress * [self toolbarHeight];
3864 }
3865}
3866
sdefresnee65fd872016-12-19 13:38:133867#pragma mark - KeyCommandsPlumbing
3868
3869- (BOOL)isOffTheRecord {
3870 return _isOffTheRecord;
3871}
3872
3873- (NSUInteger)tabsCount {
3874 return [_model count];
3875}
3876
lpromero47ea8862017-01-13 17:51:063877- (BOOL)canGoBack {
3878 return [_model currentTab].canGoBack;
3879}
3880
3881- (BOOL)canGoForward {
3882 return [_model currentTab].canGoForward;
3883}
3884
sdefresnee65fd872016-12-19 13:38:133885- (void)focusTabAtIndex:(NSUInteger)index {
3886 if ([_model count] > index) {
3887 [_model setCurrentTab:[_model tabAtIndex:index]];
3888 }
3889}
3890
3891- (void)focusNextTab {
3892 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3893 NSInteger modelCount = [_model count];
3894 if (currentTabIndex < modelCount - 1) {
3895 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3896 [_model setCurrentTab:nextTab];
3897 } else {
3898 [_model setCurrentTab:[_model tabAtIndex:0]];
3899 }
3900}
3901
3902- (void)focusPreviousTab {
3903 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3904 if (currentTabIndex > 0) {
3905 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3906 [_model setCurrentTab:previousTab];
3907 } else {
3908 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3909 [_model setCurrentTab:lastTab];
3910 }
3911}
3912
3913- (void)reopenClosedTab {
3914 sessions::TabRestoreService* const tabRestoreService =
3915 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3916 if (!tabRestoreService || tabRestoreService->entries().empty())
3917 return;
3918
3919 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3920 tabRestoreService->entries().front();
3921 // Only handle the TAB type.
3922 if (entry->type != sessions::TabRestoreService::TAB)
3923 return;
3924
Mark Cogandfcdea72017-07-18 13:47:383925 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133926 TabRestoreServiceDelegateImplIOS* const delegate =
3927 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3928 _browserState);
3929 tabRestoreService->RestoreEntryById(delegate, entry->id,
3930 WindowOpenDisposition::CURRENT_TAB);
3931}
3932
Kurt Horimotoe9b6002c2017-12-04 23:19:193933#pragma mark - MainContentUI
3934
3935- (MainContentUIState*)mainContentUIState {
3936 return _mainContentUIUpdater.state;
3937}
3938
Mark Cogan776e0282018-01-02 09:00:063939#pragma mark - UrlLoader (Public)
Mark Cogan5bd86ba2017-12-28 14:32:383940
sdefresnee65fd872016-12-19 13:38:133941- (void)loadURL:(const GURL&)url
3942 referrer:(const web::Referrer&)referrer
3943 transition:(ui::PageTransition)transition
3944 rendererInitiated:(BOOL)rendererInitiated {
3945 [[OmniboxGeolocationController sharedInstance]
3946 locationBarDidSubmitURL:url
3947 transition:transition
3948 browserState:_browserState];
3949
3950 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3951 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3952 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3953 }
3954
3955 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243956 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133957 // restart.
3958 if (url.host() == kChromeUIBrowserCrashHost) {
3959 [self induceBrowserCrash];
3960 // In debug the app can continue working even after the CHECK. Adding a
3961 // return avoids the crash url to be added to the history.
3962 return;
3963 }
3964
Danyao Wang85389a82017-10-25 18:56:273965 bool typed_or_generated_transition =
3966 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
3967 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
3968
Rohit Rao44f204302017-08-10 14:49:543969 PrerenderService* prerenderService =
3970 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3971 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593972 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543973 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593974 DCHECK(newWebState);
3975
sdefresnee65fd872016-12-19 13:38:133976 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593977 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133978 DCHECK(oldTab);
3979 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593980
kkhorimotod804c5732017-03-15 23:44:523981 bool canPruneItems =
3982 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593983
Sylvain Defresne17b8aa42017-12-21 16:17:173984 if (canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523985 [newTab navigationManager]->CopyStateFromAndPrune(
3986 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593987
Sylvain Defresnef5d2d952017-11-14 11:15:313988 // Set _insertedTabWasPrerenderedTab to YES while the Tab is inserted
3989 // so that the correct toolbar height is used and animation are played.
3990 _insertedTabWasPrerenderedTab = YES;
sdefresne2c600c52017-04-04 16:49:593991 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3992 std::move(newWebState));
Sylvain Defresnef5d2d952017-11-14 11:15:313993 _insertedTabWasPrerenderedTab = NO;
sdefresnee65fd872016-12-19 13:38:133994
Sylvain Defresne17b8aa42017-12-21 16:17:173995 if ([newTab loadFinished]) {
3996 // If the page has finished loading, take a snapshot. If the page is
3997 // still loading, do nothing, as the tab helper will automatically take
3998 // a snapshot once the load completes.
3999 SnapshotTabHelper::FromWebState(newTab.webState)
4000 ->UpdateSnapshot(
4001 /*with_overlays=*/true, /*visible_frame_only=*/true);
4002 }
4003
Danyao Wang85389a82017-10-25 18:56:274004 if (typed_or_generated_transition) {
4005 LoadTimingTabHelper::FromWebState(newTab.webState)
4006 ->DidPromotePrerenderTab();
4007 }
sdefresnee65fd872016-12-19 13:38:134008
sdefresne2f7781c2017-03-02 19:12:464009 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:134010 return;
4011 }
4012 }
4013
4014 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:544015 if (prerenderService) {
4016 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:134017 }
4018
sdefresnee65fd872016-12-19 13:38:134019 // Some URLs are not allowed while in incognito. If we are in incognito and
4020 // load a disallowed URL, instead create a new tab not in the incognito state.
4021 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
4022 [self webPageOrderedOpen:url
4023 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134024 inIncognito:NO
4025 inBackground:NO
4026 appendTo:kCurrentTab];
4027 return;
4028 }
4029
Danyao Wang85389a82017-10-25 18:56:274030 if (typed_or_generated_transition) {
4031 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
4032 ->DidInitiatePageLoad();
4033 }
4034
mrefaata84d5a02017-06-08 17:13:294035 // If this is a reload initiated from the omnibox.
4036 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
4037 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
4038 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
4039 [[_model currentTab] navigationManager]->Reload(
4040 web::ReloadType::NORMAL, true /* check_for_repost */);
4041 return;
4042 }
4043
sdefresnee65fd872016-12-19 13:38:134044 web::NavigationManager::WebLoadParams params(urlToLoad);
4045 params.referrer = referrer;
4046 params.transition_type = transition;
4047 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104048 Tab* currentTab = [_model currentTab];
4049 DCHECK(currentTab);
4050 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4051 currentTab.navigationManager->LoadURLWithParams(params);
4052 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4053 // to be animated on screen because the transition animator will handle the
4054 // animations. When a Tab stops being a voice search Tab, the voice search
4055 // bar should be animated away.
4056 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4057 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134058}
4059
4060- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544061 PrerenderService* prerenderService =
4062 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4063 if (prerenderService) {
4064 prerenderService->CancelPrerender();
4065 }
sdefresnee65fd872016-12-19 13:38:134066 DCHECK([_model currentTab]);
Mark Cogan776e0282018-01-02 09:00:064067 if (self.currentWebState)
4068 self.currentWebState->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134069}
4070
sdefresnee65fd872016-12-19 13:38:134071// Load a new URL on a new page/tab.
4072- (void)webPageOrderedOpen:(const GURL&)URL
4073 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134074 inBackground:(BOOL)inBackground
4075 appendTo:(OpenPosition)appendTo {
4076 Tab* adjacentTab = nil;
4077 if (appendTo == kCurrentTab)
4078 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354079 [_model insertTabWithURL:URL
4080 referrer:referrer
4081 transition:ui::PAGE_TRANSITION_LINK
4082 opener:adjacentTab
4083 openedByDOM:NO
4084 atIndex:TabModelConstants::kTabPositionAutomatically
4085 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134086}
4087
4088- (void)webPageOrderedOpen:(const GURL&)url
4089 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134090 inIncognito:(BOOL)inIncognito
4091 inBackground:(BOOL)inBackground
4092 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004093 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234094 // feature_engagement::Tracker based on |inIncognito|.
4095 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004096
sdefresnee65fd872016-12-19 13:38:134097 if (inIncognito == _isOffTheRecord) {
4098 [self webPageOrderedOpen:url
4099 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134100 inBackground:inBackground
4101 appendTo:appendTo];
4102 return;
4103 }
4104 // When sending an open command that switches modes, ensure the tab
4105 // ends up appended to the end of the model, not just next to what is
4106 // currently selected in the other mode. This is done with the |append|
4107 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274108 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134109 initWithURL:url
4110 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134111 inIncognito:inIncognito
4112 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274113 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154114 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134115}
4116
4117- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054118 WebStateList* webStateList = [_model webStateList];
4119 webStateList->ReplaceWebStateAt(
4120 webStateList->active_index(),
4121 session_util::CreateWebStateWithNavigationEntries(
4122 [_model browserState], sessionTab->current_navigation_index,
4123 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134124}
4125
Mark Cogan776e0282018-01-02 09:00:064126#pragma mark - UrlLoader helpers
4127
4128// Induce an intentional crash in the browser process.
4129- (void)induceBrowserCrash {
4130 CHECK(false);
4131 // Call another function, so that the above CHECK can't be tail-call
4132 // optimized. This ensures that this method's name will show up in the stack
4133 // for easier identification.
4134 CHECK(true);
sdefresnee65fd872016-12-19 13:38:134135}
4136
Mark Cogan776e0282018-01-02 09:00:064137#pragma mark - WebToolbarDelegate (Public)
sdefresnee65fd872016-12-19 13:38:134138
Gauthier Ambard45963ce22017-11-17 15:49:114139- (void)locationBarDidBecomeFirstResponder {
sdefresnee65fd872016-12-19 13:38:134140 if (_locationBarHasFocus)
4141 return; // TODO(crbug.com/244366): This should not be necessary.
4142 _locationBarHasFocus = YES;
4143 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524144 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134145 object:nil];
Mark Cogan5bd86ba2017-12-28 14:32:384146 [self.sideSwipeController setEnabled:NO];
sdefresnee65fd872016-12-19 13:38:134147 if ([[_model currentTab].webController wantsKeyboardShield]) {
Mark Cogan5bd86ba2017-12-28 14:32:384148 [[self view] insertSubview:self.typingShield aboveSubview:_contentArea];
4149 [self.typingShield setAlpha:0.0];
4150 [self.typingShield setHidden:NO];
sdefresnee65fd872016-12-19 13:38:134151 [UIView animateWithDuration:0.3
4152 animations:^{
Mark Cogan5bd86ba2017-12-28 14:32:384153 [self.typingShield setAlpha:1.0];
sdefresnee65fd872016-12-19 13:38:134154 }];
4155 }
4156 [[OmniboxGeolocationController sharedInstance]
4157 locationBarDidBecomeFirstResponder:_browserState];
4158}
4159
Gauthier Ambard45963ce22017-11-17 15:49:114160- (void)locationBarDidResignFirstResponder {
sdefresnee65fd872016-12-19 13:38:134161 if (!_locationBarHasFocus)
4162 return; // TODO(crbug.com/244366): This should not be necessary.
4163 _locationBarHasFocus = NO;
Mark Cogan5bd86ba2017-12-28 14:32:384164 [self.sideSwipeController setEnabled:YES];
sdefresnee65fd872016-12-19 13:38:134165 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524166 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134167 object:nil];
4168 [UIView animateWithDuration:0.3
4169 animations:^{
Mark Cogan5bd86ba2017-12-28 14:32:384170 [self.typingShield setAlpha:0.0];
sdefresnee65fd872016-12-19 13:38:134171 }
4172 completion:^(BOOL finished) {
4173 // This can happen if one quickly resigns the omnibox and then taps
4174 // on the omnibox again during this animation. If the animation is
4175 // interrupted and the toolbar controller is first responder, it's safe
Mark Cogan5bd86ba2017-12-28 14:32:384176 // to assume |self.typingShield| shouldn't be hidden here.
sczsf1620e52017-10-02 22:54:464177 if (!finished && [_toolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134178 return;
Mark Cogan5bd86ba2017-12-28 14:32:384179 [self.typingShield setHidden:YES];
sdefresnee65fd872016-12-19 13:38:134180 }];
4181 [[OmniboxGeolocationController sharedInstance]
4182 locationBarDidResignFirstResponder:_browserState];
4183
4184 // If a load was cancelled by an omnibox edit, but nothing is loading when
4185 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4186 if (_locationBarEditCancelledLoad) {
4187 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294188
4189 web::WebState* webState = [_model currentTab].webState;
4190 if (!_toolbarModelIOS->IsLoading() && webState)
4191 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4192 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134193 }
4194}
4195
Gauthier Ambard45963ce22017-11-17 15:49:114196- (void)locationBarBeganEdit {
sdefresnee65fd872016-12-19 13:38:134197 // On handsets, if a page is currently loading it should be stopped.
4198 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354199 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134200 _locationBarEditCancelledLoad = YES;
4201 }
4202}
4203
sdefresnee65fd872016-12-19 13:38:134204- (ToolbarModelIOS*)toolbarModelIOS {
4205 return _toolbarModelIOS.get();
4206}
4207
sdefresnee65fd872016-12-19 13:38:134208
Mark Cogan849244ee2017-12-29 15:57:194209#pragma mark - ToolsMenuConfigurationProvider
Peter Laurense0b80f12017-11-21 07:52:404210
4211- (void)prepareForToolsMenuPresentationByCoordinator:
4212 (ToolsMenuCoordinator*)coordinator {
4213 DCHECK(_browserState);
4214 DCHECK(self.visible || self.dismissingModal);
4215
4216 // Dismiss the omnibox (if open).
4217 [_toolbarCoordinator cancelOmniboxEdit];
4218 // Dismiss the soft keyboard (if open).
4219 [[_model currentTab].webController dismissKeyboard];
4220 // Dismiss Find in Page focus.
4221 [self updateFindBar:NO shouldFocus:NO];
4222
4223 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4224 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4225 }
4226}
4227
4228- (ToolsMenuConfiguration*)menuConfigurationForToolsMenuCoordinator:
4229 (ToolsMenuCoordinator*)coordinator {
4230 ToolsMenuConfiguration* configuration =
4231 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4232 baseViewController:self];
4233 configuration.requestStartTime = [NSDate date].timeIntervalSinceReferenceDate;
4234
4235 if ([_model count] == 0)
4236 [configuration setNoOpenedTabs:YES];
4237
4238 if (_isOffTheRecord)
4239 [configuration setInIncognito:YES];
4240
4241 if (!_readingListMenuNotifier) {
4242 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4243 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4244 _browserState)];
4245 }
4246
4247 feature_engagement::Tracker* engagementTracker =
4248 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4249 if (engagementTracker->ShouldTriggerHelpUI(
4250 feature_engagement::kIPHBadgedReadingListFeature)) {
4251 [configuration setShowReadingListNewBadge:YES];
4252 [configuration setEngagementTracker:engagementTracker];
4253 }
4254 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4255
4256 [configuration setUserAgentType:self.userAgentType];
4257
4258 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4259 [configuration setHighlightNewIncognitoTabCell:YES];
4260 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4261 }
4262
4263 return configuration;
4264}
4265
4266- (BOOL)shouldHighlightBookmarkButtonForToolsMenuCoordinator:
4267 (ToolsMenuCoordinator*)coordinator {
4268 return [_model currentTab] ? _toolbarModelIOS->IsCurrentTabBookmarked() : NO;
4269}
4270
4271- (BOOL)shouldShowFindBarForToolsMenuCoordinator:
4272 (ToolsMenuCoordinator*)coordinator {
4273 return [_model currentTab] ? self.canShowFindBar : NO;
4274}
4275
4276- (BOOL)shouldShowShareMenuForToolsMenuCoordinator:
4277 (ToolsMenuCoordinator*)coordinator {
4278 return [_model currentTab] ? self.canShowShareMenu : NO;
4279}
4280
4281- (BOOL)isTabLoadingForToolsMenuCoordinator:(ToolsMenuCoordinator*)coordinator {
4282 return ([_model currentTab] && !IsIPadIdiom()) ? _toolbarModelIOS->IsLoading()
4283 : NO;
4284}
4285
Mark Cogan6ebbde02017-07-07 12:50:134286#pragma mark - BrowserCommands
4287
4288- (void)goBack {
4289 [[_model currentTab] goBack];
4290}
4291
4292- (void)goForward {
4293 [[_model currentTab] goForward];
4294}
4295
Mark Coganb9aac6432017-07-07 13:26:354296- (void)stopLoading {
4297 [_model currentTab].webState->Stop();
4298}
4299
4300- (void)reload {
4301 web::WebState* webState = [_model currentTab].webState;
4302 if (webState) {
4303 // |check_for_repost| is true because the reload is explicitly initiated
4304 // by the user.
4305 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4306 true /* check_for_repost */);
4307 }
4308}
4309
Mark Cogan8e791022017-07-10 09:55:354310- (void)bookmarkPage {
4311 [self initializeBookmarkInteractionController];
4312 [_bookmarkInteractionController
4313 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064314 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354315}
4316
Mark Cogandfcdea72017-07-18 13:47:384317- (void)openNewTab:(OpenNewTabCommand*)command {
4318 if (self.isOffTheRecord != command.incognito) {
edchin3ab78ff2017-11-13 19:13:144319 // Must take a snapshot of the tab before we switch the incognito mode
4320 // because the currentTab will change after the switch.
4321 Tab* currentTab = [_model currentTab];
4322 if (currentTab) {
Sylvain Defresne17b8aa42017-12-21 16:17:174323 SnapshotTabHelper::FromWebState(currentTab.webState)
4324 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
edchin3ab78ff2017-11-13 19:13:144325 }
Mark Cogandfcdea72017-07-18 13:47:384326 // Not for this browser state, send it on its way.
4327 [self.dispatcher switchModesAndOpenNewTab:command];
4328 return;
4329 }
4330
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004331 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234332 // events to the feature_engagement::Tracker based on |command.userInitiated|
4333 // and |command.incognito|.
4334 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004335
Mark Cogandfcdea72017-07-18 13:47:384336 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4337 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494338 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4339 self.foregroundTabWasAddedCompletionBlock;
Louis Romero960a12f2017-11-30 19:08:594340 __weak BrowserViewController* weakSelf = self;
Mark Cogandfcdea72017-07-18 13:47:384341 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494342 if (oldForegroundTabWasAddedCompletionBlock) {
4343 oldForegroundTabWasAddedCompletionBlock();
4344 }
Mark Cogandfcdea72017-07-18 13:47:384345 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4346 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4347 if (offTheRecord) {
4348 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4349 timeDelta);
4350 } else {
4351 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4352 }
Louis Romero960a12f2017-11-30 19:08:594353 if (command.shouldFocusOmnibox) {
Mark Cogan5bd86ba2017-12-28 14:32:384354 [weakSelf.dispatcher focusOmnibox];
Louis Romero960a12f2017-11-30 19:08:594355 }
Mark Cogandfcdea72017-07-18 13:47:384356 };
4357
4358 [self setLastTapPoint:command];
Rohit Rao2e22b8d2017-11-07 19:54:544359 // When the tab switcher presentation experiment is enabled, the new tab can
4360 // be opened before BVC has been made visible onscreen. Test for this case by
4361 // checking if the parent container VC is currently in the process of being
4362 // presented.
4363 DCHECK(self.visible || self.dismissingModal ||
4364 (TabSwitcherPresentsBVCEnabled() &&
4365 self.parentViewController.isBeingPresented));
edchin3ab78ff2017-11-13 19:13:144366
4367 // In most cases, we want to take a snapshot of the current tab before opening
4368 // a new tab. However, if the current tab is not fully visible (did not finish
4369 // |-viewDidAppear:|, then we must not take an empty snapshot, replacing an
4370 // existing snapshot for the tab. This can happen when a new regular tab is
4371 // opened from an incognito tab. A different BVC is displayed, which may not
4372 // have enough time to finish appearing before a snapshot is requested.
Mark Cogandfcdea72017-07-18 13:47:384373 Tab* currentTab = [_model currentTab];
edchin3ab78ff2017-11-13 19:13:144374 if (currentTab && self.viewVisible) {
Sylvain Defresne17b8aa42017-12-21 16:17:174375 SnapshotTabHelper::FromWebState(currentTab.webState)
4376 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
Mark Cogandfcdea72017-07-18 13:47:384377 }
4378 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4379 transition:ui::PAGE_TRANSITION_TYPED];
4380}
4381
Mark Cogan123895002017-07-20 12:54:064382- (void)printTab {
4383 Tab* currentTab = [_model currentTab];
4384 // The UI should prevent users from printing non-printable pages. However, a
4385 // redirection to an un-printable page can happen before it is reflected in
4386 // the UI.
4387 if (![currentTab viewForPrinting]) {
4388 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364389 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064390 return;
4391 }
4392 DCHECK(_browserState);
4393 if (!_printController) {
4394 _printController = [[PrintController alloc]
4395 initWithContextGetter:_browserState->GetRequestContext()];
4396 }
4397 [_printController printView:[currentTab viewForPrinting]
4398 withTitle:[currentTab title]
4399 viewController:self];
4400}
4401
Mark Coganfa25b052017-07-20 17:31:034402- (void)addToReadingList:(ReadingListAddCommand*)command {
4403 [self addToReadingListURL:[command URL] title:[command title]];
4404}
4405
sczs3a8c8602017-08-01 20:14:084406- (void)showReadingList {
4407 _readingListCoordinator = [[ReadingListCoordinator alloc]
4408 initWithBaseViewController:self
4409 browserState:self.browserState
4410 loader:self];
4411
4412 [_readingListCoordinator start];
4413}
4414
Jean-François Geyelinedef9552017-08-07 09:56:564415- (void)preloadVoiceSearch {
4416 // Preload VoiceSearchController and views and view controllers needed
4417 // for voice search.
4418 [self ensureVoiceSearchControllerCreated];
4419 _voiceSearchController->PrepareToAppear();
4420}
4421
edchinc5720722017-08-14 22:06:314422#if !defined(NDEBUG)
4423- (void)viewSource {
4424 Tab* tab = [_model currentTab];
4425 DCHECK(tab);
4426 CRWWebController* webController = tab.webController;
4427 NSString* script = @"document.documentElement.outerHTML;";
4428 __weak Tab* weakTab = tab;
4429 __weak BrowserViewController* weakSelf = self;
4430 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4431 Tab* strongTab = weakTab;
4432 if (!strongTab)
4433 return;
4434 if (![result isKindOfClass:[NSString class]])
4435 result = @"Not an HTML page";
4436 std::string base64HTML;
4437 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4438 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194439 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314440 web::ReferrerPolicyDefault);
4441
4442 [[weakSelf tabModel]
4443 insertTabWithURL:URL
4444 referrer:referrer
4445 transition:ui::PAGE_TRANSITION_LINK
4446 opener:strongTab
4447 openedByDOM:YES
4448 atIndex:TabModelConstants::kTabPositionAutomatically
4449 inBackground:NO];
4450 };
4451 [webController executeJavaScript:script
4452 completionHandler:completionHandlerBlock];
4453}
4454#endif // !defined(NDEBUG)
4455
edchin2134c042017-08-18 13:57:354456// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4457- (void)showRateThisAppDialog {
4458 DCHECK(!_rateThisAppDialog);
4459
4460 // Store the current timestamp whenever this dialog is shown.
4461 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4462 base::Time::Now().ToInternalValue());
4463
Gregory Chatzinofff39ec5162017-10-05 20:28:534464 // iOS11 no longer supports the itms link to the app store. So, use a deep
4465 // link for iOS11 and the itms link for prior versions.
4466 NSURL* storeURL;
4467 if (base::ios::IsRunningOnIOS11OrLater()) {
4468 storeURL =
4469 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4470 @"google-chrome-the-fast-and-secure-web-browser/"
4471 @"id535886823?action=write-review")];
4472 } else {
4473 storeURL = [NSURL
4474 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4475 @"MZStore.woa/wa/"
4476 @"viewContentsUserReviews?type=Purple+Software&id="
4477 @"535886823&pt=9008&ct=rating")];
4478 }
edchin2134c042017-08-18 13:57:354479
4480 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014481 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354482
4483 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4484 [_rateThisAppDialog setAppStoreURL:storeURL];
4485 [_rateThisAppDialog setDelegate:self];
4486 [_rateThisAppDialog show];
4487}
4488
Gregory Chatzinoff3f40c1542017-08-30 07:50:044489- (void)showFindInPage {
4490 if (!self.canShowFindBar)
4491 return;
4492
4493 if (!_findBarController) {
4494 _findBarController =
4495 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4496 _findBarController.dispatcher = self.dispatcher;
4497 }
4498
4499 Tab* tab = [_model currentTab];
4500 DCHECK(tab);
4501 auto* helper = FindTabHelper::FromWebState(tab.webState);
4502 DCHECK(!helper->IsFindUIActive());
4503 helper->SetFindUIActive(true);
4504 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4505}
4506
4507- (void)closeFindInPage {
4508 __weak BrowserViewController* weakSelf = self;
4509 Tab* currentTab = [_model currentTab];
4510 if (currentTab) {
4511 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4512 [weakSelf updateFindBar:NO shouldFocus:NO];
4513 });
4514 }
4515}
4516
4517- (void)searchFindInPage {
4518 DCHECK([_model currentTab]);
4519 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4520 __weak BrowserViewController* weakSelf = self;
4521 helper->StartFinding(
4522 [_findBarController searchTerm], ^(FindInPageModel* model) {
4523 BrowserViewController* strongSelf = weakSelf;
4524 if (!strongSelf) {
4525 return;
4526 }
4527 [strongSelf->_findBarController updateResultsCount:model];
4528 });
4529
4530 if (!_isOffTheRecord)
4531 helper->PersistSearchTerm();
4532}
4533
4534- (void)findNextStringInPage {
4535 Tab* currentTab = [_model currentTab];
4536 DCHECK(currentTab);
4537 // TODO(crbug.com/603524): Reshow find bar if necessary.
4538 FindTabHelper::FromWebState(currentTab.webState)
4539 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4540 [_findBarController updateResultsCount:model];
4541 });
4542}
4543
4544- (void)findPreviousStringInPage {
4545 Tab* currentTab = [_model currentTab];
4546 DCHECK(currentTab);
4547 // TODO(crbug.com/603524): Reshow find bar if necessary.
4548 FindTabHelper::FromWebState(currentTab.webState)
4549 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4550 [_findBarController updateResultsCount:model];
4551 });
4552}
4553
edchinf84b2502017-08-31 21:30:454554- (void)showHelpPage {
4555 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4556 [self webPageOrderedOpen:helpUrl
4557 referrer:web::Referrer()
4558 inBackground:NO
4559 appendTo:kCurrentTab];
4560}
4561
edchinb59b5602017-09-01 15:00:204562- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104563 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204564 [self showAllBookmarks];
4565 } else {
4566 [self initializeBookmarkInteractionController];
4567 [_bookmarkInteractionController presentBookmarks];
4568 }
4569}
4570
edchin8ee0807d2017-09-01 23:52:474571- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104572 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474573 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4574 } else {
4575 if (!self.recentTabsCoordinator) {
4576 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4577 initWithBaseViewController:self];
4578 self.recentTabsCoordinator.loader = self;
4579 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4580 self.recentTabsCoordinator.browserState = _browserState;
4581 }
4582 [self.recentTabsCoordinator start];
4583 }
4584}
4585
Mark Cogan6de7e9a2017-09-06 12:57:214586- (void)requestDesktopSite {
4587 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4588}
4589
4590- (void)requestMobileSite {
4591 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4592}
4593
sdefresnee65fd872016-12-19 13:38:134594- (void)closeCurrentTab {
4595 Tab* currentTab = [_model currentTab];
4596 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4597 if (tabIndex == NSNotFound)
4598 return;
4599
jif7fed8122017-02-08 13:15:254600 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4601 // iPad.
sdefresnee65fd872016-12-19 13:38:134602 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4603 exitingPage.image =
Sylvain Defresne17b8aa42017-12-21 16:17:174604 SnapshotTabHelper::FromWebState(currentTab.webState)
4605 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
sdefresnee65fd872016-12-19 13:38:134606
4607 // Close the actual tab, and add its image as a subview.
4608 [_model closeTabAtIndex:tabIndex];
4609
4610 // Do not animate close in iPad.
4611 if (!IsIPadIdiom()) {
4612 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524613 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134614 exitingPage, 0, YES, IsPortrait(), ^{
4615 [exitingPage removeFromSuperview];
4616 });
4617 }
4618}
4619
Mark Cogan776e0282018-01-02 09:00:064620#pragma mark - ToolbarOwner (Public)
sdefresnee65fd872016-12-19 13:38:134621
Kurt Horimotoea429dd2017-11-28 02:24:304622- (CGFloat)toolbarHeight {
Mark Cogan849244ee2017-12-29 15:57:194623 return self.headerHeight;
Kurt Horimotoea429dd2017-11-28 02:24:304624}
4625
Gauthier Ambard04ddb512017-11-07 09:14:164626- (CGRect)toolbarFrame {
sczs42f7f7482017-11-08 01:13:274627 return _toolbarCoordinator.toolbarViewController.view.frame;
Gauthier Ambard04ddb512017-11-07 09:14:164628}
4629
Gauthier Ambard996d9b12017-11-06 09:39:214630- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
4631 id<ToolbarSnapshotProviding> toolbarSnapshotProvider = nil;
sczs42f7f7482017-11-08 01:13:274632 if (_toolbarCoordinator.toolbarViewController.view.hidden) {
Gauthier Ambard996d9b12017-11-06 09:39:214633 Tab* currentTab = [_model currentTab];
4634 if (currentTab.webState &&
4635 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
4636 // Use the native content controller's toolbar when the BVC's is hidden.
4637 id nativeController = [self nativeControllerForTab:currentTab];
4638 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4639 toolbarSnapshotProvider = [nativeController toolbarSnapshotProvider];
4640 }
4641 }
4642 } else {
4643 toolbarSnapshotProvider = _toolbarCoordinator;
4644 }
4645 return toolbarSnapshotProvider;
4646}
4647
sdefresnee65fd872016-12-19 13:38:134648#pragma mark - TabModelObserver methods
4649
4650// Observer method, tab inserted.
4651- (void)tabModel:(TabModel*)model
4652 didInsertTab:(Tab*)tab
4653 atIndex:(NSUInteger)modelIndex
4654 inForeground:(BOOL)fg {
4655 DCHECK(tab);
4656 [self installDelegatesForTab:tab];
4657
4658 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194659 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134660 }
4661}
4662
4663// Observer method, active tab changed.
4664- (void)tabModel:(TabModel*)model
4665 didChangeActiveTab:(Tab*)newTab
4666 previousTab:(Tab*)previousTab
4667 atIndex:(NSUInteger)index {
4668 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4669 // Currently this observer method is always called with a non-nil |newTab|,
4670 // but that may change in the future. Remove this DCHECK when it does.
4671 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274672 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304673 DCHECK(newTab.webState);
4674 infobars::InfoBarManager* infoBarManager =
4675 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134676 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
Mark Coganb12391c62017-12-28 13:35:064677
4678 // Dismiss the language selector, if any; this is a no-op when there's
4679 // no language selector presented.
4680 [_languageSelectionCoordinator dismissLanguageSelector];
sdefresnee65fd872016-12-19 13:38:134681 }
4682 [self updateVoiceSearchBarVisibilityAnimated:NO];
4683
Mohamad Ahmadi7d09ec32017-07-11 22:32:194684 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134685
Gauthier Ambard64396902017-12-08 10:14:584686 [self tabSelected:newTab notifyToolbar:YES];
sdefresnee65fd872016-12-19 13:38:134687}
4688
Mark Cogan5b494642018-01-02 12:55:464689- (void)tabModel:(TabModel*)model willStartLoadingTab:(Tab*)tab {
4690 // Stop any Find in Page searches and close the find bar when navigating to a
4691 // new page.
4692 [self closeFindInPage];
4693}
4694
sdefresnee65fd872016-12-19 13:38:134695- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4696 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4697 if (tab == [_model currentTab]) {
4698 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134699 }
4700}
4701
Mark Cogan5b494642018-01-02 12:55:464702- (void)tabModel:(TabModel*)model didStartLoadingTab:(Tab*)tab {
4703 if (tab == [_model currentTab]) {
4704 if (![self isTabNativePage:tab]) {
4705 [_toolbarCoordinator currentPageLoadStarted];
4706 }
4707 [self updateVoiceSearchBarVisibilityAnimated:NO];
4708 }
4709}
4710
4711- (void)tabModel:(TabModel*)model
4712 didFinishLoadingTab:(Tab*)tab
Mark Cogan50a4c06a2018-01-02 18:26:204713 success:(BOOL)success {
Mark Cogan5b494642018-01-02 12:55:464714 [self tabLoadComplete:tab withSuccess:success];
Mark Coganfc591c8c2018-01-02 16:07:004715 if (IsIPadIdiom()) {
4716 UIUserInterfaceSizeClass sizeClass =
4717 self.view.window.traitCollection.horizontalSizeClass;
4718 [SizeClassRecorder pageLoadedWithHorizontalSizeClass:sizeClass];
4719 }
Mark Cogan5b494642018-01-02 12:55:464720}
4721
4722- (void)tabModel:(TabModel*)model
4723 newTabWillOpen:(Tab*)tab
4724 inBackground:(BOOL)background {
4725 DCHECK(tab);
4726 _temporaryNativeController = nil;
4727
4728 // When adding new tabs, check what kind of reminder infobar should
4729 // be added to the new tab. Try to add only one of them.
4730 // This check is done when a new tab is added either through the Tools Menu
4731 // "New Tab" or through "New Tab" in Stack View Controller. This method
4732 // is called after a new tab has added and finished initial navigation.
4733 // If this is added earlier, the initial navigation may end up clearing
4734 // the infobar(s) that are just added. See http://crbug/340250 for details.
4735 web::WebState* webState = tab.webState;
4736 DCHECK(webState);
4737
4738 infobars::InfoBarManager* infoBarManager =
4739 InfoBarManagerImpl::FromWebState(webState);
4740 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
4741 forTabId:[tab tabId]];
4742 if (!ReSignInInfoBarDelegate::Create(_browserState, tab,
4743 self /* id<SigninPresenter> */)) {
4744 DisplaySyncErrors(_browserState, tab, self /* id<SyncPresenter> */);
4745 }
4746
4747 // The rest of this function initiates the new tab animation, which is
4748 // phone-specific. Call the foreground tab added completion block; for
4749 // iPhones, this will get executed after the animation has finished.
4750 if (IsIPadIdiom()) {
4751 if (self.foregroundTabWasAddedCompletionBlock) {
4752 // This callback is called before webState is activated (on
4753 // kTabModelNewTabWillOpenNotification notification). Dispatch the
4754 // callback asynchronously to be sure the activation is complete.
4755 dispatch_async(dispatch_get_main_queue(), ^() {
4756 // Test existence again as the block may have been deleted.
4757 if (self.foregroundTabWasAddedCompletionBlock) {
4758 self.foregroundTabWasAddedCompletionBlock();
4759 self.foregroundTabWasAddedCompletionBlock = nil;
4760 }
4761 });
4762 }
4763 return;
4764 }
4765
4766 // Do nothing if browsing is currently suspended. The BVC will set everything
4767 // up correctly when browsing resumes.
4768 if (!self.visible || ![_model webUsageEnabled])
4769 return;
4770
4771 // Block that starts voice search at the end of new Tab animation if
4772 // necessary.
4773 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
4774 if (_startVoiceSearchAfterNewTabAnimation) {
4775 _startVoiceSearchAfterNewTabAnimation = NO;
4776 [self startVoiceSearchWithOriginView:nil];
4777 }
4778 };
4779
4780 self.inNewTabAnimation = YES;
4781 if (!background) {
4782 UIView* animationParentView = _contentArea;
4783 // Create the new page image, and load with the new tab snapshot except if
4784 // it is the NTP.
4785 CGFloat newPageOffset = 0;
4786 UIView* newPage;
4787 CGFloat offset = 0;
4788 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
4789 !_isOffTheRecord && !IsIPadIdiom()) {
4790 offset = 0;
4791 animationParentView = self.view;
4792 newPage = tab.view;
4793 newPage.userInteractionEnabled = NO;
4794 // Compute a frame for the new page by removing the status bar height from
4795 // the bounds of |self.view|.
4796 CGRect viewBounds, remainder;
4797 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
4798 CGRectMinYEdge);
4799 newPage.frame = viewBounds;
4800 } else {
4801 UIImageView* pageScreenshot = [self pageOpenCloseAnimationView];
4802 tab.view.frame = _contentArea.bounds;
4803 pageScreenshot.image = SnapshotTabHelper::FromWebState(tab.webState)
4804 ->UpdateSnapshot(/*with_overlays=*/true,
4805 /*visible_frame_only=*/true);
4806 newPage = pageScreenshot;
4807 offset =
4808 pageScreenshot.frame.size.height - pageScreenshot.image.size.height;
4809 }
4810 newPageOffset = newPage.frame.origin.y;
4811
4812 [animationParentView addSubview:newPage];
4813 CGPoint origin = [self lastTapPoint];
4814 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
4815 newPage, -newPageOffset, offset, origin, _isOffTheRecord, NULL, ^{
4816 [tab view].frame = _contentArea.bounds;
4817 newPage.userInteractionEnabled = YES;
4818 [newPage removeFromSuperview];
4819 self.inNewTabAnimation = NO;
4820 // Use the model's currentTab here because it is possible that it can
4821 // be reset to a new value before the new Tab animation finished (e.g.
4822 // if another Tab shows a dialog via |dialogPresenter|). However, that
4823 // tab's view hasn't been displayed yet because it was in a new tab
4824 // animation.
4825 Tab* currentTab = [_model currentTab];
4826 if (currentTab) {
4827 [self tabSelected:currentTab notifyToolbar:NO];
4828 }
4829 startVoiceSearchIfNecessaryBlock();
4830
4831 if (self.foregroundTabWasAddedCompletionBlock) {
4832 self.foregroundTabWasAddedCompletionBlock();
4833 self.foregroundTabWasAddedCompletionBlock = nil;
4834 }
4835 });
4836 } else {
4837 // SnapshotTabHelper::UpdateSnapshot will force a screen redraw, so take the
4838 // snapshot before adding the views needed for the background animation.
4839 Tab* topTab = [_model currentTab];
4840 UIImage* image =
4841 SnapshotTabHelper::FromWebState(topTab.webState)
4842 ->UpdateSnapshot(/*with_overlays=*/true,
4843 /*visible_frame_only=*/self.isToolbarOnScreen);
4844
4845 // The size of the |image| above can be wrong if the snapshot fails, grab
4846 // the correct size here.
4847 CGRect imageFrame = CGRectZero;
4848 if (self.isToolbarOnScreen) {
4849 imageFrame = UIEdgeInsetsInsetRect(
4850 _contentArea.bounds, [self snapshotEdgeInsetsForTab:topTab]);
4851 } else {
4852 imageFrame = [topTab.webState->GetView() bounds];
4853 }
4854
4855 // Add three layers in order on top of the contentArea for the animation:
4856 // 1. The black "background" screen.
4857 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
4858 InstallBackgroundInView(background);
4859 [_contentArea addSubview:background];
4860
4861 // 2. A CardView displaying the data from the current tab.
4862 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
4863 NSString* title = [topTab title];
4864 if (![title length])
4865 title = [topTab urlDisplayString];
4866 [topCard setTitle:title];
4867 [topCard setImage:image];
4868 [topCard setFavicon:nil];
4869
4870 favicon::FaviconDriver* faviconDriver =
4871 favicon::WebFaviconDriver::FromWebState(topTab.webState);
4872 if (faviconDriver && faviconDriver->FaviconIsValid()) {
4873 gfx::Image favicon = faviconDriver->GetFavicon();
4874 if (!favicon.IsEmpty())
4875 [topCard setFavicon:favicon.ToUIImage()];
4876 }
4877
4878 // 3. A new, blank CardView to represent the new tab being added.
4879 // Launch the new background tab animation.
4880 page_animation_util::AnimateNewBackgroundPageWithCompletion(
4881 topCard, [_contentArea frame], imageFrame, IsPortrait(), ^{
4882 [background removeFromSuperview];
4883 [topCard removeFromSuperview];
4884 self.inNewTabAnimation = NO;
4885 // Resnapshot the top card if it has its own toolbar, as the toolbar
4886 // will be captured in the new tab animation, but isn't desired for
4887 // the stack view snapshots.
4888 id nativeController = [self nativeControllerForTab:topTab];
4889 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4890 SnapshotTabHelper::FromWebState(topTab.webState)
4891 ->UpdateSnapshot(/*with_overlays=*/true,
4892 /*visible_frame_only=*/true);
4893 }
4894 startVoiceSearchIfNecessaryBlock();
4895 });
4896 // Reset the foreground tab completion block so that it can never be
4897 // called more than once regardless of foreground/background tab
4898 // appearances.
4899 self.foregroundTabWasAddedCompletionBlock = nil;
4900 }
4901}
4902
4903- (void)tabModel:(TabModel*)model didDeselectTab:(Tab*)tab {
4904 [tab wasHidden];
4905 [self dismissPopups];
4906}
4907
sdefresne49cf2862017-03-15 13:46:144908// Observer method, tab replaced.
4909- (void)tabModel:(TabModel*)model
4910 didReplaceTab:(Tab*)oldTab
4911 withTab:(Tab*)newTab
4912 atIndex:(NSUInteger)index {
4913 [self uninstallDelegatesForTab:oldTab];
4914 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264915
michaeldo79909fb2017-05-09 23:42:504916 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304917 infobars::InfoBarManager* infoBarManager = nullptr;
4918 if (newTab) {
4919 DCHECK(newTab.webState);
4920 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4921 }
michaeldo79909fb2017-05-09 23:42:504922 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4923 }
4924
Kurt Horimoto4d1f6962017-12-19 23:38:094925 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4926 if (self.active && model.currentTab == newTab)
kkhorimotofa0844cc2017-03-20 17:01:264927 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464928
4929 if (newTab)
4930 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144931}
4932
sdefresnee65fd872016-12-19 13:38:134933// A tab has been removed, remove its views from display if necessary.
4934- (void)tabModel:(TabModel*)model
4935 didRemoveTab:(Tab*)tab
4936 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144937 [self uninstallDelegatesForTab:tab];
4938
kkhorimoto496fdd72017-06-12 19:56:314939 // Cancel dialogs for |tab|'s WebState.
4940 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4941
sdefresnee65fd872016-12-19 13:38:134942 // Ignore changes while the tab stack view is visible (or while suspended).
4943 // The display will be refreshed when this view becomes active again.
4944 if (!self.visible || !model.webUsageEnabled)
4945 return;
4946
4947 // Remove the find bar for now.
4948 [self hideFindBarWithAnimation:NO];
4949}
4950
4951- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4952 if (tab == [model currentTab]) {
4953 [_contentArea displayContentView:nil];
sczsf1620e52017-10-02 22:54:464954 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:134955 }
4956
Mohamad Ahmadi7d09ec32017-07-11 22:32:194957 [_paymentRequestManager stopTrackingWebState:tab.webState];
4958
sdefresnee65fd872016-12-19 13:38:134959 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4960 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194961 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134962 }
4963}
4964
4965// Called when the number of tabs changes. Update the toolbar accordingly.
4966- (void)tabModelDidChangeTabCount:(TabModel*)model {
4967 DCHECK(model == _model);
sczsf1620e52017-10-02 22:54:464968 [_toolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134969}
4970
Mark Cogan849244ee2017-12-29 15:57:194971#pragma mark - UpgradeCenterClient
sdefresnee65fd872016-12-19 13:38:134972
4973- (void)showUpgrade:(UpgradeCenter*)center {
4974 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274975 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134976 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304977 DCHECK(tab.webState);
4978 infobars::InfoBarManager* infoBarManager =
4979 InfoBarManagerImpl::FromWebState(tab.webState);
4980 DCHECK(infoBarManager);
4981 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134982 }
4983}
4984
Mark Cogan80aa28d2017-11-30 13:11:344985#pragma mark - InfobarContainerStateDelegate
sdefresnee65fd872016-12-19 13:38:134986
Mark Cogan80aa28d2017-11-30 13:11:344987- (void)infoBarContainerStateDidChangeAnimated:(BOOL)animated {
sdefresnee65fd872016-12-19 13:38:134988 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4989 DCHECK(infoBarContainerView);
4990 CGRect containerFrame = infoBarContainerView.frame;
4991 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4992 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4993 containerFrame.size.height = height;
4994 BOOL isViewVisible = self.visible;
4995 [UIView animateWithDuration:0.1
4996 animations:^{
4997 [infoBarContainerView setFrame:containerFrame];
4998 }
4999 completion:^(BOOL finished) {
5000 if (!isViewVisible)
5001 return;
5002 UIAccessibilityPostNotification(
5003 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
5004 }];
5005}
5006
Mark Cogan80aa28d2017-11-30 13:11:345007#pragma mark - UIGestureRecognizerDelegate
sdefresnee65fd872016-12-19 13:38:135008
5009// Always return yes, as this tap should work with various recognizers,
5010// including UITextTapRecognizer, UILongPressGestureRecognizer,
5011// UIScrollViewPanGestureRecognizer and others.
5012- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
5013 shouldRecognizeSimultaneouslyWithGestureRecognizer:
5014 (UIGestureRecognizer*)otherGestureRecognizer {
5015 return YES;
5016}
5017
5018// Tap gestures should only be recognized within |_contentArea|.
5019- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
5020 CGPoint location = [gesture locationInView:self.view];
5021
5022 // Only allow touches on descendant views of |_contentArea|.
5023 UIView* hitView = [self.view hitTest:location withEvent:nil];
5024 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
5025}
5026
Mark Cogan849244ee2017-12-29 15:57:195027#pragma mark - SideSwipeControllerDelegate
sdefresnee65fd872016-12-19 13:38:135028
5029- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
5030 DCHECK(!IsIPadIdiom());
5031 // Update frame incase orientation changed while |_contentArea| was out of
5032 // the view hierarchy.
5033 [_contentArea setFrame:[sideSwipeView frame]];
5034
Justin Cohen16ad60e2017-11-10 14:56:265035 [self.view insertSubview:_contentArea aboveSubview:_fakeStatusBarView];
sdefresnee65fd872016-12-19 13:38:135036 [self updateVoiceSearchBarVisibilityAnimated:NO];
5037 [self updateToolbar];
5038
5039 // Reset horizontal stack view.
5040 [sideSwipeView removeFromSuperview];
Mark Cogan5bd86ba2017-12-28 14:32:385041 [self.sideSwipeController setInSwipe:NO];
sdefresnee65fd872016-12-19 13:38:135042 [_infoBarContainer->view() setHidden:NO];
5043}
5044
Mark Cogan849244ee2017-12-29 15:57:195045- (UIView*)sideSwipeContentView {
sdefresnee65fd872016-12-19 13:38:135046 return _contentArea;
5047}
5048
Mark Cogan849244ee2017-12-29 15:57:195049- (void)sideSwipeRedisplayTab:(Tab*)tab {
5050 [self displayTab:tab isNewSelection:YES];
5051}
5052
sdefresnee65fd872016-12-19 13:38:135053- (BOOL)preventSideSwipe {
Peter Laurense0b80f12017-11-21 07:52:405054 if ([_toolbarCoordinator isShowingToolsMenu])
sdefresnee65fd872016-12-19 13:38:135055 return YES;
5056
5057 if (_voiceSearchController && _voiceSearchController->IsVisible())
5058 return YES;
5059
sdefresnee65fd872016-12-19 13:38:135060 if (!self.active)
5061 return YES;
5062
5063 return NO;
5064}
5065
5066- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5067 if (visible) {
5068 [self updateVoiceSearchBarVisibilityAnimated:NO];
5069 [self updateToolbar];
5070 [_infoBarContainer->view() setHidden:NO];
5071 } else {
5072 // Hide UI accessories such as find bar and first visit overlays
5073 // for welcome page.
5074 [self hideFindBarWithAnimation:NO];
5075 [_infoBarContainer->view() setHidden:YES];
5076 [_voiceSearchBar setHidden:YES];
5077 }
5078}
5079
Mark Cogan849244ee2017-12-29 15:57:195080- (CGFloat)headerHeightForSideSwipe {
5081 return self.headerHeight;
5082}
5083
sdefresnee65fd872016-12-19 13:38:135084- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5085 BOOL seenToolbar = NO;
5086 BOOL seenInfoBarContainer = NO;
5087 BOOL seenContentArea = NO;
5088 for (UIView* view in views.subviews) {
sczs42f7f7482017-11-08 01:13:275089 if (view == _toolbarCoordinator.toolbarViewController.view)
sdefresnee65fd872016-12-19 13:38:135090 seenToolbar = YES;
5091 else if (view == _infoBarContainer->view())
5092 seenInfoBarContainer = YES;
5093 else if (view == _contentArea)
5094 seenContentArea = YES;
5095 if ((seenToolbar && !seenInfoBarContainer) ||
5096 (seenInfoBarContainer && !seenContentArea))
5097 return NO;
5098 }
5099 return YES;
5100}
5101
5102#pragma mark - PreloadControllerDelegate methods
5103
rohitraoeeb5293b2017-06-15 14:40:025104- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445105 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135106}
5107
rohitraoeeb5293b2017-06-15 14:40:025108- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5109 return [self hasControllerForURL:url];
5110}
5111
Gauthier Ambard65e949b092017-11-29 08:46:205112// TODO(crbug.com/788705): BVC doesn't need to implement
5113// BookmarkModelBridgeObserver once the new toolbar is turned on.
5114#pragma mark - BookmarkModelBridgeObserver
sdefresnee65fd872016-12-19 13:38:135115
5116// If an added or removed bookmark is the same as the current url, update the
5117// toolbar so the star highlight is kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205118- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
5119 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:135120}
5121
5122// If all bookmarks are removed, update the toolbar so the star highlight is
5123// kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205124- (void)bookmarkModelRemovedAllNodes {
sdefresnee65fd872016-12-19 13:38:135125 [self updateToolbar];
5126}
5127
Gauthier Ambard65e949b092017-11-29 08:46:205128// In case we are on a bookmarked page before the model is loaded.
5129- (void)bookmarkModelLoaded {
5130 [self updateToolbar];
5131}
5132
5133- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
5134 // No-op -- required by BookmarkModelBridgeObserver but not used.
5135}
5136
5137- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
5138 movedFromParent:(const BookmarkNode*)oldParent
5139 toParent:(const BookmarkNode*)newParent {
5140 // No-op -- required by BookmarkModelBridgeObserver but not used.
5141}
5142
5143- (void)bookmarkNodeDeleted:(const BookmarkNode*)node
5144 fromFolder:(const BookmarkNode*)folder {
5145 // No-op -- required by BookmarkModelBridgeObserver but not used.
5146}
5147
Mark Cogan849244ee2017-12-29 15:57:195148#pragma mark - NetExportTabHelperDelegate
sdefresnee65fd872016-12-19 13:38:135149
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575150- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5151 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135152 if (![MFMailComposeViewController canSendMail]) {
5153 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575154 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135155 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575156 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135157 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5158 return;
5159 }
stkhapuginc9eee7b2017-04-10 15:49:275160 MFMailComposeViewController* mailViewController =
5161 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135162 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575163 [mailViewController setToRecipients:[context toRecipients]];
5164 [mailViewController setSubject:[context subject]];
5165 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135166
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575167 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135168 if (!textFile.empty()) {
5169 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5170 NSData* data = [NSData dataWithContentsOfFile:filename];
5171 if (data) {
5172 NSString* displayName =
5173 base::SysUTF8ToNSString(textFile.BaseName().value());
5174 [mailViewController addAttachmentData:data
5175 mimeType:@"text/plain"
5176 fileName:displayName];
5177 }
5178 }
5179
5180 [mailViewController setMailComposeDelegate:self];
5181 [self presentViewController:mailViewController animated:YES completion:nil];
5182}
5183
5184#pragma mark - MFMailComposeViewControllerDelegate methods
5185
5186- (void)mailComposeController:(MFMailComposeViewController*)controller
5187 didFinishWithResult:(MFMailComposeResult)result
5188 error:(NSError*)error {
5189 [self dismissViewControllerAnimated:YES completion:nil];
5190}
5191
Mark Cogan849244ee2017-12-29 15:57:195192#pragma mark - SKStoreProductViewControllerDelegate
sdefresnee65fd872016-12-19 13:38:135193
5194- (void)productViewControllerDidFinish:
5195 (SKStoreProductViewController*)viewController {
5196 [self dismissViewControllerAnimated:YES completion:nil];
5197}
5198
Mark Cogan849244ee2017-12-29 15:57:195199#pragma mark - StoreKitLauncher methods
5200
sdefresnee65fd872016-12-19 13:38:135201- (void)openAppStore:(NSString*)appId {
5202 if (![appId length])
5203 return;
5204 NSDictionary* product =
5205 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275206 SKStoreProductViewController* storeViewController =
5207 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135208 [storeViewController setDelegate:self];
5209 [storeViewController loadProductWithParameters:product completionBlock:nil];
5210 [self presentViewController:storeViewController animated:YES completion:nil];
5211}
5212
5213#pragma mark - TabDialogDelegate methods
5214
sdefresnee65fd872016-12-19 13:38:135215- (void)cancelDialogForTab:(Tab*)tab {
5216 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5217}
5218
Mark Cogan849244ee2017-12-29 15:57:195219#pragma mark - AppRatingPromptDelegate
sdefresnee65fd872016-12-19 13:38:135220
5221- (void)userTappedRateApp:(UIView*)view {
5222 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275223 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135224}
5225
5226- (void)userTappedSendFeedback:(UIView*)view {
5227 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275228 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205229 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135230}
5231
5232- (void)userTappedDismiss:(UIView*)view {
5233 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275234 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135235}
5236
Mark Cogan849244ee2017-12-29 15:57:195237#pragma mark - VoiceSearchBarOwner
5238
5239- (id<VoiceSearchBar>)voiceSearchBar {
5240 return _voiceSearchBar;
5241}
5242
sdefresnee65fd872016-12-19 13:38:135243#pragma mark - VoiceSearchBarDelegate
5244
5245- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275246 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135247 [self ensureVoiceSearchControllerCreated];
5248 return _voiceSearchController->IsTextToSpeechEnabled() &&
5249 _voiceSearchController->IsTextToSpeechSupported();
5250}
5251
5252- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275253 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
Sylvain Defresne17b8aa42017-12-21 16:17:175254 SnapshotTabHelper::FromWebState(self.tabModel.currentTab.webState)
5255 ->UpdateSnapshot(/*with_overlays=*/true, /*visible_frame_only=*/true);
sdefresnee65fd872016-12-19 13:38:135256}
5257
Mark Cogan776e0282018-01-02 09:00:065258#pragma mark - VoiceSearchPresenter (Public)
sdefresnee65fd872016-12-19 13:38:135259
5260- (UIView*)voiceSearchButton {
5261 return _voiceSearchButton;
5262}
5263
5264- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5265 return [self currentLogoAnimationControllerOwner];
5266}
5267
Mark Cogan849244ee2017-12-29 15:57:195268#pragma mark - VoiceSearchPresenter helpers
5269
5270// The LogoAnimationControllerOwner to be used for the next logo transition
5271// animation.
5272- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
5273 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
5274 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
5275 self.shouldShowVoiceSearchBar) {
5276 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
5277 // animations.
5278 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
5279 }
5280 id currentNativeController =
5281 [self nativeControllerForTab:self.tabModel.currentTab];
5282 Protocol* possibleOwnerProtocol =
5283 @protocol(LogoAnimationControllerOwnerOwner);
5284 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
5285 [currentNativeController logoAnimationControllerOwner]) {
5286 // If the current native controller is showing a GLIF view (e.g. the NTP
5287 // when there is no doodle), use that GLIFControllerOwner.
5288 return [currentNativeController logoAnimationControllerOwner];
5289 }
5290 return nil;
5291}
5292
5293#pragma mark - ActivityServicePresentation
Rohit Rao01e0e002017-08-14 20:49:435294
5295- (void)presentActivityServiceViewController:(UIViewController*)controller {
5296 [self presentViewController:controller animated:YES completion:nil];
5297}
5298
5299- (void)activityServiceDidEndPresenting {
Mark Cogan5bd86ba2017-12-28 14:32:385300 self.dialogPresenterDelegateIsPresenting = NO;
Rohit Rao01e0e002017-08-14 20:49:435301 [self.dialogPresenter tryToPresent];
5302}
5303
Mark Cogan849244ee2017-12-29 15:57:195304- (void)showActivityServiceErrorAlertWithStringTitle:(NSString*)title
5305 message:(NSString*)message {
5306 [self showErrorAlertWithStringTitle:title message:message];
5307}
5308
5309#pragma mark - QRScannerPresenting
Rohit Raocda0a992017-08-16 15:37:115310
5311- (void)presentQRScannerViewController:(UIViewController*)controller {
5312 [self presentViewController:controller animated:YES completion:nil];
5313}
5314
5315- (void)dismissQRScannerViewController:(UIViewController*)controller
5316 completion:(void (^)(void))completion {
5317 DCHECK_EQ(controller, self.presentedViewController);
5318 [self dismissViewControllerAnimated:YES completion:completion];
5319}
5320
Mark Cogan849244ee2017-12-29 15:57:195321#pragma mark - TabHeadersDelegate
5322
5323- (CGFloat)tabHeaderHeightForTab:(Tab*)tab {
5324 return [self headerHeightForTab:tab];
5325}
5326
5327#pragma mark - TabHistoryPresentation
sczsdd860eba2017-08-10 01:55:385328
sczs0a726d22017-08-21 22:40:135329- (UIView*)viewForTabHistoryPresentation {
5330 return self.view;
5331}
5332
sczsdd860eba2017-08-10 01:55:385333- (void)prepareForTabHistoryPresentation {
5334 DCHECK(self.visible || self.dismissingModal);
5335 [[self.tabModel currentTab].webController dismissKeyboard];
sczsf1620e52017-10-02 22:54:465336 [_toolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385337}
5338
Mike Doughertya1ec26402017-08-23 19:46:315339#pragma mark - CaptivePortalDetectorTabHelperDelegate
5340
Mike Dougherty4620cf8e2017-10-31 23:37:095341- (void)captivePortalDetectorTabHelper:
5342 (CaptivePortalDetectorTabHelper*)tabHelper
5343 connectWithLandingURL:(const GURL&)landingURL {
Mike Dougherty66e58812017-11-03 06:54:285344 [self addSelectedTabWithURL:landingURL transition:ui::PAGE_TRANSITION_TYPED];
Mike Doughertya1ec26402017-08-23 19:46:315345}
5346
Gregory Chatzinoffdf93d692017-09-09 01:32:275347#pragma mark - PageInfoPresentation
5348
Gregory Chatzinoffb6a01f72017-09-20 20:06:395349- (void)presentPageInfoView:(UIView*)pageInfoView {
5350 [pageInfoView setFrame:self.view.bounds];
5351 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275352}
5353
5354- (void)prepareForPageInfoPresentation {
5355 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:465356 [_toolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275357}
5358
Gregory Chatzinoffb6a01f72017-09-20 20:06:395359- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5360 return [self.view convertPoint:origin fromView:nil];
5361}
5362
Sylvain Defresnecacc3a52017-09-12 13:51:045363#pragma mark - WebStatePrinter
5364
5365- (void)printWebState:(web::WebState*)webState {
5366 if (webState == [_model currentTab].webState)
Mark Cogan849244ee2017-12-29 15:57:195367 [self.dispatcher printTab];
Sylvain Defresnecacc3a52017-09-12 13:51:045368}
5369
Eugene But35ded552017-09-13 23:31:595370#pragma mark - RepostFormTabHelperDelegate
5371
5372- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
Sylvain Defresnee3c698122017-11-17 11:16:325373 presentRepostFormDialogForWebState:(web::WebState*)webState
5374 dialogAtPoint:(CGPoint)location
5375 completionHandler:(void (^)(BOOL))completion {
5376 _repostFormCoordinator =
5377 [[RepostFormCoordinator alloc] initWithBaseViewController:self
5378 dialogLocation:location
5379 webState:webState
5380 completionHandler:completion];
Eugene But35ded552017-09-13 23:31:595381 [_repostFormCoordinator start];
5382}
5383
5384- (void)repostFormTabHelperDismissRepostFormDialog:
5385 (RepostFormTabHelper*)helper {
5386 _repostFormCoordinator = nil;
5387}
5388
edchinf5150c682017-09-18 02:50:035389#pragma mark - TabStripPresentation
5390
5391- (BOOL)isTabStripFullyVisible {
5392 return ([self currentHeaderOffset] == 0.0f);
5393}
5394
5395- (void)showTabStripView:(UIView*)tabStripView {
5396 DCHECK([self isViewLoaded]);
5397 DCHECK(tabStripView);
5398 self.tabStripView = tabStripView;
5399 CGRect tabStripFrame = [self.tabStripView frame];
5400 tabStripFrame.origin = CGPointZero;
5401 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5402 // because the CGPointZero above will break reset the offset, but it's not
5403 // clear what removing that will do.
Mark Cogan849244ee2017-12-29 15:57:195404 tabStripFrame.origin.y = self.headerOffset;
edchinf5150c682017-09-18 02:50:035405 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5406 [self.tabStripView setFrame:tabStripFrame];
5407 [[self view] addSubview:tabStripView];
5408}
5409
edchincd32fdf2017-10-25 12:45:455410#pragma mark - ManageAccountsDelegate
5411
5412- (void)onManageAccounts {
5413 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5414 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5415 ->GetState());
edchin5b8aa052017-10-30 23:27:285416 [self.dispatcher showAccountsSettingsFromViewController:self];
edchincd32fdf2017-10-25 12:45:455417}
5418
5419- (void)onAddAccount {
5420 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5421 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5422 ->GetState());
edchinb082b2982017-11-03 19:41:215423 [self.dispatcher showAddAccountFromViewController:self];
edchincd32fdf2017-10-25 12:45:455424}
5425
5426- (void)onGoIncognito:(const GURL&)url {
5427 // The user taps on go incognito from the mobile U-turn webpage (the web page
5428 // that displays all users accounts available in the content area). As the
5429 // user chooses to go to incognito, the mobile U-turn page is no longer
5430 // neeeded. The current solution is to go back in history. This has the
5431 // advantage of keeping the current browsing session and give a good user
5432 // experience when the user comes back from incognito.
5433 [self.tabModel.currentTab goBack];
5434
5435 if (url.is_valid()) {
5436 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5437 initWithURL:url
5438 referrer:web::Referrer() // Strip referrer when switching modes.
5439 inIncognito:YES
5440 inBackground:NO
5441 appendTo:kLastTab];
5442 [self.dispatcher openURL:command];
5443 } else {
5444 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5445 }
5446}
5447
Mark Cogan776e0282018-01-02 09:00:065448#pragma mark - SyncPresenter (Public)
edchin95c927072017-11-04 00:35:075449
5450- (void)showReauthenticateSignin {
5451 [self.dispatcher
edchin3b46e8d2017-11-07 22:48:125452 showSignin:
5453 [[ShowSigninCommand alloc]
5454 initWithOperation:AUTHENTICATION_OPERATION_REAUTHENTICATE
5455 accessPoint:signin_metrics::AccessPoint::
5456 ACCESS_POINT_UNKNOWN]
5457 baseViewController:self];
edchin95c927072017-11-04 00:35:075458}
5459
5460- (void)showSyncSettings {
edchina14d7182017-11-06 18:37:505461 [self.dispatcher showSyncSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075462}
5463
5464- (void)showSyncPassphraseSettings {
edchinec723062017-11-06 20:03:545465 [self.dispatcher showSyncPassphraseSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075466}
5467
edchin9e7a1112017-11-07 18:28:035468#pragma mark - SigninPresenter
5469
5470- (void)showSignin:(ShowSigninCommand*)command {
edchin3b46e8d2017-11-07 22:48:125471 [self.dispatcher showSignin:command baseViewController:self];
edchin9e7a1112017-11-07 18:28:035472}
5473
sdefresnee65fd872016-12-19 13:38:135474@end