blob: ff076193f56b69cceb17e8f436f2dc0974e20197 [file] [log] [blame]
sdefresnee65fd872016-12-19 13:38:131// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ios/chrome/browser/ui/browser_view_controller.h"
6
7#import <AssetsLibrary/AssetsLibrary.h>
8#import <MobileCoreServices/MobileCoreServices.h>
9#import <PassKit/PassKit.h>
10#import <Photos/Photos.h>
11#import <QuartzCore/QuartzCore.h>
12
13#include <stdint.h>
14#include <cmath>
15#include <memory>
16
17#include "base/base64.h"
18#include "base/command_line.h"
mathp9b4c11d2017-07-06 20:24:1319#include "base/feature_list.h"
gambard9efce7a2017-02-09 18:53:1720#include "base/files/file_path.h"
sdefresnee65fd872016-12-19 13:38:1321#include "base/format_macros.h"
22#include "base/i18n/rtl.h"
23#include "base/ios/block_types.h"
24#include "base/ios/ios_util.h"
sdefresnee65fd872016-12-19 13:38:1325#include "base/logging.h"
26#include "base/mac/bind_objc_block.h"
27#include "base/mac/bundle_locations.h"
28#include "base/mac/foundation_util.h"
sdefresnee65fd872016-12-19 13:38:1329#include "base/macros.h"
30#include "base/memory/ptr_util.h"
asvitkinef1899e32017-01-27 16:30:2931#include "base/metrics/histogram_macros.h"
sdefresnee65fd872016-12-19 13:38:1332#include "base/metrics/user_metrics.h"
33#include "base/metrics/user_metrics_action.h"
sdefresnee65fd872016-12-19 13:38:1334#include "base/strings/sys_string_conversions.h"
rohitraocd324eb72017-04-04 15:36:3935#include "base/strings/utf_string_conversions.h"
Sylvain Defresnefd3ecf22017-07-12 18:47:2436#include "base/task_scheduler/post_task.h"
tzik14236032017-02-15 06:41:0137#include "base/threading/sequenced_worker_pool.h"
Sylvain Defresnefd3ecf22017-07-12 18:47:2438#include "base/threading/thread_restrictions.h"
sdefresnee65fd872016-12-19 13:38:1339#include "components/bookmarks/browser/base_bookmark_model_observer.h"
40#include "components/bookmarks/browser/bookmark_model.h"
Sylvain Defresne7178d4c2017-09-14 13:22:3741#include "components/favicon/ios/web_favicon_driver.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2342#include "components/feature_engagement/public/event_constants.h"
Cooper Knaake4f495cf2017-07-27 23:30:0343#include "components/feature_engagement/public/feature_constants.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2344#include "components/feature_engagement/public/tracker.h"
gambardbdc07cc2017-02-03 16:43:1145#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
sdefresnee65fd872016-12-19 13:38:1346#include "components/infobars/core/infobar_manager.h"
Mark Coganca30df62017-11-20 14:29:1147#import "components/language/ios/browser/ios_language_detection_tab_helper.h"
mathp9b4c11d2017-07-06 20:24:1348#include "components/payments/core/features.h"
sdefresnee65fd872016-12-19 13:38:1349#include "components/prefs/pref_service.h"
olivierrobin52b6cd6ec2017-03-23 13:55:5450#include "components/reading_list/core/reading_list_model.h"
sdefresnee65fd872016-12-19 13:38:1351#include "components/search_engines/search_engines_pref_names.h"
52#include "components/search_engines/template_url_service.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0553#include "components/sessions/core/session_types.h"
sdefresnee65fd872016-12-19 13:38:1354#include "components/sessions/core/tab_restore_service_helper.h"
edchincd32fdf2017-10-25 12:45:4555#include "components/signin/core/browser/account_reconcilor.h"
56#include "components/signin/core/browser/signin_metrics.h"
57#import "components/signin/ios/browser/account_consistency_service.h"
Eugene Butc90499d52017-09-22 16:02:0958#include "components/signin/ios/browser/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:1359#include "components/strings/grit/components_strings.h"
60#include "components/toolbar/toolbar_model_impl.h"
61#include "ios/chrome/app/tests_hook.h"
62#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
63#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
64#include "ios/chrome/browser/chrome_url_constants.h"
65#include "ios/chrome/browser/chrome_url_util.h"
Eugene But49a7c572017-12-11 20:54:1566#import "ios/chrome/browser/download/pass_kit_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1367#include "ios/chrome/browser/experimental_flags.h"
68#import "ios/chrome/browser/favicon/favicon_loader.h"
69#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2370#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
71#include "ios/chrome/browser/feature_engagement/tracker_util.h"
sdefresnee65fd872016-12-19 13:38:1372#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
73#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3674#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1375#include "ios/chrome/browser/first_run/first_run.h"
76#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
Mark Cogan80aa28d2017-11-30 13:11:3477#include "ios/chrome/browser/infobars/infobar_container_delegate_ios.h"
sdefresnee65fd872016-12-19 13:38:1378#include "ios/chrome/browser/infobars/infobar_container_ios.h"
Mark Cogan80aa28d2017-11-30 13:11:3479#import "ios/chrome/browser/infobars/infobar_container_state_delegate.h"
sdefresnee65fd872016-12-19 13:38:1380#include "ios/chrome/browser/infobars/infobar_container_view.h"
Rohit Raoaf46af92017-08-10 12:52:3081#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
Mark Coganca30df62017-11-20 14:29:1182#import "ios/chrome/browser/language/url_language_histogram_factory.h"
sdefresnee65fd872016-12-19 13:38:1383#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
84#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
sdefresnee65fd872016-12-19 13:38:1385#import "ios/chrome/browser/open_url_util.h"
86#import "ios/chrome/browser/passwords/password_controller.h"
Tomasz Garbusb844e992017-09-29 12:44:5587#include "ios/chrome/browser/passwords/password_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1388#include "ios/chrome/browser/pref_names.h"
Rohit Rao44f204302017-08-10 14:49:5489#import "ios/chrome/browser/prerender/preload_controller_delegate.h"
90#import "ios/chrome/browser/prerender/prerender_service.h"
91#import "ios/chrome/browser/prerender/prerender_service_factory.h"
olivierrobin013ba672017-03-01 21:16:2492#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1393#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
94#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
95#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0596#include "ios/chrome/browser/sessions/session_util.h"
sdefresnee65fd872016-12-19 13:38:1397#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
98#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
edchincd32fdf2017-10-25 12:45:4599#import "ios/chrome/browser/signin/account_consistency_service_factory.h"
100#include "ios/chrome/browser/signin/account_reconcilor_factory.h"
sdefresnee65fd872016-12-19 13:38:13101#import "ios/chrome/browser/snapshots/snapshot_cache.h"
102#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
103#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
Mike Dougherty4620cf8e2017-10-31 23:37:09104#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
105#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper_delegate.h"
pkld6e73e52017-03-08 15:56:51106#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:28107#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13108#import "ios/chrome/browser/tabs/tab.h"
109#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:19110#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:13111#import "ios/chrome/browser/tabs/tab_model.h"
112#import "ios/chrome/browser/tabs/tab_model_observer.h"
Sylvain Defresne72c530e42017-08-25 15:28:16113#import "ios/chrome/browser/tabs/tab_private.h"
sdefresnee65fd872016-12-19 13:38:13114#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
Mark Coganca30df62017-11-20 14:29:11115#import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
116#import "ios/chrome/browser/translate/language_selection_handler.h"
Rohit Rao01e0e002017-08-14 20:49:43117#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
118#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
sdefresnee65fd872016-12-19 13:38:13119#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
Eugene But35ded552017-09-13 23:31:59120#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13121#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
122#import "ios/chrome/browser/ui/background_generator.h"
123#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
Gauthier Ambard65e949b092017-11-29 08:46:20124#include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
sdefresnee65fd872016-12-19 13:38:13125#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13126#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
Cooper Knaak33f9f402017-08-09 18:04:38127#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
sdefresnee65fd872016-12-19 13:38:13128#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
Mark Cogan5e3da152017-07-11 15:57:30129#import "ios/chrome/browser/ui/commands/application_commands.h"
Mark Cogan6c58ea92017-07-06 13:08:24130#import "ios/chrome/browser/ui/commands/browser_commands.h"
edchin9badb062017-08-16 18:47:54131#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
Mark Cogandfcdea72017-07-18 13:47:38132#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13133#import "ios/chrome/browser/ui/commands/open_url_command.h"
134#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
edchin95c927072017-11-04 00:35:07135#import "ios/chrome/browser/ui/commands/show_signin_command.h"
edchin7f210cd2017-09-28 08:03:53136#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00137#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23138#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13139#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13140#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
141#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
Eugene But87a09532017-12-08 20:02:05142#import "ios/chrome/browser/ui/download/legacy_download_manager_controller.h"
Eugene But49a7c572017-12-11 20:54:15143#import "ios/chrome/browser/ui/download/pass_kit_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13144#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
145#import "ios/chrome/browser/ui/external_file_controller.h"
Louis Romerod11747a2017-10-20 20:10:35146#import "ios/chrome/browser/ui/external_search/external_search_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13147#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
148#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19149#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller.h"
150#import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_factory.h"
Kurt Horimoto803840622017-10-28 01:20:37151#import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
Kurt Horimoto06b94252017-12-08 19:45:59152#import "ios/chrome/browser/ui/fullscreen/fullscreen_scroll_end_animator.h"
153#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_element.h"
154#import "ios/chrome/browser/ui/fullscreen/fullscreen_ui_updater.h"
Kurt Horimoto62e97c72017-11-03 19:51:47155#import "ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38156#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13157#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13158#import "ios/chrome/browser/ui/key_commands_provider.h"
Kurt Horimoto1945ef42017-10-26 03:57:26159#import "ios/chrome/browser/ui/location_bar_notification_names.h"
Rohit Rao9a8ad772017-10-30 22:35:59160#import "ios/chrome/browser/ui/main/main_feature_flags.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19161#import "ios/chrome/browser/ui/main_content/main_content_ui.h"
162#import "ios/chrome/browser/ui/main_content/main_content_ui_broadcasting_util.h"
163#import "ios/chrome/browser/ui/main_content/main_content_ui_state.h"
164#import "ios/chrome/browser/ui/main_content/web_scroll_view_main_content_ui_forwarder.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10165#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13166#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42167#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13168#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27169#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
170#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13171#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37172#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
Mark Coganca30df62017-11-20 14:29:11173#import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
sdefresnee65fd872016-12-19 13:38:13174#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11175#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
176#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13177#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03178#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13179#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13180#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53181#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
sczs40443972017-09-13 19:02:39182#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13183#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
edchin9e7a1112017-11-07 18:28:03184#import "ios/chrome/browser/ui/signin_interaction/public/signin_presenter.h"
edchin7f210cd2017-09-28 08:03:53185#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13186#import "ios/chrome/browser/ui/stack_view/card_view.h"
187#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
188#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13189#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03190#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
191#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
192#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
Gauthier Ambard76d826ac2017-11-16 16:27:34193#include "ios/chrome/browser/ui/toolbar/legacy_toolbar_coordinator.h"
Kurt Horimotoea429dd2017-11-28 02:24:30194#import "ios/chrome/browser/ui/toolbar/legacy_toolbar_ui_updater.h"
sdefresnee65fd872016-12-19 13:38:13195#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
196#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
Gauthier Ambard29939db12017-10-30 16:47:31197#import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
Kurt Horimotoea429dd2017-11-28 02:24:30198#import "ios/chrome/browser/ui/toolbar/toolbar_ui.h"
Kurt Horimotoe9b6002c2017-12-04 23:19:19199#import "ios/chrome/browser/ui/toolbar/toolbar_ui_broadcasting_util.h"
sczsc2b4f152017-10-11 01:44:24200#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
Peter Laurense0b80f12017-11-21 07:52:40201#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_configuration_provider.h"
202#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_presentation_provider.h"
sczsbbad1632017-07-29 03:48:00203#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13204#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
Mark Coganca30df62017-11-20 14:29:11205#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13206#include "ios/chrome/browser/ui/ui_util.h"
207#import "ios/chrome/browser/ui/uikit_ui_util.h"
Gauthier Ambard087e3572017-12-20 12:54:47208#import "ios/chrome/browser/ui/util/named_guide.h"
gambard6a138362017-02-06 17:19:28209#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13210#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
211#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51212#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13213#import "ios/chrome/browser/web/error_page_content.h"
Danyao Wang85389a82017-10-25 18:56:27214#import "ios/chrome/browser/web/load_timing_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"
gambard9efce7a2017-02-09 18:53:17253#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13254#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
255#include "net/ssl/ssl_info.h"
256#include "net/url_request/url_request_context_getter.h"
257#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
258#include "ui/base/l10n/l10n_util.h"
259#include "ui/base/l10n/l10n_util_mac.h"
260#include "ui/base/page_transition_types.h"
261#include "url/gurl.h"
262
stkhapuginf58b10d02017-04-10 13:36:17263#if !defined(__has_feature) || !__has_feature(objc_arc)
264#error "This file requires ARC support."
265#endif
266
sdefresnee65fd872016-12-19 13:38:13267using base::UserMetricsAction;
268using bookmarks::BookmarkNode;
269
sdefresnee65fd872016-12-19 13:38:13270class InfoBarContainerDelegateIOS;
271
sdefresnee65fd872016-12-19 13:38:13272namespace {
273
274typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
275 // Note: these values must match the ContextMenuOption enum in histograms.xml.
276 ACTION_OPEN_IN_NEW_TAB = 0,
277 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
278 ACTION_COPY_LINK_ADDRESS = 2,
279 ACTION_SAVE_IMAGE = 6,
280 ACTION_OPEN_IMAGE = 7,
281 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
282 ACTION_SEARCH_BY_IMAGE = 11,
283 ACTION_OPEN_JAVASCRIPT = 21,
284 ACTION_READ_LATER = 22,
285 NUM_ACTIONS = 23,
286};
287
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28288void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13289 if (is_image) {
290 if (is_link) {
291 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
292 NUM_ACTIONS);
293 } else {
294 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
295 NUM_ACTIONS);
296 }
297 } else {
298 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
299 NUM_ACTIONS);
300 }
301}
302
edchinf5150c682017-09-18 02:50:03303// Returns the status bar background color.
304UIColor* StatusBarBackgroundColor() {
305 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
306}
307
308// Duration of the toolbar animation.
Kurt Horimoto62e97c72017-11-03 19:51:47309const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3;
edchinf5150c682017-09-18 02:50:03310
sdefresnee65fd872016-12-19 13:38:13311const CGFloat kVoiceSearchBarHeight = 59.0;
312
313// Dimensions to use when downsizing an image for search-by-image.
314const CGFloat kSearchByImageMaxImageArea = 90000.0;
315const CGFloat kSearchByImageMaxImageWidth = 600.0;
316const CGFloat kSearchByImageMaxImageHeight = 400.0;
317
sdefresnee65fd872016-12-19 13:38:13318enum HeaderBehaviour {
319 // The header moves completely out of the screen.
320 Hideable = 0,
321 // This header stays on screen and doesn't overlap with the content.
322 Visible,
323 // This header stay on screen and covers part of the content.
324 Overlap
325};
326
sdefresnee65fd872016-12-19 13:38:13327const CGFloat kIPadFindBarOverlap = 11;
328
329bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24330 // Most URLs are allowed in incognito; the following is an exception.
331 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13332}
333
edchineeb4d422017-10-02 17:39:36334// Snackbar category for browser view controller.
335NSString* const kBrowserViewControllerSnackbarCategory =
336 @"BrowserViewControllerSnackbarCategory";
337
rohitrao005a6432017-03-16 20:52:42338} // namespace
sdefresnee65fd872016-12-19 13:38:13339
stkhapugin952ecef2017-04-11 12:11:45340#pragma mark - HeaderDefinition helper
341
342@interface HeaderDefinition : NSObject
343
344// The header view.
345@property(nonatomic, strong) UIView* view;
346// How to place the view, and its behaviour when the headers move.
347@property(nonatomic, assign) HeaderBehaviour behaviour;
348// Reduces the height of a header to adjust for shadows.
349@property(nonatomic, assign) CGFloat heightAdjustement;
350// Nudges that particular header up by this number of points.
351@property(nonatomic, assign) CGFloat inset;
352
353- (instancetype)initWithView:(UIView*)view
354 headerBehaviour:(HeaderBehaviour)behaviour
355 heightAdjustment:(CGFloat)heightAdjustment
356 inset:(CGFloat)inset;
357
358+ (instancetype)definitionWithView:(UIView*)view
359 headerBehaviour:(HeaderBehaviour)behaviour
360 heightAdjustment:(CGFloat)heightAdjustment
361 inset:(CGFloat)inset;
362
363@end
364
365@implementation HeaderDefinition
366@synthesize view = _view;
367@synthesize behaviour = _behaviour;
368@synthesize heightAdjustement = _heightAdjustement;
369@synthesize inset = _inset;
370
371+ (instancetype)definitionWithView:(UIView*)view
372 headerBehaviour:(HeaderBehaviour)behaviour
373 heightAdjustment:(CGFloat)heightAdjustment
374 inset:(CGFloat)inset {
375 return [[self alloc] initWithView:view
376 headerBehaviour:behaviour
377 heightAdjustment:heightAdjustment
378 inset:inset];
379}
380
381- (instancetype)initWithView:(UIView*)view
382 headerBehaviour:(HeaderBehaviour)behaviour
383 heightAdjustment:(CGFloat)heightAdjustment
384 inset:(CGFloat)inset {
385 self = [super init];
386 if (self) {
387 _view = view;
388 _behaviour = behaviour;
389 _heightAdjustement = heightAdjustment;
390 _inset = inset;
391 }
392 return self;
393}
394
395@end
396
397#pragma mark - BVC
398
Rohit Rao01e0e002017-08-14 20:49:43399@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43400 AppRatingPromptDelegate,
Gauthier Ambard65e949b092017-11-29 08:46:20401 BookmarkModelBridgeObserver,
Mike Dougherty4620cf8e2017-10-31 23:37:09402 CaptivePortalDetectorTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13403 CRWNativeContentProvider,
404 CRWWebStateDelegate,
405 DialogPresenterDelegate,
Kurt Horimoto06b94252017-12-08 19:45:59406 FullscreenUIElement,
Kurt Horimoto62e97c72017-11-03 19:51:47407 LegacyFullscreenControllerDelegate,
Mark Cogan80aa28d2017-11-30 13:11:34408 InfobarContainerStateDelegate,
sdefresnee65fd872016-12-19 13:38:13409 KeyCommandsPlumbing,
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57410 NetExportTabHelperDelegate,
Kurt Horimotoe9b6002c2017-12-04 23:19:19411 MainContentUI,
edchincd32fdf2017-10-25 12:45:45412 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13413 MFMailComposeViewControllerDelegate,
414 NewTabPageControllerObserver,
415 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27416 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13417 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55418 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13419 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11420 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59421 RepostFormTabHelperDelegate,
Gauthier Ambard29939db12017-10-30 16:47:31422 SideSwipeControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13423 SKStoreProductViewControllerDelegate,
424 SnapshotOverlayProvider,
425 StoreKitLauncher,
edchin9e7a1112017-11-07 18:28:03426 SigninPresenter,
sdefresnee65fd872016-12-19 13:38:13427 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19428 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38429 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13430 TabModelObserver,
431 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03432 TabStripPresentation,
Peter Laurense0b80f12017-11-21 07:52:40433 ToolsMenuConfigurationProvider,
sdefresnee65fd872016-12-19 13:38:13434 UIGestureRecognizerDelegate,
435 UpgradeCenterClientProtocol,
436 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04437 VoiceSearchBarOwner,
438 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13439 // The dependency factory passed on initialization. Used to vend objects used
440 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27441 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13442
443 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27444 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13445
sczsf1620e52017-10-02 22:54:46446 // Facade objects used by |_toolbarCoordinator|.
447 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13448 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
449 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
450
sdefresnee65fd872016-12-19 13:38:13451 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27452 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13453
454 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27455 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13456
457 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27458 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13459
460 // Handles presentation of JavaScript dialogs.
461 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
462
justincohen75011c32017-04-28 16:31:39463 // Handles command dispatching.
464 CommandDispatcher* _dispatcher;
465
sdefresnee65fd872016-12-19 13:38:13466 // Keyboard commands provider. It offloads most of the keyboard commands
467 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27468 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13469
sdefresnee65fd872016-12-19 13:38:13470 // Used to inject Javascript implementing the PaymentRequest API and to
471 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27472 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13473
sdefresnee65fd872016-12-19 13:38:13474 // Used to display the Voice Search UI. Nil if not visible.
475 scoped_refptr<VoiceSearchController> _voiceSearchController;
476
gambard6299cc1d2017-02-21 13:06:03477 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27478 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03479
sdefresnee65fd872016-12-19 13:38:13480 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27481 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13482
sdefresnee65fd872016-12-19 13:38:13483 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27484 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13485
486 // Records the set of domains for which full screen alert has already been
487 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27488 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13489
490 // Adapter to let BVC be the delegate for WebState.
491 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
492
493 // YES if new tab is animating in.
494 BOOL _inNewTabAnimation;
495
496 // YES if Voice Search should be started when the new tab animation is
497 // finished.
498 BOOL _startVoiceSearchAfterNewTabAnimation;
499
500 // YES if the user interacts with the location bar.
501 BOOL _locationBarHasFocus;
502 // YES if a load was cancelled due to typing in the location bar.
503 BOOL _locationBarEditCancelledLoad;
504 // YES if waiting for a foreground tab due to expectNewForegroundTab.
505 BOOL _expectingForegroundTab;
506
Sylvain Defresne41170aa2017-06-15 10:25:20507 // Whether or not -shutdown has been called.
508 BOOL _isShutdown;
509
sdefresnee65fd872016-12-19 13:38:13510 // The ChromeBrowserState associated with this BVC.
511 ios::ChromeBrowserState* _browserState; // weak
512
513 // Whether or not Incognito* is enabled.
514 BOOL _isOffTheRecord;
515
516 // The last point within |_contentArea| that's received a touch.
517 CGPoint _lastTapPoint;
518
519 // The time at which |_lastTapPoint| was most recently set.
520 CFTimeInterval _lastTapTime;
521
522 // A single infobar container handles all infobars in all tabs. It keeps
523 // track of infobars for current tab (accessed via infobar helper of
524 // the current tab).
525 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
526
527 // Bridge class to deliver container change notifications to BVC.
528 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
529
530 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37531 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27532 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13533
534 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11535 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13536
sdefresnee65fd872016-12-19 13:38:13537 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27538 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13539
540 // Bridge to register for bookmark changes.
Gauthier Ambard65e949b092017-11-29 08:46:20541 std::unique_ptr<bookmarks::BookmarkModelBridge> _bookmarkModelBridge;
sdefresnee65fd872016-12-19 13:38:13542
543 // Cached pointer to the bookmarks model.
544 bookmarks::BookmarkModel* _bookmarkModel; // weak
545
546 // The controller that shows the bookmarking UI after the user taps the star
547 // button.
stkhapuginc9eee7b2017-04-10 15:49:27548 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13549
sdefresnee65fd872016-12-19 13:38:13550 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27551 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13552
Eugene But56efc322017-08-11 14:03:44553 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28554 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13555
556 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27557 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13558
Jean-François Geyelin3d47c212017-08-03 09:24:09559 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27560 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13561
Rohit Rao01e0e002017-08-14 20:49:43562 // Coordinator for the share menu (Activity Services).
563 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
564
sdefresnee65fd872016-12-19 13:38:13565 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27566 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38567
Rohit Raocda0a992017-08-16 15:37:11568 // Coordinator for the QR scanner.
569 QRScannerLegacyCoordinator* _qrScannerCoordinator;
570
sczsdd860eba2017-08-10 01:55:38571 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13572 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53573
574 // Coordinator for displaying Sad Tab.
575 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27576
577 // Coordinator for Page Info UI.
578 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59579
580 // Coordinator for displaying Repost Form dialog.
581 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22582
edchin7f210cd2017-09-28 08:03:53583 // Coordinator for displaying snackbars.
584 SnackbarCoordinator* _snackbarCoordinator;
585
sczsf1620e52017-10-02 22:54:46586 // Coordinator for the toolbar.
587 LegacyToolbarCoordinator* _toolbarCoordinator;
588
Kurt Horimotoea429dd2017-11-28 02:24:30589 // The toolbar UI updater for the toolbar managed by |_toolbarCoordinator|.
590 LegacyToolbarUIUpdater* _toolbarUIUpdater;
591
Kurt Horimotoe9b6002c2017-12-04 23:19:19592 // The main content UI updater for the content displayed by this BVC.
593 MainContentUIStateUpdater* _mainContentUIUpdater;
594
595 // The forwarder for web scroll view interation events.
596 WebScrollViewMainContentUIForwarder* _webMainContentUIForwarder;
597
Kurt Horimoto06b94252017-12-08 19:45:59598 // The updater that adjusts the toolbar's layout for fullscreen events.
599 std::unique_ptr<FullscreenUIUpdater> _fullscreenUIUpdater;
600
Louis Romerod11747a2017-10-20 20:10:35601 // Coordinator for the External Search UI.
602 ExternalSearchCoordinator* _externalSearchCoordinator;
603
Mark Coganca30df62017-11-20 14:29:11604 // Coordinator for the language selection UI.
605 LanguageSelectionCoordinator* _languageSelectionCoordinator;
606
Eugene But49a7c572017-12-11 20:54:15607 // Coordinator for the PassKit UI presentation.
608 PassKitCoordinator* _passKitCoordinator;
609
Justin Cohenb3170c32017-09-19 01:55:22610 // Fake status bar view used to blend the toolbar into the status bar.
611 UIView* _fakeStatusBarView;
Sylvain Defresnef5d2d952017-11-14 11:15:31612
613 // Stores whether the Tab currently inserted was a pre-rendered Tab. This
614 // is used to determine whether the pre-rendering animation should be played
615 // or not.
616 BOOL _insertedTabWasPrerenderedTab;
sdefresnee65fd872016-12-19 13:38:13617}
618
619// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17620@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13621// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17622@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13623// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17624@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13625// Whether the current tab can enable the request desktop menu item.
626@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
627// Whether the sharing menu should be enabled.
628@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
629// Helper method to check web controller canShowFindBar method.
630@property(nonatomic, assign, readonly) BOOL canShowFindBar;
631// Whether the controller's view is currently available.
632// YES from viewWillAppear to viewWillDisappear.
633@property(nonatomic, assign, getter=isVisible) BOOL visible;
634// Whether the controller's view is currently visible.
635// YES from viewDidAppear to viewWillDisappear.
636@property(nonatomic, assign) BOOL viewVisible;
Kurt Horimotoe9b6002c2017-12-04 23:19:19637// Whether the controller should broadcast its UI.
638@property(nonatomic, assign, getter=isBroadcasting) BOOL broadcasting;
sdefresnee65fd872016-12-19 13:38:13639// Whether the controller is currently dismissing a presented view controller.
640@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
641// Returns YES if the toolbar has not been scrolled out by fullscreen.
642@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
643 BOOL toolbarOnScreen;
644// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12645@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13646// Whether BVC prefers to hide the status bar. This value is used to determine
647// the response from the |prefersStatusBarHidden| method.
648@property(nonatomic, assign) BOOL hideStatusBar;
649// Whether the VoiceSearchBar should be displayed.
650@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
651// Coordinator for displaying a modal overlay with activity indicator to prevent
652// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17653@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13654 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39655// A block to be run when the |tabWasAdded:| method completes the animation
656// for the presentation of a new tab. Can be used to record performance metrics.
657@property(nonatomic, strong, nullable)
658 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42659// Coordinator for Recent Tabs.
660@property(nonatomic, strong)
661 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03662// Coordinator for tablet tab strip.
663@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
664// A weak reference to the view of the tab strip on tablet.
665@property(nonatomic, weak) UIView* tabStripView;
sdefresnee65fd872016-12-19 13:38:13666
liaoyukeea9f3ee62017-03-07 22:05:39667// The user agent type used to load the currently visible page. User agent type
668// is NONE if there is no visible page or visible page is a native page.
669@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
670
stkhapugin952ecef2017-04-11 12:11:45671// Returns the header views, all the chrome on top of the page, including the
672// ones that cannot be scrolled off screen by full screen.
673@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
674
Cooper Knaakd0a974cd2017-08-10 18:05:47675// Used to display the new tab tip in-product help promotion bubble. |nil| if
676// the new tab tip bubble has not yet been presented. Once the bubble is
677// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38678@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47679 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38680
Helen Yang9175bd52017-08-12 00:28:40681// Used to display the new incognito tab tip in-product help promotion bubble.
682@property(nonatomic, strong)
683 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
684
Justin Cohen9fe9ef672017-12-01 20:37:43685// Vertical offset for fullscreen toolbar.
686@property(nonatomic, strong) NSLayoutConstraint* toolbarOffsetConstraint;
687
sdefresnee65fd872016-12-19 13:38:13688// BVC initialization:
689// If the BVC is initialized with a valid browser state & tab model immediately,
690// the path is straightforward: functionality is enabled, and the UI is built
691// when -viewDidLoad is called.
692// If the BVC is initialized without a browser state or tab model, the tab model
693// and browser state may or may not be provided before -viewDidLoad is called.
694// In most cases, they will not, to improve startup performance.
695// In order to handle this, initialization of various aspects of BVC have been
696// broken out into the following functions, which have expectations (enforced
697// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
698
699// Registers for notifications.
700- (void)registerForNotifications;
701// Called when a tab is starting to load. If it's a link click or form
702// submission, the user is navigating away from any entries in the forward
703// history. Tell the toolbar so it can update the UI appropriately.
704// See the warning on [Tab webWillStartLoadingURL] about invocation of this
705// method sequence by malicious pages.
706- (void)pageLoadStarting:(NSNotification*)notify;
707// Called when a tab actually starts loading.
708- (void)pageLoadStarted:(NSNotification*)notify;
709// Called when a tab finishes loading. Update the Omnibox with the url and
710// stop any page load progess display.
711- (void)pageLoadComplete:(NSNotification*)notify;
712// Called when a tab is deselected in the model.
713// This notification also occurs when a tab is closed.
714- (void)tabDeselected:(NSNotification*)notify;
715// Animates sliding current tab and rotate-entering new tab while new tab loads
716// in background on the iPhone only.
717- (void)tabWasAdded:(NSNotification*)notify;
718
719// Updates non-view-related functionality with the given browser state and tab
720// model.
721// Does not matter whether or not the view has been loaded.
722- (void)updateWithTabModel:(TabModel*)model
723 browserState:(ios::ChromeBrowserState*)browserState;
724// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
725// the status bar to mimic this layout.
726- (void)installFakeStatusBar;
727// Builds the UI parts of tab strip and the toolbar. Does not matter whether
728// or not browser state and tab model are valid.
729- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50730// Sets up the constraints on the toolbar.
731- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13732// Updates view-related functionality with the given tab model and browser
733// state. The view must have been loaded. Uses |_browserState| and |_model|.
734- (void)addUIFunctionalityForModelAndBrowserState;
Justin Cohen4eeada32017-11-13 18:21:28735// Sets the correct frame and hierarchy for subviews and helper views. Only
736// insert views on |initialLayout|.
737- (void)setUpViewLayout:(BOOL)initialLayout;
sdefresnee65fd872016-12-19 13:38:13738// Makes |tab| the currently visible tab, displaying its view. Calls
739// -selectedTabChanged on the toolbar only if |newSelection| is YES.
740- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
741// Initializes the bookmark interaction controller if not already initialized.
742- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13743// Add all delegates to the provided |tab|.
744- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14745// Remove delegates from the provided |tab|.
746- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13747// Closes the current tab, with animation if applicable.
748- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13749// Show the bookmarks page.
750- (void)showAllBookmarks;
751// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23752- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13753// Dismisses the "rate this app" dialog.
754- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32755// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13756- (BOOL)isTabNativePage:(Tab*)tab;
757// Returns the view to use when animating a page in or out, positioning it to
758// fill the content area but not actually adding it to the view hierarchy.
759- (UIImageView*)pageOpenCloseAnimationView;
sdefresnee65fd872016-12-19 13:38:13760// Updates the toolbar display based on the current tab.
761- (void)updateToolbar;
Kurt Horimotoe9b6002c2017-12-04 23:19:19762// Starts or stops broadcasting the toolbar UI and main content UI depending on
763// whether the BVC is visible and active.
764- (void)updateBroadcastState;
sdefresnee65fd872016-12-19 13:38:13765// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12766// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13767- (void)updateDialogPresenterActiveState;
768// Dismisses popups and modal dialogs that are displayed above the BVC upon size
769// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
770// is performed.
771// TODO(crbug.com/522721): Support size changes for all popups and modal
772// dialogs.
773- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47774
775// Returns a bubble associated with an in-product help promotion if
776// it is valid to show the promotion and |nil| otherwise. |feature| is the
777// base::Feature object associated with the given promotion. |direction| is the
778// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21779// arrow on the button. |text| is the text displayed by the bubble. This method
780// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47781- (BubbleViewControllerPresenter*)
782bubblePresenterForFeature:(const base::Feature&)feature
783 direction:(BubbleArrowDirection)direction
784 alignment:(BubbleAlignment)alignment
785 text:(NSString*)text;
786
Cooper Knaak120cee5e2017-08-10 20:57:00787// Waits to present a bubble associated with the new tab tip in-product help
788// promotion until the feature engagement tracker database is fully initialized.
789// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
790// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11791// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21792// determines it is not valid to present it. This method requires that
793// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00794- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11795// Optionally presents a bubble associated with the new tab tip in-product help
796// promotion. If the feature engagement tracker determines it is valid to show
797// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
798// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21799// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
800// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00801- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40802// Waits to present a bubble associated with the new incognito tab tip
803// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21804// fully initialized. This method requires that |self.browserState| is
805// not NULL.
Helen Yang9175bd52017-08-12 00:28:40806- (void)presentNewIncognitoTabTipBubbleOnInitialized;
807// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21808// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40809- (void)presentNewIncognitoTabTipBubble;
Gregory Chatzinoff541b8642017-10-25 00:25:21810// Presents the New Tab Tip or New Incognito Tab Tip Bubble if one is
811// eligible. Only one can be eligible per session (as enforced by the
812// FeatureEngagementTracker). If neither is eligible, neither bubble is
813// presented. This method requires that |self.browserState| is not NULL.
814- (void)presentBubblesIfEligible;
Gregory Chatzinoffe205f44642017-12-19 17:54:03815// Returns whether |tab| is scrolled to the top.
816- (BOOL)isTabScrolledToTop:(Tab*)tab;
Cooper Knaak120cee5e2017-08-10 20:57:00817
sdefresnee65fd872016-12-19 13:38:13818// Update find bar with model data. If |shouldFocus| is set to YES, the text
819// field will become first responder.
820- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13821// Hide find bar.
822- (void)hideFindBarWithAnimation:(BOOL)animate;
823// Shows find bar. If |selectText| is YES, all text inside the Find Bar
824// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
825// set to be first responder.
826- (void)showFindBarWithAnimation:(BOOL)animate
827 selectText:(BOOL)selectText
828 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36829
sdefresnee65fd872016-12-19 13:38:13830// Adds a CardView on top of the contentArea either taking the size of the full
831// screen or just the size of the space under the header.
832// Returns the CardView that was added.
833- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
834// Called when either a tab finishes loading or when a tab with finished content
835// is added directly to the model via pre-rendering. The tab must be non-nil and
836// must be a member of the tab model controlled by this BrowserViewController.
837- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
838// Evaluates Javascript asynchronously using the current page context.
839- (void)openJavascript:(NSString*)javascript;
edchineeb4d422017-10-02 17:39:36840// Shows a self-dismissing snackbar displaying |message|.
841- (void)showSnackbar:(NSString*)message;
sdefresnee65fd872016-12-19 13:38:13842// Induces an intentional crash in the browser process.
843- (void)induceBrowserCrash;
844// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17845- (void)managePermissionAndSaveImage:(NSData*)data
846 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13847// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24848// saved as a temporary file on disk then saved in photos. Saving will happen
849// on a background sequence and the completion block will be invoked on that
850// sequence.
851- (void)saveImage:(NSData*)data
852 withFileExtension:(NSString*)fileExtension
853 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13854// Called when Chrome has been denied access to the photos or videos and the
855// user can change it.
856// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
857// settings. Dismiss previous alert if it has not been dismissed yet.
858- (void)displayImageErrorAlertWithSettingsOnMainQueue;
859// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
860// previous alert if it has not been dismissed yet.
861- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
862// Called when Chrome has been denied access to the photos or videos and the
863// user cannot change it.
864// Shows a privacy alert on the main queue, with errorContent as the message.
865// Dismisses previous alert if it has not been dismissed yet.
866- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
867// Called with the results of saving a picture in the photo album. If error is
868// nil the save succeeded.
869- (void)finishSavingImageWithError:(NSError*)error;
870// Provides a view that encompasses currently displayed infobar(s) or nil
871// if no infobar is presented.
872- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
873// Returns a vertical infobar offset relative to the tab content.
874- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
875// Provides a view that encompasses the voice search bar if it's displayed or
876// nil if the voice search bar isn't displayed.
877- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
878// Returns a vertical voice search bar offset relative to the tab content.
879- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
880// Lazily instantiates |_voiceSearchController|.
881- (void)ensureVoiceSearchControllerCreated;
882// Lazily instantiates |_voiceSearchBar| and adds it to the view.
883- (void)ensureVoiceSearchBarCreated;
884// Shows/hides the voice search bar.
885- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
886// The LogoAnimationControllerOwner to be used for the next logo transition
887// animation.
888- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13889// Returns the footer view if one exists (e.g. the voice search bar).
890- (UIView*)footerView;
891// Returns the height of the header view for the tab model's current tab.
892- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13893// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45894- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13895 atOffset:(CGFloat)headerOffset;
896// Returns the y coordinate for the footer's frame when animating the footer
897// in/out of fullscreen.
898- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
899// Called when the animation for setting the header view's offset is finished.
900// |completed| should indicate if the animation finished completely or was
901// interrupted. |offset| should indicate the header offset after the animation.
902// |dragged| should indicate if the header moved due to the user dragging.
Kurt Horimoto62e97c72017-11-03 19:51:47903- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:13904 headerAnimationCompleted:(BOOL)completed
905 offset:(CGFloat)offset;
906// Performs a search with the image at the given url. The referrer is used to
907// download the image.
908- (void)searchByImageAtURL:(const GURL&)url
909 referrer:(const web::Referrer)referrer;
910// Saves the image at the given URL on the system's album. The referrer is used
911// to download the image.
912- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
913
Mark Cogandfcdea72017-07-18 13:47:38914// Record the last tap point based on the |originPoint| (if any) passed in
915// |command|.
916- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13917// Get return the last stored |_lastTapPoint| if it's been set within the past
918// second.
919- (CGPoint)lastTapPoint;
920// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
921- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
922// Returns the native controller being used by |tab|'s web controller.
923- (id)nativeControllerForTab:(Tab*)tab;
924// Installs the BVC as overscroll actions controller of |nativeContent| if
925// needed. Sets the style of the overscroll actions toolbar.
926- (void)setOverScrollActionControllerToStaticNativeContent:
927 (StaticHtmlNativeContent*)nativeContent;
928// Whether the BVC should declare keyboard commands.
929- (BOOL)shouldRegisterKeyboardCommands;
930// Adds the given url to the reading list.
931- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
932@end
933
sdefresnee65fd872016-12-19 13:38:13934@implementation BrowserViewController
935
936@synthesize contentArea = _contentArea;
937@synthesize typingShield = _typingShield;
938@synthesize active = _active;
939@synthesize visible = _visible;
940@synthesize viewVisible = _viewVisible;
Kurt Horimotoe9b6002c2017-12-04 23:19:19941@synthesize broadcasting = _broadcasting;
sdefresnee65fd872016-12-19 13:38:13942@synthesize dismissingModal = _dismissingModal;
943@synthesize hideStatusBar = _hideStatusBar;
944@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
945@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39946@synthesize foregroundTabWasAddedCompletionBlock =
947 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:40948@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
949@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:42950@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03951@synthesize tabStripCoordinator = _tabStripCoordinator;
952@synthesize tabStripView = _tabStripView;
Justin Cohen9fe9ef672017-12-01 20:37:43953@synthesize toolbarOffsetConstraint = _toolbarOffsetConstraint;
sdefresnee65fd872016-12-19 13:38:13954
955#pragma mark - Object lifecycle
956
Mark Cogan5e3da152017-07-11 15:57:30957- (instancetype)
958 initWithTabModel:(TabModel*)model
959 browserState:(ios::ChromeBrowserState*)browserState
960 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
961applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:13962 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
963 if (self) {
964 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17965
stkhapuginc9eee7b2017-04-10 15:49:27966 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:27967 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
968 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39969 _dispatcher = [[CommandDispatcher alloc] init];
970 [_dispatcher startDispatchingToTarget:self
971 forProtocol:@protocol(UrlLoader)];
972 [_dispatcher startDispatchingToTarget:self
973 forProtocol:@protocol(WebToolbarDelegate)];
974 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:24975 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:30976 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
977 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:32978 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
979 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
980 // dispatched to the endpoint as well. Since this is potentially
981 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
982 DCHECK(!applicationCommandEndpoint ||
983 [applicationCommandEndpoint
984 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
985 [_dispatcher
986 startDispatchingToTarget:applicationCommandEndpoint
987 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:39988
edchin7f210cd2017-09-28 08:03:53989 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
990 _snackbarCoordinator.dispatcher = _dispatcher;
991 [_snackbarCoordinator start];
992
Mark Coganca30df62017-11-20 14:29:11993 _languageSelectionCoordinator =
994 [[LanguageSelectionCoordinator alloc] initWithBaseViewController:self];
995 _languageSelectionCoordinator.presenter =
996 [[VerticalAnimationContainer alloc] init];
997
Eugene But49a7c572017-12-11 20:54:15998 _passKitCoordinator =
999 [[PassKitCoordinator alloc] initWithBaseViewController:self];
1000
sdefresnee65fd872016-12-19 13:38:131001 _javaScriptDialogPresenter.reset(
1002 new JavaScriptDialogPresenterImpl(_dialogPresenter));
1003 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
1004 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:151005 [[UpgradeCenter sharedInstance] registerClient:self
1006 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131007 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131008 if (model && browserState)
1009 [self updateWithTabModel:model browserState:browserState];
1010 if ([[NSUserDefaults standardUserDefaults]
1011 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271012 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131013 }
1014 }
1015 return self;
1016}
1017
1018- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1019 bundle:(NSBundle*)nibBundleOrNil {
1020 NOTREACHED();
1021 return nil;
1022}
1023
1024- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1025 NOTREACHED();
1026 return nil;
1027}
1028
1029- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201030 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131031}
1032
1033#pragma mark - Accessibility
1034
1035- (BOOL)accessibilityPerformEscape {
1036 [self dismissPopups];
1037 return YES;
1038}
1039
1040#pragma mark - Properties
1041
edchin3365c7d2017-09-01 22:20:371042- (id<ApplicationCommands,
1043 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371044 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531045 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371046 UrlLoader,
1047 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561048 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531049 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1050 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241051}
1052
sdefresnee65fd872016-12-19 13:38:131053- (void)setActive:(BOOL)active {
1054 if (_active == active) {
1055 return;
1056 }
1057 _active = active;
1058
1059 // If not active, display an activity indicator overlay over the view to
1060 // prevent interaction with the web page.
1061 // TODO(crbug.com/637093): This coordinator should be managed by the
1062 // coordinator used to present BrowserViewController, when implemented.
1063 if (active) {
1064 [self.activityOverlayCoordinator stop];
1065 self.activityOverlayCoordinator = nil;
1066 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171067 self.activityOverlayCoordinator =
1068 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131069 [self.activityOverlayCoordinator start];
1070 }
1071
1072 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091073 ActiveStateManager* active_state_manager =
1074 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131075 active_state_manager->SetActive(active);
1076 }
1077
1078 [_model setWebUsageEnabled:active];
1079 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191080 [self updateBroadcastState];
sdefresnee65fd872016-12-19 13:38:131081
1082 if (active) {
1083 // Make sure the tab (if any; it's possible to get here without a current
1084 // tab if the caller is about to create one) ends up on screen completely.
1085 Tab* currentTab = [_model currentTab];
1086 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441087 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131088 if (_expectingForegroundTab)
1089 [currentTab.webController setOverlayPreviewMode:YES];
1090 if (currentTab)
1091 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341092 } else {
1093 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131094 }
sdefresnee65fd872016-12-19 13:38:131095 [_paymentRequestManager enablePaymentRequest:active];
1096
1097 [self setNeedsStatusBarAppearanceUpdate];
1098}
1099
1100- (void)setPrimary:(BOOL)primary {
1101 [_model setPrimary:primary];
1102 if (primary) {
1103 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191104 [self updateBroadcastState];
sdefresnee65fd872016-12-19 13:38:131105 } else {
1106 self.dialogPresenter.active = false;
1107 }
1108}
1109
1110- (BOOL)isPlayingTTS {
1111 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1112}
1113
sdefresne6165c8742017-01-16 15:42:021114- (ios::ChromeBrowserState*)browserState {
1115 return _browserState;
1116}
1117
1118- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271119 return _model;
sdefresne6165c8742017-01-16 15:42:021120}
1121
sdefresnee65fd872016-12-19 13:38:131122- (SideSwipeController*)sideSwipeController {
1123 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271124 _sideSwipeController =
1125 [[SideSwipeController alloc] initWithTabModel:_model
1126 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131127 [_sideSwipeController setSnapshotDelegate:self];
Gauthier Ambard29939db12017-10-30 16:47:311128 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sdefresnee65fd872016-12-19 13:38:131129 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031130 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131131 }
1132 return _sideSwipeController;
1133}
1134
sdefresnee65fd872016-12-19 13:38:131135- (DialogPresenter*)dialogPresenter {
1136 return _dialogPresenter;
1137}
1138
sdefresnee65fd872016-12-19 13:38:131139- (BOOL)canUseDesktopUserAgent {
1140 Tab* tab = [_model currentTab];
1141 if ([self isTabNativePage:tab])
1142 return NO;
1143
1144 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441145 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131146}
1147
1148// Whether the sharing menu should be shown.
1149- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191150 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251151 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131152}
1153
1154- (BOOL)canShowFindBar {
1155 // Make sure web controller can handle find in page.
1156 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421157 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131158 return NO;
rohitrao005a6432017-03-16 20:52:421159 }
sdefresnee65fd872016-12-19 13:38:131160
rohitrao005a6432017-03-16 20:52:421161 auto* helper = FindTabHelper::FromWebState(tab.webState);
1162 return (helper && helper->CurrentPageSupportsFindInPage() &&
1163 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131164}
1165
liaoyukeea9f3ee62017-03-07 22:05:391166- (web::UserAgentType)userAgentType {
1167 web::WebState* webState = [_model currentTab].webState;
1168 if (!webState)
1169 return web::UserAgentType::NONE;
1170 web::NavigationItem* visibleItem =
1171 webState->GetNavigationManager()->GetVisibleItem();
1172 if (!visibleItem)
1173 return web::UserAgentType::NONE;
1174
1175 return visibleItem->GetUserAgentType();
1176}
1177
sdefresnee65fd872016-12-19 13:38:131178- (void)setVisible:(BOOL)visible {
1179 if (_visible == visible)
1180 return;
Peter Laurense0b80f12017-11-21 07:52:401181
sdefresnee65fd872016-12-19 13:38:131182 _visible = visible;
1183}
1184
1185- (void)setViewVisible:(BOOL)viewVisible {
1186 if (_viewVisible == viewVisible)
1187 return;
1188 _viewVisible = viewVisible;
1189 self.visible = viewVisible;
1190 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191191 [self updateBroadcastState];
1192}
1193
1194- (void)setBroadcasting:(BOOL)broadcasting {
1195 if (_broadcasting == broadcasting)
1196 return;
1197 _broadcasting = broadcasting;
1198 if (base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
1199 // TODO(crbug.com/790886): Use the Browser's broadcaster once Browsers are
1200 // supported.
Kurt Horimoto06b94252017-12-08 19:45:591201 FullscreenController* fullscreenController =
1202 FullscreenControllerFactory::GetInstance()->GetForBrowserState(
1203 _browserState);
1204 ChromeBroadcaster* broadcaster = fullscreenController->broadcaster();
Kurt Horimotoe9b6002c2017-12-04 23:19:191205 if (_broadcasting) {
1206 _toolbarUIUpdater = [[LegacyToolbarUIUpdater alloc]
1207 initWithToolbarUI:[[ToolbarUIState alloc] init]
1208 toolbarOwner:self
1209 webStateList:[_model webStateList]];
1210 [_toolbarUIUpdater startUpdating];
1211 StartBroadcastingToolbarUI(_toolbarUIUpdater.toolbarUI, broadcaster);
Kurt Horimoto06b94252017-12-08 19:45:591212
Kurt Horimotoe9b6002c2017-12-04 23:19:191213 _mainContentUIUpdater = [[MainContentUIStateUpdater alloc]
1214 initWithState:[[MainContentUIState alloc] init]];
1215 _webMainContentUIForwarder = [[WebScrollViewMainContentUIForwarder alloc]
1216 initWithUpdater:_mainContentUIUpdater
1217 webStateList:[_model webStateList]];
1218 StartBroadcastingMainContentUI(self, broadcaster);
Kurt Horimoto06b94252017-12-08 19:45:591219
1220 _fullscreenUIUpdater = base::MakeUnique<FullscreenUIUpdater>(self);
1221 fullscreenController->AddObserver(_fullscreenUIUpdater.get());
1222
1223 fullscreenController->SetWebStateList([_model webStateList]);
Kurt Horimotoe9b6002c2017-12-04 23:19:191224 } else {
1225 StopBroadcastingToolbarUI(broadcaster);
1226 StopBroadcastingMainContentUI(broadcaster);
1227 [_toolbarUIUpdater stopUpdating];
1228 _toolbarUIUpdater = nil;
1229 _mainContentUIUpdater = nil;
1230 [_webMainContentUIForwarder disconnect];
1231 _webMainContentUIForwarder = nil;
Kurt Horimoto06b94252017-12-08 19:45:591232 fullscreenController->RemoveObserver(_fullscreenUIUpdater.get());
1233 _fullscreenUIUpdater = nullptr;
1234 fullscreenController->SetWebStateList(nullptr);
Kurt Horimotoe9b6002c2017-12-04 23:19:191235 }
1236 }
sdefresnee65fd872016-12-19 13:38:131237}
1238
1239- (BOOL)isToolbarOnScreen {
1240 return [self headerHeight] - [self currentHeaderOffset] > 0;
1241}
1242
kkhorimotoa44349c12017-04-12 23:02:121243- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1244 if (_inNewTabAnimation == inNewTabAnimation)
1245 return;
1246 _inNewTabAnimation = inNewTabAnimation;
1247 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191248 [self updateBroadcastState];
kkhorimotoa44349c12017-04-12 23:02:121249}
1250
sdefresnee65fd872016-12-19 13:38:131251- (BOOL)isInNewTabAnimation {
1252 return _inNewTabAnimation;
1253}
1254
1255- (BOOL)shouldShowVoiceSearchBar {
1256 // On iPads, the voice search bar should only be shown for regular horizontal
1257 // size class configurations. It should always be shown for voice search
1258 // results Tabs on iPhones, including configurations with regular horizontal
1259 // size classes (i.e. landscape iPhone 6 Plus).
1260 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1261 UIUserInterfaceSizeClassCompact;
1262 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1263 (!IsIPadIdiom() || compactWidth);
1264}
1265
1266- (void)setHideStatusBar:(BOOL)hideStatusBar {
1267 if (_hideStatusBar == hideStatusBar)
1268 return;
1269 _hideStatusBar = hideStatusBar;
1270 [self setNeedsStatusBarAppearanceUpdate];
1271}
1272
1273#pragma mark - IBActions
1274
1275- (void)shieldWasTapped:(id)sender {
sczsf1620e52017-10-02 22:54:461276 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131277}
1278
Cooper Knaakd0a974cd2017-08-10 18:05:471279- (void)userEnteredTabSwitcher {
1280 if ([self.tabTipBubblePresenter isUserEngaged]) {
1281 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1282 }
1283}
1284
Cooper Knaake963d6702017-08-11 21:03:111285- (void)presentBubblesIfEligible {
1286 [self presentNewTabTipBubbleOnInitialized];
Gregory Chatzinoff56635192017-12-06 02:11:261287 [self presentNewIncognitoTabTipBubbleOnInitialized];
Cooper Knaake963d6702017-08-11 21:03:111288}
1289
sdefresnee65fd872016-12-19 13:38:131290#pragma mark - UIViewController methods
1291
1292// Perform additional set up after loading the view, typically from a nib.
1293- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091294 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281295 initialViewsRect.origin.y += StatusBarHeight();
1296 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131297 UIViewAutoresizing initialViewAutoresizing =
1298 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1299
stkhapuginf58b10d02017-04-10 13:36:171300 self.contentArea =
1301 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131302 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171303 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131304 self.typingShield.autoresizingMask = initialViewAutoresizing;
Justin Cohen41b1f382017-12-04 15:22:081305 self.typingShield.accessibilityIdentifier = @"Typing Shield";
1306 self.typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1307
sdefresnee65fd872016-12-19 13:38:131308 [self.typingShield addTarget:self
1309 action:@selector(shieldWasTapped:)
1310 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131311 self.view.autoresizingMask = initialViewAutoresizing;
1312 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1313 [self.view addSubview:self.contentArea];
1314 [self.view addSubview:self.typingShield];
1315 [super viewDidLoad];
1316
1317 // Install fake status bar for iPad iOS7
1318 [self installFakeStatusBar];
1319 [self buildToolbarAndTabStrip];
Justin Cohen4eeada32017-11-13 18:21:281320 [self setUpViewLayout:YES];
Justin Cohenba27610e2017-11-08 19:34:451321 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:501322 [self addConstraintsToToolbar];
1323 }
sdefresnee65fd872016-12-19 13:38:131324 // If the tab model and browser state are valid, finish initialization.
1325 if (_model && _browserState)
1326 [self addUIFunctionalityForModelAndBrowserState];
1327
1328 // Add a tap gesture recognizer to save the last tap location for the source
1329 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271330 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1331 initWithTarget:self
1332 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131333 [tapRecognizer setDelegate:self];
1334 [tapRecognizer setCancelsTouchesInView:NO];
1335 [_contentArea addGestureRecognizer:tapRecognizer];
1336}
1337
Justin Cohenb3170c32017-09-19 01:55:221338- (void)viewSafeAreaInsetsDidChange {
1339 [super viewSafeAreaInsetsDidChange];
1340 // Gate this behind iPhone X, since it's currently the only device that
1341 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501342 if (IsIPhoneX()) {
Justin Cohen4eeada32017-11-13 18:21:281343 [self setUpViewLayout:NO];
Jean-François Geyelined4cde72017-10-11 11:34:501344 }
Justin Cohenba27610e2017-11-08 19:34:451345 if (IsSafeAreaCompatibleToolbarEnabled()) {
Gauthier Ambard100670f72017-10-27 09:54:271346 // TODO(crbug.com/778236): Check if this call can be removed once the
1347 // Toolbar is a contained ViewController.
sczs42f7f7482017-11-08 01:13:271348 [_toolbarCoordinator.toolbarViewController viewSafeAreaInsetsDidChange];
Gauthier Ambard100670f72017-10-27 09:54:271349 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501350 }
Justin Cohenb3170c32017-09-19 01:55:221351}
1352
sdefresnee65fd872016-12-19 13:38:131353- (void)viewDidAppear:(BOOL)animated {
1354 [super viewDidAppear:animated];
1355 self.viewVisible = YES;
1356 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191357 [self updateBroadcastState];
Gregory Chatzinoff541b8642017-10-25 00:25:211358
1359 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1360 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1361 // check for |self.browserState| before calling the presenting the bubbles.
1362 if (self.browserState) {
1363 [self presentBubblesIfEligible];
1364 }
sdefresnee65fd872016-12-19 13:38:131365}
1366
1367- (void)viewWillAppear:(BOOL)animated {
1368 [super viewWillAppear:animated];
1369
sdefresnee65fd872016-12-19 13:38:131370 self.visible = YES;
1371
1372 // Restore hidden infobars.
Rohit Rao755c37b2017-11-10 14:05:521373 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131374 _infoBarContainer->RestoreInfobars();
1375 }
1376
1377 // If the controller is suspended, or has been paged out due to low memory,
1378 // updating the view will be handled when it's displayed again.
1379 if (![_model webUsageEnabled] || !self.contentArea)
1380 return;
1381 // Update the displayed tab (if any; the switcher may not have created one
1382 // yet) in case it changed while showing the switcher.
1383 Tab* currentTab = [_model currentTab];
1384 if (currentTab)
1385 [self displayTab:currentTab isNewSelection:YES];
1386}
1387
1388- (void)viewWillDisappear:(BOOL)animated {
1389 self.viewVisible = NO;
1390 [self updateDialogPresenterActiveState];
Kurt Horimotoe9b6002c2017-12-04 23:19:191391 [self updateBroadcastState];
sdefresnee65fd872016-12-19 13:38:131392 [[_model currentTab] wasHidden];
1393 [_bookmarkInteractionController dismissSnackbar];
Rohit Rao755c37b2017-11-10 14:05:521394 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131395 _infoBarContainer->SuspendInfobars();
1396 }
1397 [super viewWillDisappear:animated];
1398}
1399
1400- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1401 duration:(NSTimeInterval)duration {
1402 [super willRotateToInterfaceOrientation:orient duration:duration];
1403 [self dismissPopups];
1404 [self reshowFindBarIfNeededWithCoordinator:nil];
1405}
1406
1407- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1408 [super didRotateFromInterfaceOrientation:orient];
1409
1410 // This reinitializes the toolbar, including updating the Overlay View,
1411 // if there is one.
1412 [self updateToolbar];
Mark Cogan80aa28d2017-11-30 13:11:341413 [self infoBarContainerStateDidChangeAnimated:NO];
sdefresnee65fd872016-12-19 13:38:131414}
1415
1416- (BOOL)prefersStatusBarHidden {
1417 return self.hideStatusBar;
1418}
1419
1420// Called when in the foreground and the OS needs more memory. Release as much
1421// as possible.
1422- (void)didReceiveMemoryWarning {
1423 // Releases the view if it doesn't have a superview.
1424 [super didReceiveMemoryWarning];
1425
1426 // Release any cached data, images, etc that aren't in use.
1427 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1428 // but since the FaviconCache uses obj-c in the header, it can't be included
1429 // there.
1430 if (_browserState) {
1431 FaviconLoader* loader =
1432 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1433 _browserState);
1434 if (loader)
1435 loader->PurgeCache();
1436 }
1437
1438 if (![self isViewLoaded]) {
1439 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1440 // as the BrowserViewController.
1441 self.contentArea = nil;
1442 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271443 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131444 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271445 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421446 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461447 _toolbarCoordinator = nil;
Kurt Horimotoea429dd2017-11-28 02:24:301448 [_toolbarUIUpdater stopUpdating];
1449 _toolbarUIUpdater = nil;
stkhapuginc9eee7b2017-04-10 15:49:271450 _toolbarModelDelegate = nil;
1451 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031452 [self.tabStripCoordinator stop];
1453 self.tabStripCoordinator = nil;
1454 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271455 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131456 }
1457}
1458
1459- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1460 [super traitCollectionDidChange:previousTraitCollection];
1461 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1462 // because in some cases the presented view controller isn't a child of the
1463 // BVC in the view controller hierarchy (some intervening object isn't a
1464 // view controller).
1465 [self.presentedViewController
1466 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131467 // Update voice search bar visibility.
1468 [self updateVoiceSearchBarVisibilityAnimated:NO];
1469}
1470
1471- (void)viewWillTransitionToSize:(CGSize)size
1472 withTransitionCoordinator:
1473 (id<UIViewControllerTransitionCoordinator>)coordinator {
1474 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1475 [self dismissPopups];
1476 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1477}
1478
1479- (void)reshowFindBarIfNeededWithCoordinator:
1480 (id<UIViewControllerTransitionCoordinator>)coordinator {
1481 if (![_findBarController isFindInPageShown])
1482 return;
1483
1484 // Record focused state.
1485 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1486
1487 [self hideFindBarWithAnimation:NO];
1488
stkhapuginc9eee7b2017-04-10 15:49:271489 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131490 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1491 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271492 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131493 if (strongSelf)
1494 [strongSelf showFindBarWithAnimation:NO
1495 selectText:NO
1496 shouldFocus:isFocusedBeforeReshow];
1497 };
1498
1499 BOOL enqueued =
1500 [coordinator animateAlongsideTransition:nil completion:completion];
1501 if (!enqueued) {
1502 completion(nil);
1503 }
1504}
1505
1506- (void)dismissViewControllerAnimated:(BOOL)flag
1507 completion:(void (^)())completion {
Rohit Rao685807a52017-11-10 20:50:111508 // It is an error to call this method when no VC is being presented.
1509 DCHECK(!TabSwitcherPresentsBVCEnabled() || self.presentedViewController);
1510
Rohit Raoa668c022017-11-08 00:04:441511 // Some calling code invokes |dismissViewControllerAnimated:completion:|
1512 // multiple times. When the BVC is displayed using VC containment, multiple
1513 // calls are effectively idempotent because only the first call has any effect
1514 // and subsequent calls do nothing. However, when the BVC is presented,
1515 // subsequent calls end up dismissing the BVC itself. This is never what we
Rohit Rao685807a52017-11-10 20:50:111516 // want, so check for this case and return early. It is not enough to check
1517 // |self.dismissingModal| because some dismissals do not go through
1518 // -[BrowserViewController dismissViewControllerAnimated:completion:|.
Rohit Raoa668c022017-11-08 00:04:441519 // TODO(crbug.com/782338): Fix callers and remove this early return.
Rohit Rao685807a52017-11-10 20:50:111520 if (TabSwitcherPresentsBVCEnabled() &&
1521 (self.dismissingModal || self.presentedViewController.isBeingDismissed)) {
Rohit Raoa668c022017-11-08 00:04:441522 return;
1523 }
1524
sdefresnee65fd872016-12-19 13:38:131525 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271526 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131527 [super dismissViewControllerAnimated:flag
1528 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271529 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131530 [strongSelf setDismissingModal:NO];
1531 [strongSelf setPresenting:NO];
1532 if (completion)
1533 completion();
1534 [[strongSelf dialogPresenter] tryToPresent];
1535 }];
1536}
1537
1538- (void)presentViewController:(UIViewController*)viewControllerToPresent
1539 animated:(BOOL)flag
1540 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271541 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131542 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1543 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1544 // presented, to show a temporary view of the launch screen and then remove it
1545 // when the controller for the FRE has been presented. This fix should be
1546 // removed when the FRE startup code is rewritten.
1547 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1548 experimental_flags::AlwaysDisplayFirstRun()) &&
1549 !tests_hook::DisableFirstRun();
1550 // These if statements check that |presentViewController| is being called for
1551 // the FRE case.
1552 if (firstRunLaunch &&
1553 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1554 UINavigationController* navController =
1555 base::mac::ObjCCastStrict<UINavigationController>(
1556 viewControllerToPresent);
1557 if ([navController.topViewController
1558 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1559 self.hideStatusBar = YES;
1560
1561 // Load view from Launch Screen and add it to window.
1562 NSBundle* mainBundle = base::mac::FrameworkBundle();
1563 NSArray* topObjects =
1564 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1565 UIViewController* launchScreenController =
1566 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1567 // |launchScreenView| is loaded as an autoreleased object, and is retained
1568 // by the |completion| block below.
1569 UIView* launchScreenView = launchScreenController.view;
1570 launchScreenView.userInteractionEnabled = NO;
1571 launchScreenView.frame = self.view.window.bounds;
1572 [self.view.window addSubview:launchScreenView];
1573
1574 // Replace the completion handler sent to the superclass with one which
1575 // removes |launchScreenView| and resets the status bar. If |completion|
1576 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271577 __weak BrowserViewController* weakSelf = self;
1578 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131579 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271580 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131581 if (completion)
1582 completion();
stkhapuginc9eee7b2017-04-10 15:49:271583 };
sdefresnee65fd872016-12-19 13:38:131584 }
1585 }
1586
1587 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171588 if ([_sideSwipeController inSwipe]) {
1589 [_sideSwipeController resetContentView];
1590 }
sdefresnee65fd872016-12-19 13:38:131591
1592 [super presentViewController:viewControllerToPresent
1593 animated:flag
1594 completion:finalCompletionHandler];
1595}
1596
Mark Cogan80aa28d2017-11-30 13:11:341597- (BOOL)shouldAutorotate {
1598 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
1599 // Don't rotate if a voice search is being presented or dismissed. Once the
1600 // transition animations finish, only the Voice Search UIViewController's
1601 // |-shouldAutorotate| will be called.
1602 return NO;
1603 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
1604 // Don't auto rotate if side swipe controller view says not to.
1605 return NO;
1606 } else {
1607 return [super shouldAutorotate];
1608 }
1609}
1610
sdefresnee65fd872016-12-19 13:38:131611#pragma mark - Notification handling
1612
1613- (void)registerForNotifications {
1614 DCHECK(_model);
1615 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1616 [defaultCenter addObserver:self
1617 selector:@selector(pageLoadStarting:)
1618 name:kTabModelTabWillStartLoadingNotification
1619 object:_model];
1620 [defaultCenter addObserver:self
1621 selector:@selector(pageLoadStarted:)
1622 name:kTabModelTabDidStartLoadingNotification
1623 object:_model];
1624 [defaultCenter addObserver:self
1625 selector:@selector(pageLoadComplete:)
1626 name:kTabModelTabDidFinishLoadingNotification
1627 object:_model];
1628 [defaultCenter addObserver:self
1629 selector:@selector(tabDeselected:)
1630 name:kTabModelTabDeselectedNotification
1631 object:_model];
1632 [defaultCenter addObserver:self
1633 selector:@selector(tabWasAdded:)
1634 name:kTabModelNewTabWillOpenNotification
1635 object:_model];
1636}
1637
1638- (void)pageLoadStarting:(NSNotification*)notify {
1639 Tab* tab = notify.userInfo[kTabModelTabKey];
1640 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511641
1642 // Stop any Find in Page searches and close the find bar when navigating to a
1643 // new page.
1644 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361645
sdefresnee65fd872016-12-19 13:38:131646 if (tab == [_model currentTab]) {
1647 // TODO(pinkerton): Fill in here about hiding the forward button on
1648 // navigation.
1649 }
1650}
1651
1652- (void)pageLoadStarted:(NSNotification*)notify {
1653 Tab* tab = notify.userInfo[kTabModelTabKey];
1654 DCHECK(tab);
1655 if (tab == [_model currentTab]) {
1656 if (![self isTabNativePage:tab]) {
sczsf1620e52017-10-02 22:54:461657 [_toolbarCoordinator currentPageLoadStarted];
sdefresnee65fd872016-12-19 13:38:131658 }
1659 [self updateVoiceSearchBarVisibilityAnimated:NO];
1660 }
1661}
1662
1663- (void)pageLoadComplete:(NSNotification*)notify {
1664 // Update the UI, but only if the current tab.
1665 Tab* tab = notify.userInfo[kTabModelTabKey];
1666 if (tab == [_model currentTab]) {
1667 // There isn't any need to update the toolbar here. When the page finishes,
1668 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1669 }
1670
1671 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1672
1673 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1674}
1675
1676- (void)tabDeselected:(NSNotification*)notify {
1677 DCHECK(notify);
1678 Tab* tab = notify.userInfo[kTabModelTabKey];
1679 DCHECK(tab);
1680 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221681 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131682}
1683
1684- (void)tabWasAdded:(NSNotification*)notify {
1685 Tab* tab = notify.userInfo[kTabModelTabKey];
1686 DCHECK(tab);
1687
Eugene But56efc322017-08-11 14:03:441688 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131689
1690 // When adding new tabs, check what kind of reminder infobar should
1691 // be added to the new tab. Try to add only one of them.
1692 // This check is done when a new tab is added either through the Tools Menu
1693 // "New Tab" or through "New Tab" in Stack View Controller. This method
1694 // is called after a new tab has added and finished initial navigation.
1695 // If this is added earlier, the initial navigation may end up clearing
1696 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301697 web::WebState* webState = tab.webState;
1698 DCHECK(webState);
1699
1700 infobars::InfoBarManager* infoBarManager =
1701 InfoBarManagerImpl::FromWebState(webState);
1702 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131703 forTabId:[tab tabId]];
edchin9e7a1112017-11-07 18:28:031704 if (!ReSignInInfoBarDelegate::Create(_browserState, tab,
1705 self /* id<SigninPresenter> */)) {
edchin95c927072017-11-04 00:35:071706 DisplaySyncErrors(_browserState, tab, self /* id<SyncPresenter> */);
sdefresnee65fd872016-12-19 13:38:131707 }
1708
1709 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501710 // phone-specific. Call the foreground tab added completion block; for
1711 // iPhones, this will get executed after the animation has finished.
1712 if (IsIPadIdiom()) {
1713 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431714 // This callback is called before webState is activated (on
1715 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1716 // callback asynchronously to be sure the activation is complete.
1717 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011718 // Test existence again as the block may have been deleted.
1719 if (self.foregroundTabWasAddedCompletionBlock) {
1720 self.foregroundTabWasAddedCompletionBlock();
1721 self.foregroundTabWasAddedCompletionBlock = nil;
1722 }
Olivier Robinc7e46242017-09-06 07:55:431723 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501724 }
sdefresnee65fd872016-12-19 13:38:131725 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501726 }
sdefresnee65fd872016-12-19 13:38:131727
1728 // Do nothing if browsing is currently suspended. The BVC will set everything
1729 // up correctly when browsing resumes.
1730 if (!self.visible || ![_model webUsageEnabled])
1731 return;
1732
1733 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1734
1735 // Block that starts voice search at the end of new Tab animation if
1736 // necessary.
1737 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1738 if (_startVoiceSearchAfterNewTabAnimation) {
1739 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001740 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131741 }
1742 };
1743
kkhorimotoa44349c12017-04-12 23:02:121744 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131745 if (!inBackground) {
1746 UIView* animationParentView = _contentArea;
Gauthier Ambard0afa7572017-12-11 09:52:101747 // Create the new page image, and load with the new tab snapshot except if
1748 // it is the NTP.
sdefresnee65fd872016-12-19 13:38:131749 CGFloat newPageOffset = 0;
Gauthier Ambard0afa7572017-12-11 09:52:101750 UIView* newPage;
1751 CGFloat offset = 0;
Sylvain Defresnee7f2c8a2017-10-17 02:39:191752 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
1753 !_isOffTheRecord && !IsIPadIdiom()) {
Gauthier Ambard0afa7572017-12-11 09:52:101754 offset = 0;
sdefresnee65fd872016-12-19 13:38:131755 animationParentView = self.view;
Gauthier Ambard0afa7572017-12-11 09:52:101756 newPage = tab.view;
1757 newPage.userInteractionEnabled = NO;
1758 // Compute a frame for the new page by removing the status bar height from
1759 // the bounds of |self.view|.
1760 CGRect viewBounds, remainder;
1761 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
1762 CGRectMinYEdge);
1763 newPage.frame = viewBounds;
sdefresnee65fd872016-12-19 13:38:131764 } else {
Gauthier Ambard0afa7572017-12-11 09:52:101765 UIImageView* pageScreenshot = [self pageOpenCloseAnimationView];
1766 tab.view.frame = _contentArea.bounds;
1767 pageScreenshot.image =
1768 [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1769 newPage = pageScreenshot;
1770 offset =
1771 pageScreenshot.frame.size.height - pageScreenshot.image.size.height;
sdefresnee65fd872016-12-19 13:38:131772 }
1773 newPageOffset = newPage.frame.origin.y;
1774
sdefresnee65fd872016-12-19 13:38:131775 [animationParentView addSubview:newPage];
1776 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521777 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
Gauthier Ambard0afa7572017-12-11 09:52:101778 newPage, -newPageOffset, offset, origin, _isOffTheRecord, NULL, ^{
1779 [tab view].frame = _contentArea.bounds;
1780 newPage.userInteractionEnabled = YES;
sdefresnee65fd872016-12-19 13:38:131781 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121782 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421783 // Use the model's currentTab here because it is possible that it can
1784 // be reset to a new value before the new Tab animation finished (e.g.
1785 // if another Tab shows a dialog via |dialogPresenter|). However, that
1786 // tab's view hasn't been displayed yet because it was in a new tab
1787 // animation.
1788 Tab* currentTab = [_model currentTab];
1789 if (currentTab) {
Gauthier Ambard64396902017-12-08 10:14:581790 [self tabSelected:currentTab notifyToolbar:NO];
michaeldof49c9b2c2016-12-20 23:07:421791 }
sdefresnee65fd872016-12-19 13:38:131792 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391793
1794 if (self.foregroundTabWasAddedCompletionBlock) {
1795 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451796 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391797 }
sdefresnee65fd872016-12-19 13:38:131798 });
1799 } else {
1800 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1801 // snapshot before adding the views needed for the background animation.
1802 Tab* topTab = [_model currentTab];
1803 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1804 visibleFrameOnly:self.isToolbarOnScreen];
Sylvain Defresne0a86dd22017-12-19 14:37:501805
Justin Cohenc1fa42b2017-12-12 16:59:311806 // The size of the |image| above can be wrong if the snapshot fails, grab
1807 // the correct size here.
Sylvain Defresne0a86dd22017-12-19 14:37:501808 CGRect imageFrame = CGRectZero;
1809 if (self.isToolbarOnScreen) {
1810 imageFrame = UIEdgeInsetsInsetRect(
1811 _contentArea.bounds, [self snapshotEdgeInsetsForTab:topTab]);
1812 } else {
1813 imageFrame = [topTab.webState->GetView() bounds];
1814 }
Justin Cohenc1fa42b2017-12-12 16:59:311815
sdefresnee65fd872016-12-19 13:38:131816 // Add three layers in order on top of the contentArea for the animation:
1817 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271818 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131819 InstallBackgroundInView(background);
1820 [_contentArea addSubview:background];
1821
1822 // 2. A CardView displaying the data from the current tab.
1823 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1824 NSString* title = [topTab title];
1825 if (![title length])
1826 title = [topTab urlDisplayString];
1827 [topCard setTitle:title];
sdefresnee65fd872016-12-19 13:38:131828 [topCard setImage:image];
Sylvain Defresne7178d4c2017-09-14 13:22:371829 [topCard setFavicon:nil];
1830
1831 favicon::FaviconDriver* faviconDriver =
1832 favicon::WebFaviconDriver::FromWebState(topTab.webState);
1833 if (faviconDriver && faviconDriver->FaviconIsValid()) {
1834 gfx::Image favicon = faviconDriver->GetFavicon();
1835 if (!favicon.IsEmpty())
1836 [topCard setFavicon:favicon.ToUIImage()];
1837 }
sdefresnee65fd872016-12-19 13:38:131838
1839 // 3. A new, blank CardView to represent the new tab being added.
1840 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521841 page_animation_util::AnimateNewBackgroundPageWithCompletion(
Justin Cohenc1fa42b2017-12-12 16:59:311842 topCard, [_contentArea frame], imageFrame, IsPortrait(), ^{
sdefresnee65fd872016-12-19 13:38:131843 [background removeFromSuperview];
1844 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121845 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131846 // Resnapshot the top card if it has its own toolbar, as the toolbar
1847 // will be captured in the new tab animation, but isn't desired for
1848 // the stack view snapshots.
1849 id nativeController = [self nativeControllerForTab:topTab];
1850 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1851 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1852 startVoiceSearchIfNecessaryBlock();
1853 });
peterlaurens9f1b6e02017-06-22 17:46:451854 // Reset the foreground tab completion block so that it can never be
1855 // called more than once regardless of foreground/background tab
1856 // appearances.
1857 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131858 }
1859}
1860
1861#pragma mark - UI Configuration and Layout
1862
1863- (void)updateWithTabModel:(TabModel*)model
1864 browserState:(ios::ChromeBrowserState*)browserState {
1865 DCHECK(model);
1866 DCHECK(browserState);
1867 DCHECK(!_model);
1868 DCHECK(!_browserState);
1869 _browserState = browserState;
1870 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271871 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381872
sdefresnee65fd872016-12-19 13:38:131873 [_model addObserver:self];
1874
1875 if (!_isOffTheRecord) {
1876 [DefaultIOSWebViewFactory
1877 registerWebViewFactory:[ChromeWebViewFactory class]];
1878 }
1879 NSUInteger count = [_model count];
1880 for (NSUInteger index = 0; index < count; ++index)
1881 [self installDelegatesForTab:[_model tabAtIndex:index]];
1882
1883 [self registerForNotifications];
1884
gambardbdc07cc2017-02-03 16:43:111885 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121886 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271887 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131888
sdefresnedc432f42017-01-17 14:36:591889 // Register for bookmark changed notification (BookmarkModel may be null
1890 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131891 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591892 if (_bookmarkModel) {
Gauthier Ambard65e949b092017-11-29 08:46:201893 _bookmarkModelBridge.reset(
1894 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
sdefresnedc432f42017-01-17 14:36:591895 }
sdefresnee65fd872016-12-19 13:38:131896}
1897
sdefresnee65fd872016-12-19 13:38:131898- (void)browserStateDestroyed {
1899 [self setActive:NO];
sdefresnee65fd872016-12-19 13:38:131900 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271901 _paymentRequestManager = nil;
sczsf1620e52017-10-02 22:54:461902 [_toolbarCoordinator browserStateDestroyed];
sdefresnee65fd872016-12-19 13:38:131903 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381904
1905 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431906 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111907 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381908 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271909 [_pageInfoCoordinator disconnect];
Louis Romerod11747a2017-10-20 20:10:351910 [_externalSearchCoordinator disconnect];
edchinf5150c682017-09-18 02:50:031911 [self.tabStripCoordinator stop];
1912 self.tabStripCoordinator = nil;
1913 self.tabStripView = nil;
sczsdd860eba2017-08-10 01:55:381914
sdefresnee65fd872016-12-19 13:38:131915 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391916 [_dispatcher stopDispatchingToTarget:self];
1917 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131918}
1919
1920- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221921 CGFloat statusBarHeight = StatusBarHeight();
1922 CGRect statusBarFrame =
1923 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1924 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1925 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131926 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221927 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1928 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1929 [_fakeStatusBarView layer].zPosition = 99;
1930 [[self view] addSubview:_fakeStatusBarView];
1931 } else {
1932 // Add a white bar on phone so that the status bar on the NTP is white.
1933 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1934 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131935 }
1936}
1937
1938// Create the UI elements. May or may not have valid browser state & tab model.
1939- (void)buildToolbarAndTabStrip {
1940 DCHECK([self isViewLoaded]);
1941 DCHECK(!_toolbarModelDelegate);
1942
Rohit Rao44f204302017-08-10 14:49:541943 // Initialize the prerender service before creating the toolbar controller.
1944 PrerenderService* prerenderService =
1945 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1946 if (prerenderService) {
1947 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131948 }
1949
1950 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201951 _toolbarModelDelegate.reset(
1952 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131953 _toolbarModelIOS.reset([_dependencyFactory
1954 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
Peter Laurense0b80f12017-11-21 07:52:401955 _toolbarCoordinator = [[LegacyToolbarCoordinator alloc]
1956 initWithBaseViewController:self
1957 toolsMenuConfigurationProvider:self
Kurt Horimoto975327e2017-12-20 01:38:061958 dispatcher:self.dispatcher
1959 browserState:_browserState];
Peter Laurense0b80f12017-11-21 07:52:401960
Gauthier Ambard29939db12017-10-30 16:47:311961 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
Peter Laurense0b80f12017-11-21 07:52:401962
sczsf1620e52017-10-02 22:54:461963 _toolbarCoordinator.tabModel = _model;
Gauthier Ambard82c8cc52017-10-26 15:59:051964 [_toolbarCoordinator
sczs32cfde82017-11-14 20:43:221965 setToolbarController:
1966 [_dependencyFactory
1967 newToolbarControllerWithDelegate:self
1968 urlLoader:self
1969 dispatcher:self.dispatcher]];
sczsf1620e52017-10-02 22:54:461970 [_dispatcher startDispatchingToTarget:_toolbarCoordinator
justincohen75011c32017-04-28 16:31:391971 forProtocol:@protocol(OmniboxFocuser)];
sczsf1620e52017-10-02 22:54:461972 [_toolbarCoordinator setTabCount:[_model count]];
Kurt Horimoto06b94252017-12-08 19:45:591973 [_toolbarCoordinator start];
Kurt Horimotoe9b6002c2017-12-04 23:19:191974 [self updateBroadcastState];
stkhapuginc9eee7b2017-04-10 15:49:271975 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461976 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:051977 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131978
sdefresnee65fd872016-12-19 13:38:131979 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031980 self.tabStripCoordinator =
1981 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1982 self.tabStripCoordinator.browserState = _browserState;
1983 self.tabStripCoordinator.dispatcher = _dispatcher;
1984 self.tabStripCoordinator.tabModel = _model;
1985 self.tabStripCoordinator.presentationProvider = self;
1986 self.tabStripCoordinator.animationWaitDuration =
Kurt Horimoto62e97c72017-11-03 19:51:471987 kLegacyFullscreenControllerToolbarAnimationDuration;
edchinf5150c682017-09-18 02:50:031988 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131989 }
1990
1991 // Create infobar container.
1992 if (!_infoBarContainerDelegate) {
1993 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1994 _infoBarContainer.reset(
1995 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1996 }
1997}
1998
Jean-François Geyelined4cde72017-10-11 11:34:501999- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:112000 NSLayoutYAxisAnchor* topAnchor;
2001 // On iPad, the toolbar is underneath the tab strip.
2002 // On iPhone, it is underneath the top of the screen.
2003 if (IsIPadIdiom()) {
2004 topAnchor = self.tabStripView.bottomAnchor;
2005 } else {
2006 topAnchor = [self view].topAnchor;
2007 }
2008
Gauthier Ambard100670f72017-10-27 09:54:272009 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:502010
Justin Cohen9fe9ef672017-12-01 20:37:432011 self.toolbarOffsetConstraint =
2012 [_toolbarCoordinator.toolbarViewController.view.topAnchor
2013 constraintEqualToAnchor:topAnchor];
Jean-François Geyelined4cde72017-10-11 11:34:502014 [NSLayoutConstraint activateConstraints:@[
Justin Cohen9fe9ef672017-12-01 20:37:432015 self.toolbarOffsetConstraint,
sczs42f7f7482017-11-08 01:13:272016 [_toolbarCoordinator.toolbarViewController.view.leadingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:502017 constraintEqualToAnchor:[self view].leadingAnchor],
sczs42f7f7482017-11-08 01:13:272018 [_toolbarCoordinator.toolbarViewController.view.trailingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:502019 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:502020 ]];
2021 [[self view] layoutIfNeeded];
2022}
2023
sdefresnee65fd872016-12-19 13:38:132024// Enable functionality that only makes sense if the views are loaded and
2025// both browser state and tab model are valid.
2026- (void)addUIFunctionalityForModelAndBrowserState {
2027 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:052028 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:132029 DCHECK(_model);
2030 DCHECK([self isViewLoaded]);
2031
2032 [self.sideSwipeController addHorizontalGesturesToView:self.view];
2033
Rohit Raoaf46af92017-08-10 12:52:302034 infobars::InfoBarManager* infoBarManager = nullptr;
2035 if (_model.currentTab) {
2036 DCHECK(_model.currentTab.webState);
2037 infoBarManager =
2038 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
2039 }
sdefresnee65fd872016-12-19 13:38:132040 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
2041
sczsdd860eba2017-08-10 01:55:382042 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:432043 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
2044 initWithBaseViewController:self];
2045 _activityServiceCoordinator.dispatcher = _dispatcher;
2046 _activityServiceCoordinator.tabModel = _model;
2047 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:462048 _activityServiceCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:052049 [_toolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:432050 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:432051
Rohit Raocda0a992017-08-16 15:37:112052 _qrScannerCoordinator =
2053 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
2054 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:052055 _qrScannerCoordinator.loadProvider =
2056 [_toolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:112057 _qrScannerCoordinator.presentationProvider = self;
2058
sczsdd860eba2017-08-10 01:55:382059 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:132060 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:382061 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:462062 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:052063 [_toolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:382064 _tabHistoryCoordinator.tabModel = _model;
2065 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:462066 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard82c8cc52017-10-26 15:59:052067 [_toolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:382068
sczs6ae47ad2017-09-06 17:26:532069 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:202070 _sadTabCoordinator.baseViewController = self;
2071 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:532072
sczs281fbdc22017-12-20 20:59:062073 // If there are any existing SadTabHelpers in |_model|, update the helpers
2074 // delegate with the new |_sadTabCoordinator|.
2075 for (NSUInteger i = 0; i < _model.count; i++) {
2076 SadTabTabHelper* sadTabHelper =
2077 SadTabTabHelper::FromWebState([_model tabAtIndex:i].webState);
2078 DCHECK(sadTabHelper);
2079 if (sadTabHelper) {
2080 sadTabHelper->SetDelegate(_sadTabCoordinator);
2081 }
2082 }
2083
Gregory Chatzinoffdf93d692017-09-09 01:32:272084 _pageInfoCoordinator =
2085 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
2086 _pageInfoCoordinator.browserState = _browserState;
2087 _pageInfoCoordinator.dispatcher = _dispatcher;
2088 _pageInfoCoordinator.loader = self;
2089 _pageInfoCoordinator.presentationProvider = self;
2090 _pageInfoCoordinator.tabModel = _model;
2091
Louis Romerod11747a2017-10-20 20:10:352092 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
2093 _externalSearchCoordinator.dispatcher = _dispatcher;
2094
mathp9b4c11d2017-07-06 20:24:132095 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272096 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132097 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202098 browserState:_browserState
2099 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052100 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192101 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132102 }
2103}
2104
2105// Set the frame for the various views. View must be loaded.
Justin Cohen4eeada32017-11-13 18:21:282106- (void)setUpViewLayout:(BOOL)initialLayout {
sdefresnee65fd872016-12-19 13:38:132107 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132108 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:132109 CGFloat minY = [self headerOffset];
2110
Justin Cohenb3170c32017-09-19 01:55:222111 // Update the fake toolbar background height.
2112 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2113 fakeStatusBarFrame.size.height = StatusBarHeight();
2114 _fakeStatusBarView.frame = fakeStatusBarFrame;
2115
edchinf5150c682017-09-18 02:50:032116 if (self.tabStripView) {
2117 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:132118 }
2119
2120 // Position the toolbar next, either at the top of the browser view or
2121 // directly under the tabstrip.
Justin Cohen4eeada32017-11-13 18:21:282122 if (initialLayout)
2123 [self addChildViewController:_toolbarCoordinator.toolbarViewController];
sczs42f7f7482017-11-08 01:13:272124 CGRect toolbarFrame = _toolbarCoordinator.toolbarViewController.view.frame;
sdefresnee65fd872016-12-19 13:38:132125 toolbarFrame.origin = CGPointMake(0, minY);
2126 toolbarFrame.size.width = widthOfView;
Justin Cohenba27610e2017-11-08 19:34:452127 if (!IsSafeAreaCompatibleToolbarEnabled()) {
sczs42f7f7482017-11-08 01:13:272128 [_toolbarCoordinator.toolbarViewController.view setFrame:toolbarFrame];
Jean-François Geyelined4cde72017-10-11 11:34:502129 }
sdefresnee65fd872016-12-19 13:38:132130
2131 // Place the infobar container above the content area.
2132 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
Justin Cohen4eeada32017-11-13 18:21:282133 if (initialLayout)
2134 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
sdefresnee65fd872016-12-19 13:38:132135
Gauthier Ambard087e3572017-12-20 12:54:472136 // Place the toolbar controller above the infobar container and adds the
Gauthier Ambard470c50f2017-12-21 07:55:292137 // layout guides.
Gauthier Ambard087e3572017-12-20 12:54:472138 if (initialLayout) {
Justin Cohen4eeada32017-11-13 18:21:282139 [[self view] insertSubview:_toolbarCoordinator.toolbarViewController.view
2140 aboveSubview:infoBarContainerView];
Gauthier Ambard087e3572017-12-20 12:54:472141 AddNamedGuide(kOmniboxGuide, self.view);
Gauthier Ambard470c50f2017-12-21 07:55:292142 AddNamedGuide(kBackButtonGuide, self.view);
2143 AddNamedGuide(kForwardButtonGuide, self.view);
Gauthier Ambard087e3572017-12-20 12:54:472144 }
sdefresnee65fd872016-12-19 13:38:132145 minY += CGRectGetHeight(toolbarFrame);
Justin Cohen4eeada32017-11-13 18:21:282146 if (initialLayout)
2147 [_toolbarCoordinator.toolbarViewController
2148 didMoveToParentViewController:self];
sdefresnee65fd872016-12-19 13:38:132149
2150 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
2151 // content slightly.
sczs8c837782017-10-03 02:57:242152 minY -= 0.0;
sdefresnee65fd872016-12-19 13:38:132153
2154 // Adjust the content area to be under the toolbar, for fullscreen or below
2155 // the toolbar is not fullscreen.
2156 CGRect contentFrame = [_contentArea frame];
2157 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222158 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2159 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132160 [_contentArea setFrame:contentFrame];
2161
2162 // Adjust the infobar container to be either at the bottom of the screen
2163 // (iPhone) or on the lower toolbar edge (iPad).
2164 CGRect infoBarFrame = contentFrame;
2165 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2166 infoBarFrame.size.height = 0;
2167 [infoBarContainerView setFrame:infoBarFrame];
2168
2169 // Attach the typing shield to the content area but have it hidden.
2170 [_typingShield setFrame:[_contentArea frame]];
Justin Cohen41b1f382017-12-04 15:22:082171 if (initialLayout) {
Justin Cohen4eeada32017-11-13 18:21:282172 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
Justin Cohen41b1f382017-12-04 15:22:082173 [_typingShield setHidden:YES];
2174 }
sdefresnee65fd872016-12-19 13:38:132175}
2176
sdefresnee65fd872016-12-19 13:38:132177- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2178 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442179 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132180
kkhorimotoa44349c12017-04-12 23:02:122181 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132182 // Hide findbar. |updateToolbar| will restore the findbar later.
2183 [self hideFindBarWithAnimation:NO];
2184
2185 // Make new content visible, resizing it first as the orientation may
2186 // have changed from the last time it was displayed.
2187 [[tab view] setFrame:_contentArea.bounds];
2188 [_contentArea displayContentView:[tab view]];
2189 }
2190 [self updateToolbar];
2191
2192 if (newSelection)
sczsf1620e52017-10-02 22:54:462193 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132194
2195 // Notify the Tab that it was displayed.
2196 [tab wasShown];
2197}
2198
2199- (void)initializeBookmarkInteractionController {
2200 if (_bookmarkInteractionController)
2201 return;
edchinbb8ba892017-09-12 15:44:032202 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2203 initWithBrowserState:_browserState
2204 loader:self
2205 parentController:self
2206 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132207}
2208
2209// Update the state of back and forward buttons, hiding the forward button if
2210// there is nowhere to go. Assumes the model's current tab is up to date.
2211- (void)updateToolbar {
2212 // If the BVC has been partially torn down for low memory, wait for the
2213 // view rebuild to handle toolbar updates.
2214 if (!(_toolbarModelIOS && _browserState))
2215 return;
2216
2217 Tab* tab = [_model currentTab];
2218 if (![tab navigationManager])
2219 return;
sczsf1620e52017-10-02 22:54:462220 [_toolbarCoordinator updateToolbarState];
2221 [_toolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132222
Sylvain Defresnef5d2d952017-11-14 11:15:312223 if (_insertedTabWasPrerenderedTab && !_toolbarModelIOS->IsLoading())
sczsf1620e52017-10-02 22:54:462224 [_toolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132225
rohitrao005a6432017-03-16 20:52:422226 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2227 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132228 [self showFindBarWithAnimation:NO
2229 selectText:YES
2230 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362231 }
sdefresnee65fd872016-12-19 13:38:132232
2233 // Hide the toolbar if displaying phone NTP.
2234 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552235 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132236 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552237 if (item) {
2238 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132239 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2240 hideToolbar = isNTP && !_isOffTheRecord &&
sczsf1620e52017-10-02 22:54:462241 ![_toolbarCoordinator isOmniboxFirstResponder] &&
2242 ![_toolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132243 }
sczs42f7f7482017-11-08 01:13:272244 [_toolbarCoordinator.toolbarViewController.view setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132245 }
2246}
2247
Kurt Horimotoe9b6002c2017-12-04 23:19:192248- (void)updateBroadcastState {
2249 self.broadcasting =
Kurt Horimotoea429dd2017-11-28 02:24:302250 self.active && self.viewVisible && !self.inNewTabAnimation;
Kurt Horimotoea429dd2017-11-28 02:24:302251}
2252
sdefresnee65fd872016-12-19 13:38:132253- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122254 self.dialogPresenter.active =
2255 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132256}
2257
2258- (void)dismissPopups {
Peter Laurense0b80f12017-11-21 07:52:402259 [self.dispatcher dismissToolsMenu];
Gregory Chatzinoffdf93d692017-09-09 01:32:272260 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282261 [_tabHistoryCoordinator dismissHistoryPopup];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262262 [self.tabTipBubblePresenter dismissAnimated:NO];
2263 [self.incognitoTabTipBubblePresenter dismissAnimated:NO];
Cooper Knaak33f9f402017-08-09 18:04:382264}
2265
Cooper Knaakd0a974cd2017-08-10 18:05:472266- (BubbleViewControllerPresenter*)
2267bubblePresenterForFeature:(const base::Feature&)feature
2268 direction:(BubbleArrowDirection)direction
2269 alignment:(BubbleAlignment)alignment
2270 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212271 DCHECK(self.browserState);
2272 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382273 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472274 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382275 }
2276 // Capture |weakSelf| instead of the feature engagement tracker object
2277 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2278 // the feature engagement tracker will remain pointing to invalid memory if
2279 // its owner (the ChromeBrowserState) is deallocated.
2280 __weak BrowserViewController* weakSelf = self;
2281 void (^dismissalCallback)(void) = ^() {
2282 BrowserViewController* strongSelf = weakSelf;
2283 if (strongSelf) {
2284 feature_engagement::TrackerFactory::GetForBrowserState(
2285 strongSelf.browserState)
2286 ->Dismissed(feature);
2287 }
2288 };
2289
Cooper Knaakd0a974cd2017-08-10 18:05:472290 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382291 [[BubbleViewControllerPresenter alloc] initWithText:text
2292 arrowDirection:direction
2293 alignment:alignment
2294 dismissalCallback:dismissalCallback];
2295
Cooper Knaakd0a974cd2017-08-10 18:05:472296 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132297}
2298
Cooper Knaak120cee5e2017-08-10 20:57:002299- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212300 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002301 // If the tab tip bubble has already been presented and the user is still
2302 // considered engaged, it can't be overwritten or set to |nil| or else it will
2303 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2304 // can be safely overwritten or set to |nil|.
2305 if (!self.tabTipBubblePresenter.isUserEngaged) {
2306 __weak BrowserViewController* weakSelf = self;
2307 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2308 [weakSelf presentNewTabTipBubble];
2309 };
2310
2311 // Because the new tab tip occurs on startup, the feature engagement
2312 // tracker's database is not guaranteed to be loaded by this time. For the
2313 // bubble to appear properly, a callback is used to guarantee the event data
2314 // is loaded before the check to see if the promotion should be displayed.
2315 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2316 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2317 }
2318}
2319
2320- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212321 DCHECK(self.browserState);
Gregory Chatzinofff192d5d2017-12-09 00:26:072322 // If the BVC is not visible, do not present the bubble.
2323 if (!self.viewVisible)
2324 return;
Gregory Chatzinoff876956212017-12-18 22:33:232325 // Do not present the bubble if there is no current tab or if the current tab
2326 // is the NTP.
Gregory Chatzinofff192d5d2017-12-09 00:26:072327 Tab* currentTab = [self.tabModel currentTab];
2328 if (!currentTab)
2329 return;
2330 if (currentTab.webState->GetVisibleURL() == kChromeUINewTabURL)
2331 return;
Gregory Chatzinoffe205f44642017-12-19 17:54:032332
2333 // Do not present the bubble if the tab is not scrolled to the top.
2334 if (![self isTabScrolledToTop:currentTab])
Gregory Chatzinoff876956212017-12-18 22:33:232335 return;
Gregory Chatzinofff192d5d2017-12-09 00:26:072336
Cooper Knaak120cee5e2017-08-10 20:57:002337 NSString* text =
2338 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2339 CGPoint tabSwitcherAnchor;
2340 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032341 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002342 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032343 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002344 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2345 } else {
sczsf1620e52017-10-02 22:54:462346 DCHECK([_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002347 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
sczsf1620e52017-10-02 22:54:462348 tabSwitcherAnchor = [_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002349 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2350 }
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262351
Cooper Knaake963d6702017-08-11 21:03:112352 // If the feature engagement tracker does not consider it valid to display
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262353 // the new tab tip, then end early to prevent the potential reassignment
2354 // of the existing |tabTipBubblePresenter| to nil.
2355 BubbleViewControllerPresenter* presenter =
Cooper Knaak120cee5e2017-08-10 20:57:002356 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2357 direction:BubbleArrowDirectionUp
2358 alignment:BubbleAlignmentTrailing
2359 text:text];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262360 if (!presenter)
2361 return;
2362
2363 self.tabTipBubblePresenter = presenter;
2364
Cooper Knaak120cee5e2017-08-10 20:57:002365 [self.tabTipBubblePresenter presentInViewController:self
2366 view:self.view
2367 anchorPoint:tabSwitcherAnchor];
2368}
2369
Helen Yang9175bd52017-08-12 00:28:402370- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212371 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402372 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2373 // user is still considered engaged.
2374 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2375 __weak BrowserViewController* weakSelf = self;
2376 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2377 [weakSelf presentNewIncognitoTabTipBubble];
2378 };
2379
2380 // Use a callback in case the new incognito tab tip should be shown on
2381 // startup. This ensures that the tracker's database will be fully loaded
2382 // before checking if the promotion should be displayed.
2383 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2384 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2385 }
2386}
2387
2388- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212389 DCHECK(self.browserState);
sczsf1620e52017-10-02 22:54:462390 DCHECK([_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402391 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
Gregory Chatzinofff192d5d2017-12-09 00:26:072392 // If the BVC is not visible, do not present the bubble.
2393 if (!self.viewVisible)
2394 return;
2395
Gregory Chatzinoff876956212017-12-18 22:33:232396 // Do not present the bubble if there is no current tab.
2397 Tab* currentTab = [self.tabModel currentTab];
2398 if (!currentTab)
2399 return;
Gregory Chatzinoffe205f44642017-12-19 17:54:032400
2401 // Do not present the bubble if the tab is not scrolled to the top.
2402 if (![self isTabScrolledToTop:currentTab])
Gregory Chatzinoff876956212017-12-18 22:33:232403 return;
2404
Helen Yang9175bd52017-08-12 00:28:402405 NSString* text = l10n_util::GetNSStringWithFixup(
2406 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
sczsf1620e52017-10-02 22:54:462407 CGPoint toolsButtonAnchor = [_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402408 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262409
2410 // If the feature engagement tracker does not consider it valid to display
2411 // the incognito tab tip, then end early to prevent the potential reassignment
2412 // of the existing |incognitoTabTipBubblePresenter| to nil.
2413 BubbleViewControllerPresenter* presenter =
Helen Yang9175bd52017-08-12 00:28:402414 [self bubblePresenterForFeature:feature_engagement::
2415 kIPHNewIncognitoTabTipFeature
2416 direction:BubbleArrowDirectionUp
2417 alignment:BubbleAlignmentTrailing
2418 text:text];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262419 if (!presenter)
2420 return;
2421
2422 self.incognitoTabTipBubblePresenter = presenter;
2423
Helen Yang9175bd52017-08-12 00:28:402424 [self.incognitoTabTipBubblePresenter
2425 presentInViewController:self
2426 view:self.view
2427 anchorPoint:toolsButtonAnchor];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:262428 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402429}
2430
Gregory Chatzinoffe205f44642017-12-19 17:54:032431- (BOOL)isTabScrolledToTop:(Tab*)tab {
2432 CGPoint scrollOffset =
2433 tab.webState->GetWebViewProxy().scrollViewProxy.contentOffset;
2434
2435 // If there is a native controller, use the native controller's scroll offset.
2436 id nativeController = [self nativeControllerForTab:tab];
2437 if ([nativeController conformsToProtocol:@protocol(CRWNativeContent)] &&
2438 [nativeController respondsToSelector:@selector(scrollOffset)]) {
2439 scrollOffset = [nativeController scrollOffset];
2440 }
2441 return CGPointEqualToPoint(scrollOffset, CGPointZero);
2442}
2443
sdefresnee65fd872016-12-19 13:38:132444#pragma mark - Tap handling
2445
Mark Cogandfcdea72017-07-18 13:47:382446- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032447 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2448 _lastTapPoint = CGPointZero;
2449 } else {
2450 _lastTapPoint =
2451 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132452 }
Mark Cogane01ebce2017-07-12 19:31:032453 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132454}
2455
2456- (CGPoint)lastTapPoint {
2457 if (CACurrentMediaTime() - _lastTapTime < 1) {
2458 return _lastTapPoint;
2459 }
2460 return CGPointZero;
2461}
2462
2463- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2464 UIView* view = gestureRecognizer.view;
2465 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2466 _lastTapPoint =
2467 [[view superview] convertPoint:viewCoordinate toView:self.view];
2468 _lastTapTime = CACurrentMediaTime();
2469}
2470
2471- (BOOL)addTabIfNoTabWithNormalBrowserState {
2472 if (![_model count]) {
2473 if (!_isOffTheRecord) {
2474 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2475 transition:ui::PAGE_TRANSITION_TYPED];
2476 return YES;
2477 }
2478 }
2479 return NO;
2480}
2481
2482#pragma mark - Tab creation and selection
2483
2484// Called when either a tab finishes loading or when a tab with finished content
2485// is added directly to the model via pre-rendering.
2486- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2487 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2488
2489 // Persist the session on a delay.
2490 [_model saveSessionImmediately:NO];
2491}
2492
2493- (Tab*)addSelectedTabWithURL:(const GURL&)url
2494 postData:(TemplateURLRef::PostContent*)postData
2495 transition:(ui::PageTransition)transition {
2496 return [self addSelectedTabWithURL:url
2497 postData:postData
2498 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492499 transition:transition
2500 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132501}
2502
2503- (Tab*)addSelectedTabWithURL:(const GURL&)url
2504 transition:(ui::PageTransition)transition {
2505 return [self addSelectedTabWithURL:url
2506 atIndex:[_model count]
2507 transition:transition];
2508}
2509
2510- (Tab*)addSelectedTabWithURL:(const GURL&)url
2511 atIndex:(NSUInteger)position
2512 transition:(ui::PageTransition)transition {
2513 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492514 atIndex:position
2515 transition:transition
2516 tabAddedCompletion:nil];
2517}
2518
2519- (Tab*)addSelectedTabWithURL:(const GURL&)url
2520 atIndex:(NSUInteger)position
2521 transition:(ui::PageTransition)transition
2522 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2523 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132524 postData:NULL
2525 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492526 transition:transition
2527 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132528}
2529
2530- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2531 postData:(TemplateURLRef::PostContent*)postData
2532 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492533 transition:(ui::PageTransition)transition
2534 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132535 if (position == NSNotFound)
2536 position = [_model count];
2537 DCHECK(position <= [_model count]);
2538
2539 web::NavigationManager::WebLoadParams params(URL);
2540 params.transition_type = transition;
2541 if (postData) {
2542 // Extract the content type and post params from |postData| and add them
2543 // to the load params.
2544 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2545 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2546 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172547 params.post_data.reset(data);
2548 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132549 }
Olivier Robind508a5632017-07-19 16:29:492550
2551 if (tabAddedCompletion) {
2552 if (self.foregroundTabWasAddedCompletionBlock) {
2553 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2554 self.foregroundTabWasAddedCompletionBlock;
2555 self.foregroundTabWasAddedCompletionBlock = ^{
2556 oldForegroundTabWasAddedCompletionBlock();
2557 tabAddedCompletion();
2558 };
2559 } else {
2560 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2561 }
2562 }
2563
sdefresnea6395912017-03-01 01:14:352564 Tab* tab = [_model insertTabWithLoadParams:params
2565 opener:nil
2566 openedByDOM:NO
2567 atIndex:position
2568 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132569 return tab;
2570}
2571
olivierrobin889af53f2017-03-01 14:56:322572// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132573- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322574 web::WebState* webState = tab.webState;
2575 if (!webState)
2576 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392577 web::NavigationItem* visibleItem =
2578 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322579 if (!visibleItem)
2580 return NO;
2581 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132582}
2583
2584- (void)expectNewForegroundTab {
2585 _expectingForegroundTab = YES;
2586}
2587
sdefresnee65fd872016-12-19 13:38:132588- (UIImageView*)pageOpenCloseAnimationView {
2589 CGRect frame = [_contentArea bounds];
2590
2591 frame.size.height = frame.size.height - [self headerHeight];
2592 frame.origin.y = [self headerHeight];
2593
stkhapuginf58b10d02017-04-10 13:36:172594 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132595 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2596 pageView.center = center;
2597
2598 pageView.backgroundColor = [UIColor whiteColor];
2599 return pageView;
2600}
2601
2602- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142603 // Unregistration happens when the Tab is removed from the TabModel.
Sylvain Defresnef5d2d952017-11-14 11:15:312604 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
2605
2606 // There should be no pre-rendered Tabs in TabModel.
2607 PrerenderService* prerenderService =
2608 PrerenderServiceFactory::GetForBrowserState(_browserState);
2609 DCHECK(!prerenderService ||
2610 !prerenderService->IsWebStatePrerendered(tab.webState));
edchincd32fdf2017-10-25 12:45:452611
2612 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2613 if (PasswordTabHelper* passwordTabHelper =
2614 PasswordTabHelper::FromWebState(tab.webState)) {
edchin6941b8492017-12-01 18:59:592615 passwordTabHelper->SetBaseViewController(self);
edchincd32fdf2017-10-25 12:45:452616 passwordTabHelper->SetDispatcher(self.dispatcher);
2617 passwordTabHelper->SetPasswordControllerDelegate(self);
2618 }
2619
sdefresnee65fd872016-12-19 13:38:132620 tab.dialogDelegate = self;
2621 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132622 tab.passKitDialogProvider = self;
Kurt Horimotoa5a922a2017-11-07 00:21:092623 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472624 tab.legacyFullscreenControllerDelegate = self;
Kurt Horimoto803840622017-10-28 01:20:372625 }
sdefresnee65fd872016-12-19 13:38:132626 if (!IsIPadIdiom()) {
2627 tab.overscrollActionsControllerDelegate = self;
2628 }
olivierrobin9ce77b82017-01-12 17:29:192629 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132630 tab.tabSnapshottingDelegate = self;
2631 // Install the proper CRWWebController delegates.
2632 tab.webController.nativeProvider = self;
2633 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512634 // BrowserViewController presents SKStoreKitViewController on behalf of a
2635 // tab.
2636 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2637 if (tabHelper)
2638 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132639 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532640 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292641 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532642 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292643 }
Sylvain Defresnecacc3a52017-09-12 13:51:042644 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592645 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572646 NetExportTabHelper::CreateForWebState(tab.webState, self);
Mike Dougherty4620cf8e2017-10-31 23:37:092647 CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
Eugene But49a7c572017-12-11 20:54:152648 PassKitTabHelper::CreateForWebState(tab.webState, _passKitCoordinator);
edchincd32fdf2017-10-25 12:45:452649
Mark Coganca30df62017-11-20 14:29:112650 // The language detection helper accepts a callback from the translate
2651 // client, so must be created after it.
2652 // This will explode if the webState doesn't have a JS injection manager
2653 // (this only comes up in unit tests), so check for that and bypass the
2654 // init of the translation helpers if needed.
2655 // TODO(crbug.com/785238): Remove the need for this check.
2656 if (tab.webState->GetJSInjectionReceiver()) {
2657 ChromeIOSTranslateClient::CreateForWebState(tab.webState,
2658 _languageSelectionCoordinator);
2659 language::IOSLanguageDetectionTabHelper::CreateForWebState(
2660 tab.webState,
2661 ChromeIOSTranslateClient::FromWebState(tab.webState)
2662 ->GetTranslateDriver()
2663 ->CreateLanguageDetectionCallback(),
2664 UrlLanguageHistogramFactory::GetForBrowserState(self.browserState));
2665 }
2666
edchincd32fdf2017-10-25 12:45:452667 if (AccountConsistencyService* accountConsistencyService =
2668 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2669 self.browserState)) {
2670 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552671 }
sdefresnee65fd872016-12-19 13:38:132672}
2673
sdefresne49cf2862017-03-15 13:46:142674- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112675 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
edchincd32fdf2017-10-25 12:45:452676
2677 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2678 if (PasswordTabHelper* passwordTabHelper =
2679 PasswordTabHelper::FromWebState(tab.webState))
2680 passwordTabHelper->SetDispatcher(nil);
2681
sdefresne49cf2862017-03-15 13:46:142682 tab.dialogDelegate = nil;
2683 tab.snapshotOverlayProvider = nil;
2684 tab.passKitDialogProvider = nil;
Kurt Horimotoa5a922a2017-11-07 00:21:092685 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472686 tab.legacyFullscreenControllerDelegate = nil;
Kurt Horimoto803840622017-10-28 01:20:372687 }
sdefresne49cf2862017-03-15 13:46:142688 if (!IsIPadIdiom()) {
2689 tab.overscrollActionsControllerDelegate = nil;
2690 }
2691 tab.tabHeadersDelegate = nil;
2692 tab.tabSnapshottingDelegate = nil;
2693 tab.webController.nativeProvider = nil;
2694 tab.webController.swipeRecognizerProvider = nil;
2695 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2696 if (tabHelper)
2697 tabHelper->SetLauncher(nil);
2698 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452699 if (AccountConsistencyService* accountConsistencyService =
2700 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2701 self.browserState)) {
2702 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2703 }
sdefresne49cf2862017-03-15 13:46:142704}
2705
sdefresnee65fd872016-12-19 13:38:132706// Called when a tab is selected in the model. Make any required view changes.
2707// The notification will not be sent when the tab is already the selected tab.
Gauthier Ambard64396902017-12-08 10:14:582708// |notifyToolbar| indicates whether the toolbar is notified that the tab has
2709// changed.
2710- (void)tabSelected:(Tab*)tab notifyToolbar:(BOOL)notifyToolbar {
sdefresnee65fd872016-12-19 13:38:132711 DCHECK(tab);
2712
2713 // Ignore changes while the tab stack view is visible (or while suspended).
2714 // The display will be refreshed when this view becomes active again.
2715 if (!self.visible || ![_model webUsageEnabled])
2716 return;
2717
Gauthier Ambard64396902017-12-08 10:14:582718 [self displayTab:tab isNewSelection:notifyToolbar];
sdefresnee65fd872016-12-19 13:38:132719
kkhorimotoa44349c12017-04-12 23:02:122720 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132721 // Now that the new tab has been displayed, return to normal. Rather than
2722 // keep a reference to the previous tab, just turn off preview mode for all
2723 // tabs (since doing so is a no-op for the tabs that don't have it set).
2724 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272725 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132726 [tab.webController setOverlayPreviewMode:NO];
2727 }
2728 }
2729}
2730
edchinf5150c682017-09-18 02:50:032731- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
2732 return [self.tabStripCoordinator placeholderView];
2733}
2734
Sylvain Defresne41170aa2017-06-15 10:25:202735- (void)shutdown {
2736 DCHECK(!_isShutdown);
2737 _isShutdown = YES;
edchinf5150c682017-09-18 02:50:032738 [self.tabStripCoordinator stop];
2739 self.tabStripCoordinator = nil;
sczs42f7f7482017-11-08 01:13:272740 [_toolbarCoordinator stop];
2741 _toolbarCoordinator = nil;
edchinf5150c682017-09-18 02:50:032742 self.tabStripView = nil;
Sylvain Defresne41170aa2017-06-15 10:25:202743 _infoBarContainer = nil;
2744 _readingListMenuNotifier = nil;
Gauthier Ambard65e949b092017-11-29 08:46:202745 _bookmarkModelBridge.reset();
Sylvain Defresne41170aa2017-06-15 10:25:202746 [_model removeObserver:self];
2747 [[UpgradeCenter sharedInstance] unregisterClient:self];
2748 [[NSNotificationCenter defaultCenter] removeObserver:self];
Gauthier Ambard82c8cc52017-10-26 15:59:052749 [_toolbarCoordinator setToolbarDelegate:nil];
Sylvain Defresne41170aa2017-06-15 10:25:202750 if (_voiceSearchController)
2751 _voiceSearchController->SetDelegate(nil);
2752 [_rateThisAppDialog setDelegate:nil];
2753 [_model closeAllTabs];
Mohamad Ahmadibec07eb2017-09-12 19:38:462754 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202755}
2756
sdefresnee65fd872016-12-19 13:38:132757#pragma mark - SnapshotOverlayProvider methods
2758
2759- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2760 NSMutableArray* overlays = [NSMutableArray array];
2761 if (![_model webUsageEnabled]) {
2762 return overlays;
2763 }
2764 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2765 if (voiceSearchView) {
2766 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272767 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132768 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272769 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132770 [overlays addObject:voiceSearchOverlay];
2771 }
2772 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2773 if (infoBarView) {
2774 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272775 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132776 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272777 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132778 [overlays addObject:infoBarOverlay];
2779 }
2780 return overlays;
2781}
2782
2783#pragma mark -
2784
2785- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2786 if (IsIPadIdiom()) {
2787 // Not using overlays on iPad because the content is pushed down by
2788 // infobar and the transition between snapshot and fresh page can
2789 // cause both snapshot and real infobars to appear at the same time.
2790 return nil;
2791 }
2792 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302793 if (currentTab && tab == currentTab) {
2794 DCHECK(currentTab.webState);
2795 infobars::InfoBarManager* infoBarManager =
2796 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132797 if (infoBarManager->infobar_count() > 0) {
2798 DCHECK(_infoBarContainer);
2799 return _infoBarContainer->view();
2800 }
2801 }
2802 return nil;
2803}
2804
2805- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272806 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132807 // There is no UI representation for non-current tabs or there is
2808 // no _infoBarContainer instantiated yet.
2809 // Return offset outside of tab.
2810 return CGRectGetMaxY(self.view.frame);
2811 } else if (IsIPadIdiom()) {
2812 // The infobars on iPad are display at the top of a tab.
2813 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2814 } else {
2815 // The infobars on iPhone are displayed at the bottom of a tab.
2816 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2817 return CGRectGetMaxY(visibleFrame) -
2818 CGRectGetHeight(_infoBarContainer->view().frame);
2819 }
2820}
2821
2822- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2823 Tab* currentTab = [_model currentTab];
2824 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2825 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2826 return _voiceSearchBar;
2827 }
2828 return nil;
2829}
2830
2831- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2832 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2833 // There is no UI representation for non-current tabs or there is
2834 // no visible voice search. Return offset outside of tab.
2835 return CGRectGetMaxY(self.view.frame);
2836 } else {
2837 // The voice search bar on iPhone is displayed at the bottom of a tab.
2838 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2839 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2840 }
2841}
2842
2843- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272844 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132845 VoiceSearchProvider* provider =
2846 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2847 if (provider) {
2848 _voiceSearchController =
2849 provider->CreateVoiceSearchController(_browserState);
sczsf1620e52017-10-02 22:54:462850 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:052851 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:132852 }
2853 }
2854}
2855
2856- (void)ensureVoiceSearchBarCreated {
2857 if (_voiceSearchBar)
2858 return;
2859
2860 CGFloat width = CGRectGetWidth([[self view] bounds]);
2861 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2862 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272863 _voiceSearchBar = ios::GetChromeBrowserProvider()
2864 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002865 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132866 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2867 [_voiceSearchBar setHidden:YES];
2868 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2869 UIViewAutoresizingFlexibleWidth];
2870 [self.view insertSubview:_voiceSearchBar
2871 belowSubview:_infoBarContainer->view()];
2872}
2873
2874- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2875 // Voice search bar exists and is shown/hidden.
2876 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272877 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132878 return;
2879
2880 // Voice search bar doesn't exist and thus is not visible.
2881 if (!_voiceSearchBar && !show)
2882 return;
2883
2884 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272885 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132886 else
stkhapuginc9eee7b2017-04-10 15:49:272887 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132888}
2889
2890- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2891 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2892 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2893 self.shouldShowVoiceSearchBar) {
2894 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2895 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272896 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132897 }
2898 id currentNativeController =
2899 [self nativeControllerForTab:self.tabModel.currentTab];
2900 Protocol* possibleOwnerProtocol =
2901 @protocol(LogoAnimationControllerOwnerOwner);
2902 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2903 [currentNativeController logoAnimationControllerOwner]) {
2904 // If the current native controller is showing a GLIF view (e.g. the NTP
2905 // when there is no doodle), use that GLIFControllerOwner.
2906 return [currentNativeController logoAnimationControllerOwner];
2907 }
2908 return nil;
2909}
2910
2911#pragma mark - PassKitDialogProvider methods
2912
2913- (void)presentPassKitDialog:(NSData*)data {
2914 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272915 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132916 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272917 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132918 if (error || !data) {
2919 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302920 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132921 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302922 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132923 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2924 // NULL, replace if with DCHECK).
2925 if (infoBarManager)
2926 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2927 }
2928 } else {
2929 PKAddPassesViewController* passKitViewController =
2930 [_dependencyFactory newPassKitViewControllerForPass:pass];
2931 if (passKitViewController) {
2932 [self presentViewController:passKitViewController
2933 animated:YES
2934 completion:^{
2935 }];
2936 }
2937 }
2938}
2939
2940- (UIStatusBarStyle)preferredStatusBarStyle {
2941 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2942 : UIStatusBarStyleDefault;
2943}
2944
Tomasz Garbusb844e992017-09-29 12:44:552945#pragma mark - PasswordControllerDelegate methods
2946
2947- (BOOL)displaySignInNotification:(UIViewController*)viewController
2948 fromTabId:(NSString*)tabId {
2949 // Check if the call comes from currently visible tab.
2950 if ([tabId isEqual:[_model currentTab].tabId]) {
2951 [self addChildViewController:viewController];
2952 [self.view addSubview:viewController.view];
2953 [viewController didMoveToParentViewController:self];
2954 return YES;
2955 } else {
2956 return NO;
2957 }
2958}
2959
sdefresnee65fd872016-12-19 13:38:132960#pragma mark - CRWWebStateDelegate methods.
2961
eugenebut75a06fa72017-01-09 17:09:552962- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512963 createNewWebStateForURL:(const GURL&)URL
2964 openerURL:(const GURL&)openerURL
2965 initiatedByUser:(BOOL)initiatedByUser {
2966 // Check if requested web state is a popup and block it if necessary.
2967 if (!initiatedByUser) {
2968 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2969 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102970 // It's possible for a page to inject a popup into a window created via
2971 // window.open before its initial load is committed. Rather than relying
2972 // on the last committed or pending NavigationItem's referrer policy, just
2973 // use ReferrerPolicyDefault.
2974 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2975 // policy once referrer policies are correctly recorded in
2976 // NavigationItems.
2977 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512978 helper->HandlePopup(URL, referrer);
2979 return nil;
2980 }
2981 }
2982
2983 // Requested web state should not be blocked from opening.
2984 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2985 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2986
2987 // Tabs open by DOM are always renderer initiated.
2988 web::NavigationManager::WebLoadParams params(GURL{});
2989 params.transition_type = ui::PAGE_TRANSITION_LINK;
2990 params.is_renderer_initiated = true;
2991 Tab* childTab = [[self tabModel]
2992 insertTabWithLoadParams:params
2993 opener:currentTab
2994 openedByDOM:YES
2995 atIndex:TabModelConstants::kTabPositionAutomatically
2996 inBackground:NO];
2997 return childTab.webState;
2998}
2999
eugenebutb46b2122017-03-14 02:43:263000- (void)closeWebState:(web::WebState*)webState {
3001 // Only allow a web page to close itself if it was opened by DOM, or if there
3002 // are no navigation items.
3003 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:583004 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:263005
3006 if (![self tabModel])
3007 return;
3008
3009 NSUInteger index = [[self tabModel] indexOfTab:tab];
3010 if (index != NSNotFound)
3011 [[self tabModel] closeTabAtIndex:index];
3012}
3013
eugenebut275f5892017-03-09 22:20:513014- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:553015 openURLWithParams:(const web::WebState::OpenURLParams&)params {
3016 switch (params.disposition) {
3017 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
3018 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
3019 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:353020 insertTabWithURL:params.url
3021 referrer:params.referrer
3022 transition:params.transition
3023 opener:LegacyTabHelper::GetTabForWebState(webState)
3024 openedByDOM:NO
3025 atIndex:TabModelConstants::kTabPositionAutomatically
3026 inBackground:(params.disposition ==
3027 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:553028 return tab.webState;
3029 }
3030 case WindowOpenDisposition::CURRENT_TAB: {
3031 web::NavigationManager::WebLoadParams loadParams(params.url);
3032 loadParams.referrer = params.referrer;
3033 loadParams.transition_type = params.transition;
3034 loadParams.is_renderer_initiated = params.is_renderer_initiated;
3035 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
3036 return webState;
3037 }
eugenebutd0984e82017-02-22 23:47:513038 case WindowOpenDisposition::NEW_POPUP: {
3039 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:353040 insertTabWithURL:params.url
3041 referrer:params.referrer
3042 transition:params.transition
3043 opener:LegacyTabHelper::GetTabForWebState(webState)
3044 openedByDOM:YES
3045 atIndex:TabModelConstants::kTabPositionAutomatically
3046 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:513047 return tab.webState;
3048 }
eugenebut75a06fa72017-01-09 17:09:553049 default:
3050 NOTIMPLEMENTED();
3051 return nullptr;
3052 };
3053}
3054
Mike Dougherty4e6b3a32017-08-23 18:49:213055- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:133056 handleContextMenu:(const web::ContextMenuParams&)params {
3057 // Prevent context menu from displaying for a tab which is no longer the
3058 // current one.
3059 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:213060 return;
sdefresnee65fd872016-12-19 13:38:133061 }
3062
3063 // No custom context menu if no valid url is available in |params|.
3064 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:213065 return;
sdefresnee65fd872016-12-19 13:38:133066 }
3067
3068 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:133069
stkhapuginc9eee7b2017-04-10 15:49:273070 _contextMenuCoordinator =
3071 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
3072 params:params];
sdefresnee65fd872016-12-19 13:38:133073
3074 NSString* title = nil;
3075 ProceduralBlock action = nil;
3076
stkhapuginc9eee7b2017-04-10 15:49:273077 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:133078 GURL link = params.link_url;
3079 bool isLink = link.is_valid();
3080 GURL imageUrl = params.src_url;
3081 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:193082 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:133083
3084 if (isLink) {
3085 if (link.SchemeIs(url::kJavaScriptScheme)) {
3086 // Open
3087 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
3088 action = ^{
3089 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
3090 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
3091 };
3092 [_contextMenuCoordinator addItemWithTitle:title action:action];
3093 }
3094
3095 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193096 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133097
sdefresnee65fd872016-12-19 13:38:133098 // Open in New Tab.
3099 title = l10n_util::GetNSStringWithFixup(
3100 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
3101 action = ^{
3102 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003103 // The "New Tab" item in the context menu opens a new tab in the current
3104 // browser state. |isOffTheRecord| indicates whether or not the current
3105 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:133106 [weakSelf webPageOrderedOpen:link
3107 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:003108 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:133109 inBackground:YES
3110 appendTo:kCurrentTab];
3111 };
3112 [_contextMenuCoordinator addItemWithTitle:title action:action];
3113 if (!_isOffTheRecord) {
3114 // Open in Incognito Tab.
3115 title = l10n_util::GetNSStringWithFixup(
3116 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
3117 action = ^{
3118 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
3119 [weakSelf webPageOrderedOpen:link
3120 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133121 inIncognito:YES
3122 inBackground:NO
3123 appendTo:kCurrentTab];
3124 };
3125 [_contextMenuCoordinator addItemWithTitle:title action:action];
3126 }
olivierrobin51d4cf42017-01-17 13:32:353127 }
gambard65d69152017-03-23 17:44:223128 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:353129 NSString* innerText = params.link_text;
3130 if ([innerText length] > 0) {
3131 // Add to reading list.
3132 title = l10n_util::GetNSStringWithFixup(
3133 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
3134 action = ^{
3135 Record(ACTION_READ_LATER, isImage, isLink);
3136 [weakSelf addToReadingListURL:link title:innerText];
3137 };
3138 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:533139 }
sdefresnee65fd872016-12-19 13:38:133140 }
3141 // Copy Link.
3142 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
3143 action = ^{
3144 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:283145 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:133146 };
3147 [_contextMenuCoordinator addItemWithTitle:title action:action];
3148 }
3149 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193150 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133151 // Save Image.
gambard98b4ddf2017-04-18 07:14:053152 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:133153 action = ^{
3154 Record(ACTION_SAVE_IMAGE, isImage, isLink);
3155 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
3156 };
3157 [_contextMenuCoordinator addItemWithTitle:title action:action];
3158 // Open Image.
3159 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
3160 action = ^{
3161 Record(ACTION_OPEN_IMAGE, isImage, isLink);
3162 [weakSelf loadURL:imageUrl
3163 referrer:referrer
3164 transition:ui::PAGE_TRANSITION_LINK
3165 rendererInitiated:YES];
3166 };
3167 [_contextMenuCoordinator addItemWithTitle:title action:action];
3168 // Open Image In New Tab.
3169 title = l10n_util::GetNSStringWithFixup(
3170 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
3171 action = ^{
3172 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
3173 [weakSelf webPageOrderedOpen:imageUrl
3174 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133175 inBackground:true
3176 appendTo:kCurrentTab];
3177 };
3178 [_contextMenuCoordinator addItemWithTitle:title action:action];
3179
3180 TemplateURLService* service =
3181 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103182 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133183 if (defaultURL && !defaultURL->image_url().empty() &&
3184 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3185 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3186 defaultURL->short_name());
3187 action = ^{
3188 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3189 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3190 };
3191 [_contextMenuCoordinator addItemWithTitle:title action:action];
3192 }
3193 }
3194
3195 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133196}
3197
eugenebutb739bdc2017-01-25 06:32:483198- (void)webState:(web::WebState*)webState
3199 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3200 // Display the action sheet with the arrow pointing at the top center of the
3201 // web contents.
sdefresne0452a9d2017-02-09 15:33:283202 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483203 UIView* view = webState->GetView();
3204 CGPoint dialogLocation =
3205 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283206 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033207 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173208 helper->PresentDialog(dialogLocation,
3209 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053210 handler(shouldContinue);
3211 }));
eugenebutb739bdc2017-01-25 06:32:483212}
3213
sdefresnee65fd872016-12-19 13:38:133214- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3215 (web::WebState*)webState {
3216 return _javaScriptDialogPresenter.get();
3217}
3218
eugenebut63232102017-01-19 16:19:403219- (void)webState:(web::WebState*)webState
3220 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3221 proposedCredential:(NSURLCredential*)proposedCredential
3222 completionHandler:(void (^)(NSString* username,
3223 NSString* password))handler {
3224 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3225 proposedCredential:proposedCredential
3226 webState:webState
3227 completionHandler:handler];
3228}
3229
Kurt Horimoto62e97c72017-11-03 19:51:473230#pragma mark - LegacyFullscreenControllerDelegate methods
sdefresnee65fd872016-12-19 13:38:133231
[email protected]a08e2bfb2017-11-24 19:16:453232- (void)redrawHeader {
3233 for (HeaderDefinition* header in self.headerViews) {
3234 [header.view setNeedsLayout];
3235 }
3236}
3237
sdefresnee65fd872016-12-19 13:38:133238- (CGFloat)headerOffset {
3239 if (IsIPadIdiom())
3240 return StatusBarHeight();
3241 return 0.0;
3242}
3243
stkhapugin952ecef2017-04-11 12:11:453244- (NSArray<HeaderDefinition*>*)headerViews {
3245 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:133246 if (![self isViewLoaded])
3247 return results;
3248
3249 if (!IsIPadIdiom()) {
sczs42f7f7482017-11-08 01:13:273250 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453251 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273252 definitionWithView:_toolbarCoordinator
3253 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453254 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243255 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453256 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133257 }
3258 } else {
edchinf5150c682017-09-18 02:50:033259 if (self.tabStripView) {
3260 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
3261 headerBehaviour:Hideable
3262 heightAdjustment:0.0
3263 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133264 }
sczs42f7f7482017-11-08 01:13:273265 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453266 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273267 definitionWithView:_toolbarCoordinator
3268 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453269 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243270 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453271 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133272 }
3273 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:453274 [results addObject:[HeaderDefinition
3275 definitionWithView:[_findBarController view]
3276 headerBehaviour:Overlap
3277 heightAdjustment:0.0
3278 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:133279 }
3280 }
stkhapugin952ecef2017-04-11 12:11:453281 return [results copy];
sdefresnee65fd872016-12-19 13:38:133282}
3283
3284- (UIView*)footerView {
3285 return _voiceSearchBar;
3286}
3287
3288- (CGFloat)headerHeight {
3289 return [self headerHeightForTab:[_model currentTab]];
3290}
3291
3292- (CGFloat)headerHeightForTab:(Tab*)tab {
3293 id nativeController = [self nativeControllerForTab:tab];
3294 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3295 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3296 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3297 // On iPhone, don't add any header height for ToolbarOwner native
3298 // controllers when they're displaying their own toolbar.
3299 return 0;
3300 }
3301
stkhapugin952ecef2017-04-11 12:11:453302 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133303
3304 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453305 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133306 if (header.view && header.behaviour == Hideable) {
3307 height += CGRectGetHeight([header.view frame]) -
3308 header.heightAdjustement - header.inset;
3309 }
3310 }
3311
3312 return height - StatusBarHeight();
3313}
3314
3315- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193316 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133317}
3318
3319- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453320 NSArray<HeaderDefinition*>* headers = [self headerViews];
3321 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133322 return 0.0;
3323
3324 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3325 // API documentation.
Sylvain Defresnef5d2d952017-11-14 11:15:313326 if (_insertedTabWasPrerenderedTab)
sdefresnee65fd872016-12-19 13:38:133327 return [self headerHeight];
3328
3329 UIView* topHeader = headers[0].view;
3330 return -(topHeader.frame.origin.y - [self headerOffset]);
3331}
3332
3333- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3334 UIView* footer = [self footerView];
3335 CGFloat headerHeight = [self headerHeight];
3336 if (!footer || headerHeight == 0)
3337 return 0.0;
3338
3339 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3340 CGFloat offset = headerOffset * footerHeight / headerHeight;
3341 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3342}
3343
Kurt Horimoto62e97c72017-11-03 19:51:473344- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:133345 headerAnimationCompleted:(BOOL)completed
3346 offset:(CGFloat)offset {
3347 if (completed)
justincohen04c27772016-12-21 20:16:593348 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133349}
3350
stkhapugin952ecef2017-04-11 12:11:453351- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133352 atOffset:(CGFloat)headerOffset {
3353 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453354 for (HeaderDefinition* header in headers) {
Justin Cohen9fe9ef672017-12-01 20:37:433355 CGFloat yOrigin = height - headerOffset - header.inset;
3356 // Make sure the toolbarView's constraints are also updated. Leaving the
3357 // -setFrame call to minimize changes in this CL -- otherwise the way
3358 // toolbar_view manages it's alpha changes would also need to be updated.
3359 // TODO(crbug.com/778822): This can be cleaned up when the new fullscreen
3360 // is enabled.
3361 if (IsSafeAreaCompatibleToolbarEnabled() &&
Gauthier Ambard594a5f32017-12-13 10:50:553362 header.view == _toolbarCoordinator.toolbarViewController.view &&
3363 !IsIPadIdiom()) {
Justin Cohen9fe9ef672017-12-01 20:37:433364 self.toolbarOffsetConstraint.constant = yOrigin;
3365 }
sdefresnee65fd872016-12-19 13:38:133366 CGRect frame = [header.view frame];
Justin Cohen9fe9ef672017-12-01 20:37:433367 frame.origin.y = yOrigin;
sdefresnee65fd872016-12-19 13:38:133368 [header.view setFrame:frame];
3369 if (header.behaviour != Overlap)
3370 height += CGRectGetHeight(frame);
3371 }
3372}
3373
Kurt Horimoto62e97c72017-11-03 19:51:473374- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133375 drawHeaderViewFromOffset:(CGFloat)headerOffset
3376 animate:(BOOL)animate {
3377 if ([_sideSwipeController inSwipe])
3378 return;
3379
3380 CGRect footerFrame = CGRectZero;
3381 UIView* footer = nil;
3382 // Only animate the voice search bar if the tab is a voice search results tab.
3383 if ([_model currentTab].isVoiceSearchResultsTab) {
3384 footer = [self footerView];
3385 footerFrame = footer.frame;
3386 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3387 }
3388
stkhapugin952ecef2017-04-11 12:11:453389 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133390 void (^block)(void) = ^{
3391 [self setFramesForHeaders:headers atOffset:headerOffset];
3392 footer.frame = footerFrame;
3393 };
3394 void (^completion)(BOOL) = ^(BOOL finished) {
3395 [self fullScreenController:fullScreenController
3396 headerAnimationCompleted:finished
3397 offset:headerOffset];
3398 };
3399 if (animate) {
Kurt Horimoto62e97c72017-11-03 19:51:473400 [UIView
3401 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3402 delay:0.0
3403 options:UIViewAnimationOptionBeginFromCurrentState
3404 animations:block
3405 completion:completion];
sdefresnee65fd872016-12-19 13:38:133406 } else {
3407 block();
3408 completion(YES);
3409 }
3410}
3411
Kurt Horimoto62e97c72017-11-03 19:51:473412- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133413 drawHeaderViewFromOffset:(CGFloat)headerOffset
3414 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3415 changeTopContentPadding:(BOOL)changeTopContentPadding
3416 scrollingToOffset:(CGFloat)contentOffset {
3417 DCHECK(webViewProxy);
3418 if ([_sideSwipeController inSwipe])
3419 return;
3420
3421 CGRect footerFrame;
3422 UIView* footer = nil;
3423 // Only animate the voice search bar if the tab is a voice search results tab.
3424 if ([_model currentTab].isVoiceSearchResultsTab) {
3425 footer = [self footerView];
3426 footerFrame = footer.frame;
3427 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3428 }
3429
stkhapugin952ecef2017-04-11 12:11:453430 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133431 void (^block)(void) = ^{
3432 [self setFramesForHeaders:headers atOffset:headerOffset];
3433 footer.frame = footerFrame;
3434 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3435 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3436 if (changeTopContentPadding)
3437 webViewProxy.topContentPadding = contentOffset;
3438 };
3439 void (^completion)(BOOL) = ^(BOOL finished) {
3440 [self fullScreenController:fullScreenController
3441 headerAnimationCompleted:finished
3442 offset:headerOffset];
3443 };
3444
Kurt Horimoto62e97c72017-11-03 19:51:473445 [UIView
3446 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3447 delay:0.0
3448 options:UIViewAnimationOptionBeginFromCurrentState
3449 animations:block
3450 completion:completion];
sdefresnee65fd872016-12-19 13:38:133451}
3452
3453#pragma mark - VoiceSearchBarOwner
3454
3455- (id<VoiceSearchBar>)voiceSearchBar {
3456 return _voiceSearchBar;
3457}
3458
3459#pragma mark - Install OverScrollActionController method.
3460- (void)setOverScrollActionControllerToStaticNativeContent:
3461 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343462 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133463 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173464 [[OverscrollActionsController alloc]
3465 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133466 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053467 OverscrollStyle style = _isOffTheRecord
3468 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3469 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133470 controller.style = style;
3471 nativeContent.overscrollActionsController = controller;
3472 }
3473}
3474
3475#pragma mark - OverscrollActionsControllerDelegate methods.
3476
3477- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053478 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133479 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053480 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383481 [self.dispatcher
3482 openNewTab:[OpenNewTabCommand
3483 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133484 break;
rohitrao922b7111c2017-01-03 14:31:053485 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243486 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133487 break;
Kurt Horimoto4ce19322017-11-28 19:10:473488 case OverscrollAction::REFRESH:
3489 [self reload];
sdefresnee65fd872016-12-19 13:38:133490 break;
rohitrao922b7111c2017-01-03 14:31:053491 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133492 NOTREACHED();
3493 break;
3494 }
3495}
3496
3497- (BOOL)shouldAllowOverscrollActions {
3498 return YES;
3499}
3500
3501- (UIView*)headerView {
sczs42f7f7482017-11-08 01:13:273502 return _toolbarCoordinator.toolbarViewController.view;
sdefresnee65fd872016-12-19 13:38:133503}
3504
3505- (UIView*)toolbarSnapshotView {
sczs42f7f7482017-11-08 01:13:273506 return [_toolbarCoordinator.toolbarViewController.view
3507 snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133508}
3509
3510- (CGFloat)overscrollActionsControllerHeaderInset:
3511 (OverscrollActionsController*)controller {
3512 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3513 return [self headerHeight];
3514 else
3515 return 0;
3516}
3517
3518- (CGFloat)overscrollHeaderHeight {
3519 return [self headerHeight] + StatusBarHeight();
3520}
3521
3522#pragma mark - TabSnapshottingDelegate methods.
3523
Sylvain Defresne0a86dd22017-12-19 14:37:503524- (UIEdgeInsets)snapshotEdgeInsetsForTab:(Tab*)tab {
sdefresnee65fd872016-12-19 13:38:133525 CGFloat headerHeight = [self headerHeightForTab:tab];
3526 id nativeController = [self nativeControllerForTab:tab];
3527 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3528 headerHeight += [nativeController toolbarHeight];
Sylvain Defresne0a86dd22017-12-19 14:37:503529 return UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
sdefresnee65fd872016-12-19 13:38:133530}
3531
3532#pragma mark - NewTabPageObserver methods.
3533
3534- (void)selectedPanelDidChange {
3535 [self updateToolbar];
3536}
3537
3538#pragma mark - CRWNativeContentProvider methods
3539
3540- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3541 withError:(NSError*)error
3542 isPost:(BOOL)isPost {
3543 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173544 [[ErrorPageContent alloc] initWithLoader:self
3545 browserState:self.browserState
3546 url:url
3547 error:error
3548 isPost:isPost
3549 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133550 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3551 return errorPageContent;
3552}
3553
3554- (BOOL)hasControllerForURL:(const GURL&)url {
Marti Wong64481ec2017-10-31 03:38:003555 base::StringPiece host = url.host_piece();
olivierrobin5c861c22017-04-07 15:56:453556 if (host == kChromeUIOfflineHost) {
3557 // Only allow offline URL that are fully specified.
3558 return reading_list::IsOfflineURLValid(
3559 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3560 }
sdefresnee65fd872016-12-19 13:38:133561
Justin Cohen8679e852017-08-14 16:35:253562 if (host == kChromeUIBookmarksHost) {
Marti Wong64481ec2017-10-31 03:38:003563 return IsBookmarksHostEnabled();
Justin Cohen8679e852017-08-14 16:35:253564 }
3565
3566 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133567}
3568
olivierrobind43eecb2017-01-27 20:35:263569- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3570 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133571 DCHECK(url.SchemeIs(kChromeUIScheme));
3572
3573 id<CRWNativeContent> nativeController = nil;
Marti Wong64481ec2017-10-31 03:38:003574 base::StringPiece url_host = url.host_piece();
Justin Cohen49715952017-08-22 14:12:193575 if (url_host == kChromeUINewTabHost ||
Marti Wong64481ec2017-10-31 03:38:003576 (url_host == kChromeUIBookmarksHost && IsBookmarksHostEnabled())) {
Gauthier Ambardd8890452017-09-29 12:07:463577 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3578 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3579 if (@available(iOS 11.0, *)) {
3580 safeAreaInset = self.view.safeAreaInsets;
3581 }
3582 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3583
sdefresnee65fd872016-12-19 13:38:133584 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173585 [[NewTabPageController alloc] initWithUrl:url
3586 loader:self
sczsf1620e52017-10-02 22:54:463587 focuser:_toolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173588 ntpObserver:self
3589 browserState:_browserState
3590 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463591 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453592 tabModel:_model
justincohen75011c32017-04-28 16:31:393593 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463594 dispatcher:self.dispatcher
3595 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133596 pageController.swipeRecognizerProvider = self.sideSwipeController;
3597
3598 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233599 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133600
Marti Wong64481ec2017-10-31 03:38:003601 if (IsBookmarksHostEnabled()) {
sdefresnee65fd872016-12-19 13:38:133602 // New Tab Page can have multiple panels. Each panel is addressable
3603 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3604 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3605 // the Bookmark Manager, etc.
3606 // The utility functions NewTabPage::IdentifierFromFragment() and
3607 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3608 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3609 // without changing the URL since the URL may be chrome://bookmarks/#123.
3610 // If the URL is chrome://newtab/, pre-select the panel based on the
3611 // #fragment.
3612 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233613 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133614 : NewTabPage::IdentifierFromFragment(url.ref());
3615 }
3616 [pageController selectPanel:panelType];
3617 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453618 } else if (url_host == kChromeUIOfflineHost &&
3619 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133620 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173621 [[OfflinePageNativeContent alloc] initWithLoader:self
3622 browserState:_browserState
3623 webState:webState
3624 URL:url];
sdefresnee65fd872016-12-19 13:38:133625 [self setOverScrollActionControllerToStaticNativeContent:
3626 staticNativeController];
3627 nativeController = staticNativeController;
3628 } else if (url_host == kChromeUIExternalFileHost) {
3629 // Return an instance of the |ExternalFileController| only if the file is
3630 // still in the sandbox.
3631 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3632 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173633 nativeController =
3634 [[ExternalFileController alloc] initWithURL:url
3635 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133636 }
peterlaurens44615d02017-05-23 20:23:093637 } else if (url_host == kChromeUICrashHost) {
3638 // There is no native controller for kChromeUICrashHost, it is instead
3639 // handled as any other renderer crash by the SadTabTabHelper.
3640 // nativeController must be set to nil to prevent defaulting to a
3641 // PageNotAvailableController.
3642 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133643 } else {
3644 DCHECK(![self hasControllerForURL:url]);
3645 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173646 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133647 }
3648 // If a native controller is vended before its tab is added to the tab model,
3649 // use the temporary key and add it under the new tab's tabId in the
3650 // TabModelObserver callback. This happens:
3651 // - when there is no current tab (occurs when vending the NTP controller for
3652 // the first tab that is opened),
3653 // - when the current tab's url doesn't match |url| (occurs when a native
3654 // controller is opened in a new tab)
3655 // - when the current tab's url matches |url| and there is already a native
3656 // controller of the appropriate type vended to it (occurs when a native
3657 // controller is opened in a new tab from a tab with a matching URL, e.g.
3658 // opening an NTP when an NTP is already displayed in the current tab).
3659 // For normal page loads, history navigations, tab restorations, and crash
3660 // recoveries, the tab will already exist in the tab model and the tabId can
3661 // be used as the native controller key.
3662 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3663 // that native controllers vended here always correspond to the current tab.
3664 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193665 if (!currentTab.webState ||
3666 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443667 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133668 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443669 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133670 }
sdefresnee65fd872016-12-19 13:38:133671 return nativeController;
3672}
3673
edchin2c6af4a2017-12-05 02:16:493674- (id<CRWNativeContent>)controllerForUnhandledContentAtURL:(const GURL&)URL
3675 webState:
3676 (web::WebState*)webState {
Eugene But87a09532017-12-08 20:02:053677 LegacyDownloadManagerController* downloadController =
3678 [[LegacyDownloadManagerController alloc] initWithWebState:webState
edchin2c6af4a2017-12-05 02:16:493679 downloadURL:URL
3680 baseViewController:self];
3681 [downloadController start];
3682 return downloadController;
3683}
3684
sdefresnee65fd872016-12-19 13:38:133685- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443686 id nativeController = tab.webController.nativeController;
3687 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133688}
3689
3690#pragma mark - DialogPresenterDelegate methods
3691
3692- (void)dialogPresenter:(DialogPresenter*)presenter
3693 willShowDialogForWebState:(web::WebState*)webState {
3694 for (Tab* iteratedTab in self.tabModel) {
3695 if ([iteratedTab webState] == webState) {
3696 self.tabModel.currentTab = iteratedTab;
3697 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3698 break;
3699 }
3700 }
3701}
3702
Kurt Horimoto06b94252017-12-08 19:45:593703#pragma mark - FullscreenUIElement methods
3704
3705- (void)updateForFullscreenProgress:(CGFloat)progress {
3706 [self updateHeadersForFullscreenProgress:progress];
3707 [self updateFootersForFullscreenProgress:progress];
3708 [self updateContentViewTopPaddingForFullscreenProgress:progress];
3709}
3710
3711- (void)updateForFullscreenEnabled:(BOOL)enabled {
3712 if (!enabled)
3713 [self updateForFullscreenProgress:1.0];
3714}
3715
3716- (void)finishFullscreenScrollWithAnimator:
3717 (FullscreenScrollEndAnimator*)animator {
3718 BOOL showingToolbar = animator.finalProgress > animator.startProgress;
3719 CGFloat finalProgress = animator.finalProgress;
3720 // WKWebView does not re-render its content until its model layer's bounds
3721 // have been updated at the end of the animation. If the animator is going
3722 // to hide the toolbar, update the content view's top padding early so that
3723 // content is correctly rendered behind the toolbar that's being animated
3724 // away.
3725 if (!showingToolbar)
3726 [self updateContentViewTopPaddingForFullscreenProgress:finalProgress];
3727 [animator addAnimations:^{
3728 [self updateHeadersForFullscreenProgress:finalProgress];
3729 [self updateFootersForFullscreenProgress:finalProgress];
3730 }];
3731 // If the toolbar is being animated to become visible, update the content view
3732 // top padding in the completion block so that fixed-position elements can be
3733 // properly laid out in the new viewport.
3734 if (showingToolbar) {
3735 __weak FullscreenScrollEndAnimator* weakAnimator = animator;
3736 [animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
3737 [self updateContentViewTopPaddingForFullscreenProgress:
3738 [weakAnimator progressForAnimatingPosition:finalPosition]];
3739 }];
3740 }
3741}
3742
3743#pragma mark - FullscreenUIElement helpers
3744
3745// Translates the header views up and down according to |progress|, where a
3746// progress of 1.0 fully shows the headers and a progress of 0.0 fully hides
3747// them.
3748- (void)updateHeadersForFullscreenProgress:(CGFloat)progress {
3749 [self setFramesForHeaders:[self headerViews]
3750 atOffset:(1.0 - progress) * [self toolbarHeight]];
3751}
3752
3753// Translates the footer view up and down according to |progress|, where a
3754// progress of 1.0 fully shows the footer and a progress of 0.0 fully hides it.
3755- (void)updateFootersForFullscreenProgress:(CGFloat)progress {
3756 if (![_model currentTab].isVoiceSearchResultsTab)
3757 return;
3758
3759 UIView* footerView = [self footerView];
3760 DCHECK(footerView);
3761 CGRect frame = footerView.frame;
3762 frame.origin.y = CGRectGetMaxY(footerView.superview.bounds) -
3763 progress * CGRectGetHeight(frame);
3764 footerView.frame = frame;
3765}
3766
3767// Updates the top padding of the web view proxy. This either resets the frame
3768// of the WKWebView or the contentInsets of the WKWebView's UIScrollView,
3769// depending on the the proxy's |shouldUseInsetForTopPadding| property.
3770- (void)updateContentViewTopPaddingForFullscreenProgress:(CGFloat)progress {
3771 if (self.currentWebState) {
3772 self.currentWebState->GetWebViewProxy().topContentPadding =
3773 progress * [self toolbarHeight];
3774 }
3775}
3776
sdefresnee65fd872016-12-19 13:38:133777#pragma mark - Context menu methods
3778
3779- (void)searchByImageAtURL:(const GURL&)url
3780 referrer:(const web::Referrer)referrer {
3781 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273782 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113783 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173784 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3785 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113786 DCHECK(data);
3787 dispatch_async(dispatch_get_main_queue(), ^{
3788 [weakSelf searchByImageData:data atURL:image_source_url];
3789 });
3790 };
3791 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133792 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3793 web::PolicyForNavigation(url, referrer));
3794}
3795
3796- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3797 NSData* imageData = data;
3798 UIImage* image = [UIImage imageWithData:imageData];
3799 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3800 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3801 // kSearchByImageMaxImageHeight).
3802 if (image &&
3803 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3804 (image.size.width > kSearchByImageMaxImageWidth ||
3805 image.size.height > kSearchByImageMaxImageHeight)) {
3806 CGSize newImageSize =
3807 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3808 image = [image gtm_imageByResizingToSize:newImageSize
3809 preserveAspectRatio:YES
3810 trimToFit:NO];
3811 imageData = UIImageJPEGRepresentation(image, 1.0);
3812 }
3813
3814 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3815 std::string byteString(bytes, [imageData length]);
3816
3817 TemplateURLService* templateUrlService =
3818 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103819 const TemplateURL* defaultURL =
3820 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133821 DCHECK(!defaultURL->image_url().empty());
3822 DCHECK(defaultURL->image_url_ref().IsValid(
3823 templateUrlService->search_terms_data()));
3824 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3825 search_args.image_url = imageURL;
3826 search_args.image_thumbnail_content = byteString;
3827
3828 // Generate the URL and populate |post_content| with the content type and
3829 // HTTP body for the request.
3830 TemplateURLRef::PostContent post_content;
3831 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3832 search_args, templateUrlService->search_terms_data(), &post_content));
3833 [self addSelectedTabWithURL:result
3834 postData:&post_content
3835 transition:ui::PAGE_TRANSITION_TYPED];
3836}
3837
3838- (void)saveImageAtURL:(const GURL&)url
3839 referrer:(const web::Referrer&)referrer {
3840 DCHECK(url.is_valid());
3841
gambard9efce7a2017-02-09 18:53:173842 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3843 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113844 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133845
gambardbbf85c42017-06-29 11:15:343846 if ([data length] == 0) {
3847 [self displayPrivacyErrorAlertOnMainQueue:
3848 l10n_util::GetNSString(
3849 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3850 return;
3851 }
3852
gambard9efce7a2017-02-09 18:53:173853 base::FilePath::StringType extension;
3854
3855 bool extensionSuccess =
3856 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3857 if (!extensionSuccess || extension.length() == 0) {
3858 extension = "png";
3859 }
3860
3861 NSString* fileExtension =
3862 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3863 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113864 };
3865 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133866 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3867 web::PolicyForNavigation(url, referrer));
3868}
3869
gambard9efce7a2017-02-09 18:53:173870- (void)managePermissionAndSaveImage:(NSData*)data
3871 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133872 switch ([PHPhotoLibrary authorizationStatus]) {
3873 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173874 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133875 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3876 // Call -saveImage again to check if chrome needs to display an error or
3877 // saves the image.
3878 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173879 [self managePermissionAndSaveImage:data
3880 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133881 }];
3882 break;
stkhapuginf58b10d02017-04-10 13:36:173883 }
sdefresnee65fd872016-12-19 13:38:133884
3885 // The application doesn't have permission to access photo and the user
3886 // cannot grant it.
3887 case PHAuthorizationStatusRestricted:
3888 [self displayPrivacyErrorAlertOnMainQueue:
3889 l10n_util::GetNSString(
3890 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3891 break;
3892
3893 // The application doesn't have permission to access photo and the user
3894 // can grant it.
3895 case PHAuthorizationStatusDenied:
3896 [self displayImageErrorAlertWithSettingsOnMainQueue];
3897 break;
3898
3899 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243900 default:
3901 __weak BrowserViewController* weakSelf = self;
3902 [self saveImage:data
3903 withFileExtension:fileExtension
3904 completion:^(BOOL success, NSError* error) {
3905 [weakSelf finishSavingImageWithError:error];
3906 }];
sdefresnee65fd872016-12-19 13:38:133907 break;
sdefresnee65fd872016-12-19 13:38:133908 }
3909}
3910
Sylvain Defresnefd3ecf22017-07-12 18:47:243911- (void)saveImage:(NSData*)data
3912 withFileExtension:(NSString*)fileExtension
3913 completion:(void (^)(BOOL, NSError*))completion {
3914 base::PostTaskWithTraits(
3915 FROM_HERE,
3916 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3917 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3918 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373919 base::AssertBlockingAllowed();
sdefresnee65fd872016-12-19 13:38:133920
Sylvain Defresnefd3ecf22017-07-12 18:47:243921 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3922 stringByAppendingString:fileExtension];
3923 NSURL* fileURL = [NSURL
3924 fileURLWithPath:[NSTemporaryDirectory()
3925 stringByAppendingPathComponent:fileName]];
3926 NSError* error = nil;
3927 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3928 if (error) {
3929 if (completion)
3930 completion(NO, error);
3931 return;
3932 }
sdefresnee65fd872016-12-19 13:38:133933
Sylvain Defresnefd3ecf22017-07-12 18:47:243934 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3935 [PHAssetChangeRequest
3936 creationRequestForAssetFromImageAtFileURL:fileURL];
3937 }
3938 completionHandler:^(BOOL success, NSError* error) {
3939 base::PostTaskWithTraits(
3940 FROM_HERE,
3941 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3942 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3943 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373944 base::AssertBlockingAllowed();
Sylvain Defresnefd3ecf22017-07-12 18:47:243945 if (completion)
3946 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133947
Sylvain Defresnefd3ecf22017-07-12 18:47:243948 // Cleanup the temporary file.
3949 NSError* deleteFileError = nil;
3950 [[NSFileManager defaultManager]
3951 removeItemAtURL:fileURL
3952 error:&deleteFileError];
3953 }));
3954 }];
3955 }));
sdefresnee65fd872016-12-19 13:38:133956}
3957
3958- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3959 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3960 BOOL canGoToSetting =
3961 [[UIApplication sharedApplication] canOpenURL:settingURL];
3962 if (canGoToSetting) {
3963 dispatch_async(dispatch_get_main_queue(), ^{
3964 [self displayImageErrorAlertWithSettings:settingURL];
3965 });
3966 } else {
3967 [self displayPrivacyErrorAlertOnMainQueue:
3968 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3969 }
3970}
3971
3972- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3973 // Dismiss current alert.
3974 [_alertCoordinator stop];
3975
3976 NSString* title =
3977 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3978 NSString* message = l10n_util::GetNSString(
3979 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3980
stkhapuginc9eee7b2017-04-10 15:49:273981 _alertCoordinator =
3982 [[AlertCoordinator alloc] initWithBaseViewController:self
3983 title:title
3984 message:message];
sdefresnee65fd872016-12-19 13:38:133985
3986 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3987 action:nil
3988 style:UIAlertActionStyleCancel];
3989
3990 [_alertCoordinator
3991 addItemWithTitle:l10n_util::GetNSString(
3992 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3993 action:^{
3994 OpenUrlWithCompletionHandler(settingURL, nil);
3995 }
3996 style:UIAlertActionStyleDefault];
3997
3998 [_alertCoordinator start];
3999}
4000
4001- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
4002 dispatch_async(dispatch_get_main_queue(), ^{
4003 NSString* title =
4004 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
4005 [self showErrorAlertWithStringTitle:title message:errorContent];
4006 });
4007}
4008
4009// This callback is triggered when the image is effectively saved onto the photo
4010// album, or if the save failed for some reason.
4011- (void)finishSavingImageWithError:(NSError*)error {
4012 // Was there an error?
4013 if (error) {
4014 // Saving photo failed even though user has granted access to Photos.
4015 // Display the error information from the NSError object for user.
4016 NSString* errorMessage = [NSString
4017 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
4018 [error domain], [error code]];
4019 // This code may be execute outside of the main thread. Make sure to display
4020 // the error on the main thread.
4021 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
4022 } else {
4023 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
4024 // the photo application. The current behaviour is to create the photo there
4025 // but not providing any link to it is suboptimal. That's what Safari is
4026 // doing, and what the PM want, but it doesn't make it right.
4027 }
4028}
4029
sdefresnee65fd872016-12-19 13:38:134030#pragma mark - Showing popups
4031
sdefresnee65fd872016-12-19 13:38:134032- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:134033 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
4034
4035 ReadingListModel* readingModel =
4036 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:014037 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
4038 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:134039
pinkerton07e27842017-03-02 15:29:024040 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
edchineeb4d422017-10-02 17:39:364041 [self showSnackbar:l10n_util::GetNSString(
4042 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:134043}
4044
4045#pragma mark - Keyboard commands management
4046
4047- (BOOL)shouldRegisterKeyboardCommands {
4048 if ([self presentedViewController])
4049 return NO;
4050
4051 if (_voiceSearchController && _voiceSearchController->IsVisible())
4052 return NO;
4053
4054 // If there is no first responder, try to make the webview the first
4055 // responder.
4056 if (!GetFirstResponder()) {
Eugene But08be7d02017-10-02 15:49:304057 web::WebState* webState = _model.currentTab.webState;
4058 if (webState)
4059 [webState->GetWebViewProxy() becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:134060 }
4061
4062 return YES;
4063}
4064
4065- (KeyCommandsProvider*)keyCommandsProvider {
4066 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:274067 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:134068 }
stkhapuginc9eee7b2017-04-10 15:49:274069 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:134070}
4071
4072#pragma mark - KeyCommandsPlumbing
4073
4074- (BOOL)isOffTheRecord {
4075 return _isOffTheRecord;
4076}
4077
4078- (NSUInteger)tabsCount {
4079 return [_model count];
4080}
4081
lpromero47ea8862017-01-13 17:51:064082- (BOOL)canGoBack {
4083 return [_model currentTab].canGoBack;
4084}
4085
4086- (BOOL)canGoForward {
4087 return [_model currentTab].canGoForward;
4088}
4089
sdefresnee65fd872016-12-19 13:38:134090- (void)focusTabAtIndex:(NSUInteger)index {
4091 if ([_model count] > index) {
4092 [_model setCurrentTab:[_model tabAtIndex:index]];
4093 }
4094}
4095
4096- (void)focusNextTab {
4097 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
4098 NSInteger modelCount = [_model count];
4099 if (currentTabIndex < modelCount - 1) {
4100 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
4101 [_model setCurrentTab:nextTab];
4102 } else {
4103 [_model setCurrentTab:[_model tabAtIndex:0]];
4104 }
4105}
4106
4107- (void)focusPreviousTab {
4108 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
4109 if (currentTabIndex > 0) {
4110 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
4111 [_model setCurrentTab:previousTab];
4112 } else {
4113 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
4114 [_model setCurrentTab:lastTab];
4115 }
4116}
4117
4118- (void)reopenClosedTab {
4119 sessions::TabRestoreService* const tabRestoreService =
4120 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
4121 if (!tabRestoreService || tabRestoreService->entries().empty())
4122 return;
4123
4124 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
4125 tabRestoreService->entries().front();
4126 // Only handle the TAB type.
4127 if (entry->type != sessions::TabRestoreService::TAB)
4128 return;
4129
Mark Cogandfcdea72017-07-18 13:47:384130 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:134131 TabRestoreServiceDelegateImplIOS* const delegate =
4132 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
4133 _browserState);
4134 tabRestoreService->RestoreEntryById(delegate, entry->id,
4135 WindowOpenDisposition::CURRENT_TAB);
4136}
4137
4138- (void)focusOmnibox {
sczsf1620e52017-10-02 22:54:464139 [_toolbarCoordinator focusOmnibox];
sdefresnee65fd872016-12-19 13:38:134140}
4141
Kurt Horimotoe9b6002c2017-12-04 23:19:194142#pragma mark - MainContentUI
4143
4144- (MainContentUIState*)mainContentUIState {
4145 return _mainContentUIUpdater.state;
4146}
4147
sdefresnee65fd872016-12-19 13:38:134148#pragma mark - UIResponder
4149
4150- (NSArray*)keyCommands {
4151 if (![self shouldRegisterKeyboardCommands]) {
4152 return nil;
4153 }
4154 return [self.keyCommandsProvider
4155 keyCommandsForConsumer:self
edchin8e4cfe032017-10-25 13:25:544156 baseViewController:self
Mark Cogan6c58ea92017-07-06 13:08:244157 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:134158 editingText:![self isFirstResponder]];
4159}
4160
4161#pragma mark -
4162
4163// Induce an intentional crash in the browser process.
4164- (void)induceBrowserCrash {
4165 CHECK(false);
4166 // Call another function, so that the above CHECK can't be tail-call
4167 // optimized. This ensures that this method's name will show up in the stack
4168 // for easier identification.
4169 CHECK(true);
4170}
4171
4172- (void)loadURL:(const GURL&)url
4173 referrer:(const web::Referrer&)referrer
4174 transition:(ui::PageTransition)transition
4175 rendererInitiated:(BOOL)rendererInitiated {
4176 [[OmniboxGeolocationController sharedInstance]
4177 locationBarDidSubmitURL:url
4178 transition:transition
4179 browserState:_browserState];
4180
4181 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
4182 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
4183 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
4184 }
4185
4186 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:244187 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:134188 // restart.
4189 if (url.host() == kChromeUIBrowserCrashHost) {
4190 [self induceBrowserCrash];
4191 // In debug the app can continue working even after the CHECK. Adding a
4192 // return avoids the crash url to be added to the history.
4193 return;
4194 }
4195
Danyao Wang85389a82017-10-25 18:56:274196 bool typed_or_generated_transition =
4197 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
4198 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
4199
Rohit Rao44f204302017-08-10 14:49:544200 PrerenderService* prerenderService =
4201 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4202 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:594203 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:544204 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:594205 DCHECK(newWebState);
4206
sdefresnee65fd872016-12-19 13:38:134207 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:594208 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:134209 DCHECK(oldTab);
4210 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:594211
kkhorimotod804c5732017-03-15 23:44:524212 bool canPruneItems =
4213 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:594214
kkhorimotod804c5732017-03-15 23:44:524215 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:524216 [newTab navigationManager]->CopyStateFromAndPrune(
4217 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:594218
Sylvain Defresnef5d2d952017-11-14 11:15:314219 // Set _insertedTabWasPrerenderedTab to YES while the Tab is inserted
4220 // so that the correct toolbar height is used and animation are played.
4221 _insertedTabWasPrerenderedTab = YES;
sdefresne2c600c52017-04-04 16:49:594222 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
4223 std::move(newWebState));
Sylvain Defresnef5d2d952017-11-14 11:15:314224 _insertedTabWasPrerenderedTab = NO;
sdefresnee65fd872016-12-19 13:38:134225
Danyao Wang85389a82017-10-25 18:56:274226 if (typed_or_generated_transition) {
4227 LoadTimingTabHelper::FromWebState(newTab.webState)
4228 ->DidPromotePrerenderTab();
4229 }
sdefresnee65fd872016-12-19 13:38:134230
sdefresne2f7781c2017-03-02 19:12:464231 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:134232 return;
4233 }
4234 }
4235
4236 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:544237 if (prerenderService) {
4238 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:134239 }
4240
sdefresnee65fd872016-12-19 13:38:134241 // Some URLs are not allowed while in incognito. If we are in incognito and
4242 // load a disallowed URL, instead create a new tab not in the incognito state.
4243 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
4244 [self webPageOrderedOpen:url
4245 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134246 inIncognito:NO
4247 inBackground:NO
4248 appendTo:kCurrentTab];
4249 return;
4250 }
4251
Danyao Wang85389a82017-10-25 18:56:274252 if (typed_or_generated_transition) {
4253 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
4254 ->DidInitiatePageLoad();
4255 }
4256
mrefaata84d5a02017-06-08 17:13:294257 // If this is a reload initiated from the omnibox.
4258 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
4259 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
4260 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
4261 [[_model currentTab] navigationManager]->Reload(
4262 web::ReloadType::NORMAL, true /* check_for_repost */);
4263 return;
4264 }
4265
sdefresnee65fd872016-12-19 13:38:134266 web::NavigationManager::WebLoadParams params(urlToLoad);
4267 params.referrer = referrer;
4268 params.transition_type = transition;
4269 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104270 Tab* currentTab = [_model currentTab];
4271 DCHECK(currentTab);
4272 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4273 currentTab.navigationManager->LoadURLWithParams(params);
4274 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4275 // to be animated on screen because the transition animator will handle the
4276 // animations. When a Tab stops being a voice search Tab, the voice search
4277 // bar should be animated away.
4278 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4279 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134280}
4281
4282- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544283 PrerenderService* prerenderService =
4284 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4285 if (prerenderService) {
4286 prerenderService->CancelPrerender();
4287 }
sdefresnee65fd872016-12-19 13:38:134288 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:184289 if ([self currentWebState])
4290 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134291}
4292
4293- (web::WebState*)currentWebState {
4294 return [[_model currentTab] webState];
4295}
4296
sdefresnee65fd872016-12-19 13:38:134297// Load a new URL on a new page/tab.
4298- (void)webPageOrderedOpen:(const GURL&)URL
4299 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134300 inBackground:(BOOL)inBackground
4301 appendTo:(OpenPosition)appendTo {
4302 Tab* adjacentTab = nil;
4303 if (appendTo == kCurrentTab)
4304 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354305 [_model insertTabWithURL:URL
4306 referrer:referrer
4307 transition:ui::PAGE_TRANSITION_LINK
4308 opener:adjacentTab
4309 openedByDOM:NO
4310 atIndex:TabModelConstants::kTabPositionAutomatically
4311 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134312}
4313
4314- (void)webPageOrderedOpen:(const GURL&)url
4315 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134316 inIncognito:(BOOL)inIncognito
4317 inBackground:(BOOL)inBackground
4318 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004319 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234320 // feature_engagement::Tracker based on |inIncognito|.
4321 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004322
sdefresnee65fd872016-12-19 13:38:134323 if (inIncognito == _isOffTheRecord) {
4324 [self webPageOrderedOpen:url
4325 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134326 inBackground:inBackground
4327 appendTo:appendTo];
4328 return;
4329 }
4330 // When sending an open command that switches modes, ensure the tab
4331 // ends up appended to the end of the model, not just next to what is
4332 // currently selected in the other mode. This is done with the |append|
4333 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274334 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134335 initWithURL:url
4336 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134337 inIncognito:inIncognito
4338 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274339 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154340 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134341}
4342
4343- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054344 WebStateList* webStateList = [_model webStateList];
4345 webStateList->ReplaceWebStateAt(
4346 webStateList->active_index(),
4347 session_util::CreateWebStateWithNavigationEntries(
4348 [_model browserState], sessionTab->current_navigation_index,
4349 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134350}
4351
4352- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:434353 DCHECK(javascript);
4354 javascript = [javascript stringByRemovingPercentEncoding];
4355 web::WebState* webState = [[_model currentTab] webState];
4356 if (webState) {
4357 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
4358 }
sdefresnee65fd872016-12-19 13:38:134359}
4360
4361#pragma mark - WebToolbarDelegate methods
4362
Gauthier Ambard45963ce22017-11-17 15:49:114363- (void)locationBarDidBecomeFirstResponder {
sdefresnee65fd872016-12-19 13:38:134364 if (_locationBarHasFocus)
4365 return; // TODO(crbug.com/244366): This should not be necessary.
4366 _locationBarHasFocus = YES;
4367 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524368 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134369 object:nil];
4370 [_sideSwipeController setEnabled:NO];
4371 if ([[_model currentTab].webController wantsKeyboardShield]) {
4372 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
4373 [_typingShield setAlpha:0.0];
4374 [_typingShield setHidden:NO];
4375 [UIView animateWithDuration:0.3
4376 animations:^{
4377 [_typingShield setAlpha:1.0];
4378 }];
4379 }
4380 [[OmniboxGeolocationController sharedInstance]
4381 locationBarDidBecomeFirstResponder:_browserState];
4382}
4383
Gauthier Ambard45963ce22017-11-17 15:49:114384- (void)locationBarDidResignFirstResponder {
sdefresnee65fd872016-12-19 13:38:134385 if (!_locationBarHasFocus)
4386 return; // TODO(crbug.com/244366): This should not be necessary.
4387 _locationBarHasFocus = NO;
4388 [_sideSwipeController setEnabled:YES];
4389 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524390 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134391 object:nil];
4392 [UIView animateWithDuration:0.3
4393 animations:^{
4394 [_typingShield setAlpha:0.0];
4395 }
4396 completion:^(BOOL finished) {
4397 // This can happen if one quickly resigns the omnibox and then taps
4398 // on the omnibox again during this animation. If the animation is
4399 // interrupted and the toolbar controller is first responder, it's safe
4400 // to assume the |_typingShield| shouldn't be hidden here.
sczsf1620e52017-10-02 22:54:464401 if (!finished && [_toolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134402 return;
4403 [_typingShield setHidden:YES];
4404 }];
4405 [[OmniboxGeolocationController sharedInstance]
4406 locationBarDidResignFirstResponder:_browserState];
4407
4408 // If a load was cancelled by an omnibox edit, but nothing is loading when
4409 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4410 if (_locationBarEditCancelledLoad) {
4411 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294412
4413 web::WebState* webState = [_model currentTab].webState;
4414 if (!_toolbarModelIOS->IsLoading() && webState)
4415 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4416 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134417 }
4418}
4419
Gauthier Ambard45963ce22017-11-17 15:49:114420- (void)locationBarBeganEdit {
sdefresnee65fd872016-12-19 13:38:134421 // On handsets, if a page is currently loading it should be stopped.
4422 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354423 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134424 _locationBarEditCancelledLoad = YES;
4425 }
4426}
4427
sdefresnee65fd872016-12-19 13:38:134428- (ToolbarModelIOS*)toolbarModelIOS {
4429 return _toolbarModelIOS.get();
4430}
4431
sdefresnee65fd872016-12-19 13:38:134432- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4433 CGRect frame = [_contentArea frame];
4434 if (!fullScreen) {
4435 // Changing the origin here is unnecessary, it's set in page_animation_util.
4436 frame.size.height -= [self headerHeight];
4437 }
4438
4439 CGFloat shortAxis = frame.size.width;
4440 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524441 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134442 CGFloat aspectRatio = frame.size.height / frame.size.width;
4443 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4444 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4445 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4446 CGRect cardFrame = {frame.origin, cardSize};
4447
4448 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174449 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134450 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4451 : CardCloseButtonSide::LEADING;
4452 [_contentArea addSubview:card];
4453 return card;
4454}
4455
Peter Laurense0b80f12017-11-21 07:52:404456#pragma mark - Tools Menu Configuration delegate
4457
4458- (void)prepareForToolsMenuPresentationByCoordinator:
4459 (ToolsMenuCoordinator*)coordinator {
4460 DCHECK(_browserState);
4461 DCHECK(self.visible || self.dismissingModal);
4462
4463 // Dismiss the omnibox (if open).
4464 [_toolbarCoordinator cancelOmniboxEdit];
4465 // Dismiss the soft keyboard (if open).
4466 [[_model currentTab].webController dismissKeyboard];
4467 // Dismiss Find in Page focus.
4468 [self updateFindBar:NO shouldFocus:NO];
4469
4470 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4471 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4472 }
4473}
4474
4475- (ToolsMenuConfiguration*)menuConfigurationForToolsMenuCoordinator:
4476 (ToolsMenuCoordinator*)coordinator {
4477 ToolsMenuConfiguration* configuration =
4478 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4479 baseViewController:self];
4480 configuration.requestStartTime = [NSDate date].timeIntervalSinceReferenceDate;
4481
4482 if ([_model count] == 0)
4483 [configuration setNoOpenedTabs:YES];
4484
4485 if (_isOffTheRecord)
4486 [configuration setInIncognito:YES];
4487
4488 if (!_readingListMenuNotifier) {
4489 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4490 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4491 _browserState)];
4492 }
4493
4494 feature_engagement::Tracker* engagementTracker =
4495 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4496 if (engagementTracker->ShouldTriggerHelpUI(
4497 feature_engagement::kIPHBadgedReadingListFeature)) {
4498 [configuration setShowReadingListNewBadge:YES];
4499 [configuration setEngagementTracker:engagementTracker];
4500 }
4501 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4502
4503 [configuration setUserAgentType:self.userAgentType];
4504
4505 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4506 [configuration setHighlightNewIncognitoTabCell:YES];
4507 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4508 }
4509
4510 return configuration;
4511}
4512
4513- (BOOL)shouldHighlightBookmarkButtonForToolsMenuCoordinator:
4514 (ToolsMenuCoordinator*)coordinator {
4515 return [_model currentTab] ? _toolbarModelIOS->IsCurrentTabBookmarked() : NO;
4516}
4517
4518- (BOOL)shouldShowFindBarForToolsMenuCoordinator:
4519 (ToolsMenuCoordinator*)coordinator {
4520 return [_model currentTab] ? self.canShowFindBar : NO;
4521}
4522
4523- (BOOL)shouldShowShareMenuForToolsMenuCoordinator:
4524 (ToolsMenuCoordinator*)coordinator {
4525 return [_model currentTab] ? self.canShowShareMenu : NO;
4526}
4527
4528- (BOOL)isTabLoadingForToolsMenuCoordinator:(ToolsMenuCoordinator*)coordinator {
4529 return ([_model currentTab] && !IsIPadIdiom()) ? _toolbarModelIOS->IsLoading()
4530 : NO;
4531}
4532
Mark Cogan6ebbde02017-07-07 12:50:134533#pragma mark - BrowserCommands
4534
4535- (void)goBack {
4536 [[_model currentTab] goBack];
4537}
4538
4539- (void)goForward {
4540 [[_model currentTab] goForward];
4541}
4542
Mark Coganb9aac6432017-07-07 13:26:354543- (void)stopLoading {
4544 [_model currentTab].webState->Stop();
4545}
4546
4547- (void)reload {
4548 web::WebState* webState = [_model currentTab].webState;
4549 if (webState) {
4550 // |check_for_repost| is true because the reload is explicitly initiated
4551 // by the user.
4552 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4553 true /* check_for_repost */);
4554 }
4555}
4556
Mark Cogan8e791022017-07-10 09:55:354557- (void)bookmarkPage {
4558 [self initializeBookmarkInteractionController];
4559 [_bookmarkInteractionController
4560 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064561 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354562}
4563
Mark Cogandfcdea72017-07-18 13:47:384564- (void)openNewTab:(OpenNewTabCommand*)command {
4565 if (self.isOffTheRecord != command.incognito) {
edchin3ab78ff2017-11-13 19:13:144566 // Must take a snapshot of the tab before we switch the incognito mode
4567 // because the currentTab will change after the switch.
4568 Tab* currentTab = [_model currentTab];
4569 if (currentTab) {
4570 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4571 }
Mark Cogandfcdea72017-07-18 13:47:384572 // Not for this browser state, send it on its way.
4573 [self.dispatcher switchModesAndOpenNewTab:command];
4574 return;
4575 }
4576
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004577 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234578 // events to the feature_engagement::Tracker based on |command.userInitiated|
4579 // and |command.incognito|.
4580 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004581
Mark Cogandfcdea72017-07-18 13:47:384582 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4583 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494584 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4585 self.foregroundTabWasAddedCompletionBlock;
Louis Romero960a12f2017-11-30 19:08:594586 __weak BrowserViewController* weakSelf = self;
Mark Cogandfcdea72017-07-18 13:47:384587 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494588 if (oldForegroundTabWasAddedCompletionBlock) {
4589 oldForegroundTabWasAddedCompletionBlock();
4590 }
Mark Cogandfcdea72017-07-18 13:47:384591 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4592 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4593 if (offTheRecord) {
4594 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4595 timeDelta);
4596 } else {
4597 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4598 }
Louis Romero960a12f2017-11-30 19:08:594599 if (command.shouldFocusOmnibox) {
4600 [weakSelf focusOmnibox];
4601 }
Mark Cogandfcdea72017-07-18 13:47:384602 };
4603
4604 [self setLastTapPoint:command];
Rohit Rao2e22b8d2017-11-07 19:54:544605 // When the tab switcher presentation experiment is enabled, the new tab can
4606 // be opened before BVC has been made visible onscreen. Test for this case by
4607 // checking if the parent container VC is currently in the process of being
4608 // presented.
4609 DCHECK(self.visible || self.dismissingModal ||
4610 (TabSwitcherPresentsBVCEnabled() &&
4611 self.parentViewController.isBeingPresented));
edchin3ab78ff2017-11-13 19:13:144612
4613 // In most cases, we want to take a snapshot of the current tab before opening
4614 // a new tab. However, if the current tab is not fully visible (did not finish
4615 // |-viewDidAppear:|, then we must not take an empty snapshot, replacing an
4616 // existing snapshot for the tab. This can happen when a new regular tab is
4617 // opened from an incognito tab. A different BVC is displayed, which may not
4618 // have enough time to finish appearing before a snapshot is requested.
Mark Cogandfcdea72017-07-18 13:47:384619 Tab* currentTab = [_model currentTab];
edchin3ab78ff2017-11-13 19:13:144620 if (currentTab && self.viewVisible) {
Mark Cogandfcdea72017-07-18 13:47:384621 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4622 }
4623 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4624 transition:ui::PAGE_TRANSITION_TYPED];
4625}
4626
Mark Cogan123895002017-07-20 12:54:064627- (void)printTab {
4628 Tab* currentTab = [_model currentTab];
4629 // The UI should prevent users from printing non-printable pages. However, a
4630 // redirection to an un-printable page can happen before it is reflected in
4631 // the UI.
4632 if (![currentTab viewForPrinting]) {
4633 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364634 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064635 return;
4636 }
4637 DCHECK(_browserState);
4638 if (!_printController) {
4639 _printController = [[PrintController alloc]
4640 initWithContextGetter:_browserState->GetRequestContext()];
4641 }
4642 [_printController printView:[currentTab viewForPrinting]
4643 withTitle:[currentTab title]
4644 viewController:self];
4645}
4646
Mark Coganfa25b052017-07-20 17:31:034647- (void)addToReadingList:(ReadingListAddCommand*)command {
4648 [self addToReadingListURL:[command URL] title:[command title]];
4649}
4650
sczs3a8c8602017-08-01 20:14:084651- (void)showReadingList {
4652 _readingListCoordinator = [[ReadingListCoordinator alloc]
4653 initWithBaseViewController:self
4654 browserState:self.browserState
4655 loader:self];
4656
4657 [_readingListCoordinator start];
4658}
4659
Jean-François Geyelinedef9552017-08-07 09:56:564660- (void)preloadVoiceSearch {
4661 // Preload VoiceSearchController and views and view controllers needed
4662 // for voice search.
4663 [self ensureVoiceSearchControllerCreated];
4664 _voiceSearchController->PrepareToAppear();
4665}
4666
edchinc5720722017-08-14 22:06:314667#if !defined(NDEBUG)
4668- (void)viewSource {
4669 Tab* tab = [_model currentTab];
4670 DCHECK(tab);
4671 CRWWebController* webController = tab.webController;
4672 NSString* script = @"document.documentElement.outerHTML;";
4673 __weak Tab* weakTab = tab;
4674 __weak BrowserViewController* weakSelf = self;
4675 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4676 Tab* strongTab = weakTab;
4677 if (!strongTab)
4678 return;
4679 if (![result isKindOfClass:[NSString class]])
4680 result = @"Not an HTML page";
4681 std::string base64HTML;
4682 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4683 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194684 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314685 web::ReferrerPolicyDefault);
4686
4687 [[weakSelf tabModel]
4688 insertTabWithURL:URL
4689 referrer:referrer
4690 transition:ui::PAGE_TRANSITION_LINK
4691 opener:strongTab
4692 openedByDOM:YES
4693 atIndex:TabModelConstants::kTabPositionAutomatically
4694 inBackground:NO];
4695 };
4696 [webController executeJavaScript:script
4697 completionHandler:completionHandlerBlock];
4698}
4699#endif // !defined(NDEBUG)
4700
edchin2134c042017-08-18 13:57:354701// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4702- (void)showRateThisAppDialog {
4703 DCHECK(!_rateThisAppDialog);
4704
4705 // Store the current timestamp whenever this dialog is shown.
4706 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4707 base::Time::Now().ToInternalValue());
4708
Gregory Chatzinofff39ec5162017-10-05 20:28:534709 // iOS11 no longer supports the itms link to the app store. So, use a deep
4710 // link for iOS11 and the itms link for prior versions.
4711 NSURL* storeURL;
4712 if (base::ios::IsRunningOnIOS11OrLater()) {
4713 storeURL =
4714 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4715 @"google-chrome-the-fast-and-secure-web-browser/"
4716 @"id535886823?action=write-review")];
4717 } else {
4718 storeURL = [NSURL
4719 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4720 @"MZStore.woa/wa/"
4721 @"viewContentsUserReviews?type=Purple+Software&id="
4722 @"535886823&pt=9008&ct=rating")];
4723 }
edchin2134c042017-08-18 13:57:354724
4725 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014726 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354727
4728 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4729 [_rateThisAppDialog setAppStoreURL:storeURL];
4730 [_rateThisAppDialog setDelegate:self];
4731 [_rateThisAppDialog show];
4732}
4733
Gregory Chatzinoff3f40c1542017-08-30 07:50:044734- (void)showFindInPage {
4735 if (!self.canShowFindBar)
4736 return;
4737
4738 if (!_findBarController) {
4739 _findBarController =
4740 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4741 _findBarController.dispatcher = self.dispatcher;
4742 }
4743
4744 Tab* tab = [_model currentTab];
4745 DCHECK(tab);
4746 auto* helper = FindTabHelper::FromWebState(tab.webState);
4747 DCHECK(!helper->IsFindUIActive());
4748 helper->SetFindUIActive(true);
4749 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4750}
4751
4752- (void)closeFindInPage {
4753 __weak BrowserViewController* weakSelf = self;
4754 Tab* currentTab = [_model currentTab];
4755 if (currentTab) {
4756 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4757 [weakSelf updateFindBar:NO shouldFocus:NO];
4758 });
4759 }
4760}
4761
4762- (void)searchFindInPage {
4763 DCHECK([_model currentTab]);
4764 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4765 __weak BrowserViewController* weakSelf = self;
4766 helper->StartFinding(
4767 [_findBarController searchTerm], ^(FindInPageModel* model) {
4768 BrowserViewController* strongSelf = weakSelf;
4769 if (!strongSelf) {
4770 return;
4771 }
4772 [strongSelf->_findBarController updateResultsCount:model];
4773 });
4774
4775 if (!_isOffTheRecord)
4776 helper->PersistSearchTerm();
4777}
4778
4779- (void)findNextStringInPage {
4780 Tab* currentTab = [_model currentTab];
4781 DCHECK(currentTab);
4782 // TODO(crbug.com/603524): Reshow find bar if necessary.
4783 FindTabHelper::FromWebState(currentTab.webState)
4784 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4785 [_findBarController updateResultsCount:model];
4786 });
4787}
4788
4789- (void)findPreviousStringInPage {
4790 Tab* currentTab = [_model currentTab];
4791 DCHECK(currentTab);
4792 // TODO(crbug.com/603524): Reshow find bar if necessary.
4793 FindTabHelper::FromWebState(currentTab.webState)
4794 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4795 [_findBarController updateResultsCount:model];
4796 });
4797}
4798
edchinf84b2502017-08-31 21:30:454799- (void)showHelpPage {
4800 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4801 [self webPageOrderedOpen:helpUrl
4802 referrer:web::Referrer()
4803 inBackground:NO
4804 appendTo:kCurrentTab];
4805}
4806
edchinb59b5602017-09-01 15:00:204807- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104808 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204809 [self showAllBookmarks];
4810 } else {
4811 [self initializeBookmarkInteractionController];
4812 [_bookmarkInteractionController presentBookmarks];
4813 }
4814}
4815
edchin8ee0807d2017-09-01 23:52:474816- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104817 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474818 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4819 } else {
4820 if (!self.recentTabsCoordinator) {
4821 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4822 initWithBaseViewController:self];
4823 self.recentTabsCoordinator.loader = self;
4824 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4825 self.recentTabsCoordinator.browserState = _browserState;
4826 }
4827 [self.recentTabsCoordinator start];
4828 }
4829}
4830
Mark Cogan6de7e9a2017-09-06 12:57:214831- (void)requestDesktopSite {
4832 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4833}
4834
4835- (void)requestMobileSite {
4836 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4837}
4838
sdefresnee65fd872016-12-19 13:38:134839#pragma mark - Command Handling
4840
sdefresnee65fd872016-12-19 13:38:134841- (void)closeCurrentTab {
4842 Tab* currentTab = [_model currentTab];
4843 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4844 if (tabIndex == NSNotFound)
4845 return;
4846
jif7fed8122017-02-08 13:15:254847 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4848 // iPad.
sdefresnee65fd872016-12-19 13:38:134849 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4850 exitingPage.image =
4851 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4852
4853 // Close the actual tab, and add its image as a subview.
4854 [_model closeTabAtIndex:tabIndex];
4855
4856 // Do not animate close in iPad.
4857 if (!IsIPadIdiom()) {
4858 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524859 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134860 exitingPage, 0, YES, IsPortrait(), ^{
4861 [exitingPage removeFromSuperview];
4862 });
4863 }
4864}
4865
Elodie Banelaa5ab432017-09-28 14:42:014866- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
4867 dismissOmnibox:(BOOL)dismissOmnibox {
Rohit Rao01e0e002017-08-14 20:49:434868 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134869 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4870 [_bookmarkInteractionController dismissSnackbar];
Elodie Banelaa5ab432017-09-28 14:42:014871 if (dismissOmnibox) {
sczsf1620e52017-10-02 22:54:464872 [_toolbarCoordinator cancelOmniboxEdit];
Elodie Banelaa5ab432017-09-28 14:42:014873 }
sdefresnee65fd872016-12-19 13:38:134874 [_dialogPresenter cancelAllDialogs];
Gregory Chatzinoffdf93d692017-09-09 01:32:274875 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:474876 [self.tabTipBubblePresenter dismissAnimated:NO];
Gregory Chatzinofff3ff2bc2017-12-15 01:19:264877 [self.incognitoTabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134878 if (_voiceSearchController)
4879 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364880
4881 Tab* currentTab = [_model currentTab];
4882 [currentTab dismissModals];
4883
rohitrao005a6432017-03-16 20:52:424884 if (currentTab) {
4885 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4886 if (findHelper) {
4887 findHelper->StopFinding(^{
4888 [self updateFindBar:NO shouldFocus:NO];
4889 });
4890 }
4891 }
rohitraob2bf3cb2017-02-10 14:10:364892
sdefresnee65fd872016-12-19 13:38:134893 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134894 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274895 _printController = nil;
Peter Laurense0b80f12017-11-21 07:52:404896 [self.dispatcher dismissToolsMenu];
sdefresnee65fd872016-12-19 13:38:134897 [_contextMenuCoordinator stop];
4898 [self dismissRateThisAppDialog];
4899
4900 if (self.presentedViewController) {
4901 // Dismisses any other modal controllers that may be present, e.g. Recent
4902 // Tabs.
Rohit Raoa1f1bac2017-11-07 16:27:514903 //
sdefresnee65fd872016-12-19 13:38:134904 // Note that currently, some controllers like the bookmark ones were already
4905 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
Rohit Raoa1f1bac2017-11-07 16:27:514906 // but are still reported as the presentedViewController. Calling
4907 // |dismissViewControllerAnimated:completion:| again would dismiss the BVC
4908 // itself, so instead check the value of |self.dismissingModal| and only
4909 // call dismiss if one of the above calls has not already triggered a
4910 // dismissal.
4911 //
4912 // To ensure the completion is called, nil is passed to the call to dismiss,
4913 // and the completion is called explicitly below.
4914 if (!TabSwitcherPresentsBVCEnabled() || !self.dismissingModal) {
4915 [self dismissViewControllerAnimated:NO completion:nil];
4916 }
sdefresnee65fd872016-12-19 13:38:134917 // Dismissed controllers will be so after a delay. Queue the completion
4918 // callback after that.
4919 if (completion) {
4920 dispatch_after(
4921 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4922 dispatch_get_main_queue(), ^{
4923 completion();
4924 });
4925 }
4926 } else if (completion) {
4927 // If no view controllers are presented, we should be ok with dispatching
4928 // the completion block directly.
4929 dispatch_async(dispatch_get_main_queue(), completion);
4930 }
4931}
4932
sdefresnee65fd872016-12-19 13:38:134933#pragma mark - Find Bar
4934
4935- (void)hideFindBarWithAnimation:(BOOL)animate {
4936 [_findBarController hideFindBarView:animate];
4937}
4938
4939- (void)showFindBarWithAnimation:(BOOL)animate
4940 selectText:(BOOL)selectText
4941 shouldFocus:(BOOL)shouldFocus {
4942 DCHECK(_findBarController);
4943 Tab* tab = [_model currentTab];
4944 DCHECK(tab);
4945 CRWWebController* webController = tab.webController;
4946
4947 CGRect referenceFrame = CGRectZero;
4948 if (IsIPadIdiom()) {
4949 referenceFrame = webController.visibleFrame;
4950 referenceFrame.origin.y -= kIPadFindBarOverlap;
4951 } else {
4952 referenceFrame = _contentArea.frame;
4953 }
4954
sczsf1620e52017-10-02 22:54:464955 CGRect omniboxFrame = [_toolbarCoordinator visibleOmniboxFrame];
sdefresnee65fd872016-12-19 13:38:134956 [_findBarController addFindBarView:animate
4957 intoView:self.view
4958 withFrame:referenceFrame
4959 alignWithFrame:omniboxFrame
4960 selectText:selectText];
4961 [self updateFindBar:YES shouldFocus:shouldFocus];
4962}
4963
sdefresnee65fd872016-12-19 13:38:134964- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324965 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4966 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4967 // in a crash.
4968 if (![_model currentTab]) {
4969 return;
4970 }
rohitrao005a6432017-03-16 20:52:424971 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4972 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134973 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424974 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134975 }
4976
4977 [self setFramesForHeaders:[self headerViews]
4978 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424979 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134980 initialUpdate:initialUpdate
4981 focusTextfield:shouldFocus];
4982 } else {
4983 [self hideFindBarWithAnimation:YES];
4984 }
4985}
4986
4987- (void)showAllBookmarks {
4988 DCHECK(self.visible || self.dismissingModal);
4989 GURL URL(kChromeUIBookmarksURL);
4990 Tab* tab = [_model currentTab];
4991 web::NavigationManager::WebLoadParams params(URL);
4992 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234993 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134994}
4995
Gauthier Ambardf520c022017-08-29 07:42:234996- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134997 DCHECK(self.visible || self.dismissingModal);
4998 GURL url(kChromeUINewTabURL);
4999 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
5000 if (fragment != "") {
5001 GURL::Replacements replacement;
5002 replacement.SetRefStr(fragment);
5003 url = url.ReplaceComponents(replacement);
5004 }
5005 Tab* tab = [_model currentTab];
5006 web::NavigationManager::WebLoadParams params(url);
5007 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:235008 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:135009}
5010
sdefresnee65fd872016-12-19 13:38:135011- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:275012 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:135013 base::RecordAction(base::UserMetricsAction(
5014 "IOSRateThisAppDialogDismissedProgramatically"));
5015 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:275016 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135017 }
5018}
5019
Jean-François Geyelin5d2e184c2017-07-28 19:48:005020- (void)startVoiceSearchWithOriginView:(UIView*)originView {
5021 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:135022 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:125023 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:135024 _startVoiceSearchAfterNewTabAnimation = YES;
5025 return;
5026 }
5027
5028 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
5029 // dismiss the keyboard.
5030 [self closeFindInPage];
5031 [[_model currentTab].webController dismissKeyboard];
5032
5033 // Ensure that voice search objects are created.
5034 [self ensureVoiceSearchControllerCreated];
5035 [self ensureVoiceSearchBarCreated];
5036
5037 // Present voice search.
5038 [_voiceSearchBar prepareToPresentVoiceSearch];
5039 _voiceSearchController->StartRecognition(self, [_model currentTab]);
sczsf1620e52017-10-02 22:54:465040 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:135041}
5042
5043#pragma mark - ToolbarOwner
5044
Kurt Horimotoea429dd2017-11-28 02:24:305045- (CGFloat)toolbarHeight {
5046 return [self headerHeight];
5047}
5048
Gauthier Ambard04ddb512017-11-07 09:14:165049- (CGRect)toolbarFrame {
sczs42f7f7482017-11-08 01:13:275050 return _toolbarCoordinator.toolbarViewController.view.frame;
Gauthier Ambard04ddb512017-11-07 09:14:165051}
5052
Gauthier Ambard996d9b12017-11-06 09:39:215053- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
5054 id<ToolbarSnapshotProviding> toolbarSnapshotProvider = nil;
sczs42f7f7482017-11-08 01:13:275055 if (_toolbarCoordinator.toolbarViewController.view.hidden) {
Gauthier Ambard996d9b12017-11-06 09:39:215056 Tab* currentTab = [_model currentTab];
5057 if (currentTab.webState &&
5058 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
5059 // Use the native content controller's toolbar when the BVC's is hidden.
5060 id nativeController = [self nativeControllerForTab:currentTab];
5061 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
5062 toolbarSnapshotProvider = [nativeController toolbarSnapshotProvider];
5063 }
5064 }
5065 } else {
5066 toolbarSnapshotProvider = _toolbarCoordinator;
5067 }
5068 return toolbarSnapshotProvider;
5069}
5070
sdefresnee65fd872016-12-19 13:38:135071#pragma mark - TabModelObserver methods
5072
5073// Observer method, tab inserted.
5074- (void)tabModel:(TabModel*)model
5075 didInsertTab:(Tab*)tab
5076 atIndex:(NSUInteger)modelIndex
5077 inForeground:(BOOL)fg {
5078 DCHECK(tab);
5079 [self installDelegatesForTab:tab];
5080
5081 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:195082 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:135083 }
5084}
5085
5086// Observer method, active tab changed.
5087- (void)tabModel:(TabModel*)model
5088 didChangeActiveTab:(Tab*)newTab
5089 previousTab:(Tab*)previousTab
5090 atIndex:(NSUInteger)index {
5091 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
5092 // Currently this observer method is always called with a non-nil |newTab|,
5093 // but that may change in the future. Remove this DCHECK when it does.
5094 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:275095 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:305096 DCHECK(newTab.webState);
5097 infobars::InfoBarManager* infoBarManager =
5098 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:135099 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
5100 }
5101 [self updateVoiceSearchBarVisibilityAnimated:NO];
5102
Mohamad Ahmadi7d09ec32017-07-11 22:32:195103 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:135104
Gauthier Ambard64396902017-12-08 10:14:585105 [self tabSelected:newTab notifyToolbar:YES];
sdefresnee65fd872016-12-19 13:38:135106}
5107
5108// Observer method, tab changed.
5109- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
5110 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
5111 if (tab == [_model currentTab]) {
5112 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:135113 }
5114}
5115
sdefresne49cf2862017-03-15 13:46:145116// Observer method, tab replaced.
5117- (void)tabModel:(TabModel*)model
5118 didReplaceTab:(Tab*)oldTab
5119 withTab:(Tab*)newTab
5120 atIndex:(NSUInteger)index {
5121 [self uninstallDelegatesForTab:oldTab];
5122 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:265123
michaeldo79909fb2017-05-09 23:42:505124 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:305125 infobars::InfoBarManager* infoBarManager = nullptr;
5126 if (newTab) {
5127 DCHECK(newTab.webState);
5128 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
5129 }
michaeldo79909fb2017-05-09 23:42:505130 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
5131 }
5132
Kurt Horimoto354f7f82017-12-16 02:22:125133 if (self.active && model.currentTab == newTab) {
5134 // Add |newTab|'s view to the hierarchy if it's the current Tab.
kkhorimotofa0844cc2017-03-20 17:01:265135 [self displayTab:newTab isNewSelection:NO];
Kurt Horimoto354f7f82017-12-16 02:22:125136 // Reset the temporary native controller since the current tab is replaced.
5137 _temporaryNativeController = nil;
5138 // If this is occurring for an inserted prerender tab, update its content
5139 // offset so that it's below the toolbar.
5140 if (_insertedTabWasPrerenderedTab) {
5141 CRWWebViewProxyType webViewProxy = newTab.webState->GetWebViewProxy();
5142 CRWWebViewScrollViewProxy* scrollProxy = webViewProxy.scrollViewProxy;
5143 CGFloat toolbarHeight = [self toolbarHeight];
5144 scrollProxy.contentOffset = CGPointMake(0, -toolbarHeight);
5145 webViewProxy.topContentPadding = toolbarHeight;
5146 }
5147 }
Mohamad Ahmadibec07eb2017-09-12 19:38:465148
5149 if (newTab)
5150 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:145151}
5152
sdefresnee65fd872016-12-19 13:38:135153// A tab has been removed, remove its views from display if necessary.
5154- (void)tabModel:(TabModel*)model
5155 didRemoveTab:(Tab*)tab
5156 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:145157 [self uninstallDelegatesForTab:tab];
5158
kkhorimoto496fdd72017-06-12 19:56:315159 // Cancel dialogs for |tab|'s WebState.
5160 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5161
sdefresnee65fd872016-12-19 13:38:135162 // Ignore changes while the tab stack view is visible (or while suspended).
5163 // The display will be refreshed when this view becomes active again.
5164 if (!self.visible || !model.webUsageEnabled)
5165 return;
5166
5167 // Remove the find bar for now.
5168 [self hideFindBarWithAnimation:NO];
5169}
5170
5171- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
5172 if (tab == [model currentTab]) {
5173 [_contentArea displayContentView:nil];
sczsf1620e52017-10-02 22:54:465174 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:135175 }
5176
Mohamad Ahmadi7d09ec32017-07-11 22:32:195177 [_paymentRequestManager stopTrackingWebState:tab.webState];
5178
sdefresnee65fd872016-12-19 13:38:135179 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
5180 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:195181 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:135182 }
5183}
5184
5185// Called when the number of tabs changes. Update the toolbar accordingly.
5186- (void)tabModelDidChangeTabCount:(TabModel*)model {
5187 DCHECK(model == _model);
sczsf1620e52017-10-02 22:54:465188 [_toolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:135189}
5190
5191#pragma mark - Upgrade Detection
5192
5193- (void)showUpgrade:(UpgradeCenter*)center {
5194 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:275195 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:135196 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:305197 DCHECK(tab.webState);
5198 infobars::InfoBarManager* infoBarManager =
5199 InfoBarManagerImpl::FromWebState(tab.webState);
5200 DCHECK(infoBarManager);
5201 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:135202 }
5203}
5204
Mark Cogan80aa28d2017-11-30 13:11:345205#pragma mark - InfobarContainerStateDelegate
sdefresnee65fd872016-12-19 13:38:135206
Mark Cogan80aa28d2017-11-30 13:11:345207- (void)infoBarContainerStateDidChangeAnimated:(BOOL)animated {
sdefresnee65fd872016-12-19 13:38:135208 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
5209 DCHECK(infoBarContainerView);
5210 CGRect containerFrame = infoBarContainerView.frame;
5211 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
5212 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
5213 containerFrame.size.height = height;
5214 BOOL isViewVisible = self.visible;
5215 [UIView animateWithDuration:0.1
5216 animations:^{
5217 [infoBarContainerView setFrame:containerFrame];
5218 }
5219 completion:^(BOOL finished) {
5220 if (!isViewVisible)
5221 return;
5222 UIAccessibilityPostNotification(
5223 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
5224 }];
5225}
5226
Mark Cogan80aa28d2017-11-30 13:11:345227#pragma mark - UIGestureRecognizerDelegate
sdefresnee65fd872016-12-19 13:38:135228
5229// Always return yes, as this tap should work with various recognizers,
5230// including UITextTapRecognizer, UILongPressGestureRecognizer,
5231// UIScrollViewPanGestureRecognizer and others.
5232- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
5233 shouldRecognizeSimultaneouslyWithGestureRecognizer:
5234 (UIGestureRecognizer*)otherGestureRecognizer {
5235 return YES;
5236}
5237
5238// Tap gestures should only be recognized within |_contentArea|.
5239- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
5240 CGPoint location = [gesture locationInView:self.view];
5241
5242 // Only allow touches on descendant views of |_contentArea|.
5243 UIView* hitView = [self.view hitTest:location withEvent:nil];
5244 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
5245}
5246
5247#pragma mark - SideSwipeController Delegate Methods
5248
5249- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
5250 DCHECK(!IsIPadIdiom());
5251 // Update frame incase orientation changed while |_contentArea| was out of
5252 // the view hierarchy.
5253 [_contentArea setFrame:[sideSwipeView frame]];
5254
Justin Cohen16ad60e2017-11-10 14:56:265255 [self.view insertSubview:_contentArea aboveSubview:_fakeStatusBarView];
sdefresnee65fd872016-12-19 13:38:135256 [self updateVoiceSearchBarVisibilityAnimated:NO];
5257 [self updateToolbar];
5258
5259 // Reset horizontal stack view.
5260 [sideSwipeView removeFromSuperview];
5261 [_sideSwipeController setInSwipe:NO];
5262 [_infoBarContainer->view() setHidden:NO];
5263}
5264
5265- (UIView*)contentView {
5266 return _contentArea;
5267}
5268
sdefresnee65fd872016-12-19 13:38:135269- (BOOL)preventSideSwipe {
Peter Laurense0b80f12017-11-21 07:52:405270 if ([_toolbarCoordinator isShowingToolsMenu])
sdefresnee65fd872016-12-19 13:38:135271 return YES;
5272
5273 if (_voiceSearchController && _voiceSearchController->IsVisible())
5274 return YES;
5275
sdefresnee65fd872016-12-19 13:38:135276 if (!self.active)
5277 return YES;
5278
5279 return NO;
5280}
5281
5282- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5283 if (visible) {
5284 [self updateVoiceSearchBarVisibilityAnimated:NO];
5285 [self updateToolbar];
5286 [_infoBarContainer->view() setHidden:NO];
5287 } else {
5288 // Hide UI accessories such as find bar and first visit overlays
5289 // for welcome page.
5290 [self hideFindBarWithAnimation:NO];
5291 [_infoBarContainer->view() setHidden:YES];
5292 [_voiceSearchBar setHidden:YES];
5293 }
5294}
5295
5296- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5297 BOOL seenToolbar = NO;
5298 BOOL seenInfoBarContainer = NO;
5299 BOOL seenContentArea = NO;
5300 for (UIView* view in views.subviews) {
sczs42f7f7482017-11-08 01:13:275301 if (view == _toolbarCoordinator.toolbarViewController.view)
sdefresnee65fd872016-12-19 13:38:135302 seenToolbar = YES;
5303 else if (view == _infoBarContainer->view())
5304 seenInfoBarContainer = YES;
5305 else if (view == _contentArea)
5306 seenContentArea = YES;
5307 if ((seenToolbar && !seenInfoBarContainer) ||
5308 (seenInfoBarContainer && !seenContentArea))
5309 return NO;
5310 }
5311 return YES;
5312}
5313
5314#pragma mark - PreloadControllerDelegate methods
5315
rohitraoeeb5293b2017-06-15 14:40:025316- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445317 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135318}
5319
rohitraoeeb5293b2017-06-15 14:40:025320- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5321 return [self hasControllerForURL:url];
5322}
5323
Gauthier Ambard65e949b092017-11-29 08:46:205324// TODO(crbug.com/788705): BVC doesn't need to implement
5325// BookmarkModelBridgeObserver once the new toolbar is turned on.
5326#pragma mark - BookmarkModelBridgeObserver
sdefresnee65fd872016-12-19 13:38:135327
5328// If an added or removed bookmark is the same as the current url, update the
5329// toolbar so the star highlight is kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205330- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
5331 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:135332}
5333
5334// If all bookmarks are removed, update the toolbar so the star highlight is
5335// kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205336- (void)bookmarkModelRemovedAllNodes {
sdefresnee65fd872016-12-19 13:38:135337 [self updateToolbar];
5338}
5339
Gauthier Ambard65e949b092017-11-29 08:46:205340// In case we are on a bookmarked page before the model is loaded.
5341- (void)bookmarkModelLoaded {
5342 [self updateToolbar];
5343}
5344
5345- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
5346 // No-op -- required by BookmarkModelBridgeObserver but not used.
5347}
5348
5349- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
5350 movedFromParent:(const BookmarkNode*)oldParent
5351 toParent:(const BookmarkNode*)newParent {
5352 // No-op -- required by BookmarkModelBridgeObserver but not used.
5353}
5354
5355- (void)bookmarkNodeDeleted:(const BookmarkNode*)node
5356 fromFolder:(const BookmarkNode*)folder {
5357 // No-op -- required by BookmarkModelBridgeObserver but not used.
5358}
5359
5360#pragma mark - Alerts
5361
sdefresnee65fd872016-12-19 13:38:135362- (void)showErrorAlertWithStringTitle:(NSString*)title
5363 message:(NSString*)message {
5364 // Dismiss current alert.
5365 [_alertCoordinator stop];
5366
stkhapuginc9eee7b2017-04-10 15:49:275367 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5368 message:message
5369 viewController:self];
sdefresnee65fd872016-12-19 13:38:135370 [_alertCoordinator start];
5371}
5372
edchineeb4d422017-10-02 17:39:365373- (void)showSnackbar:(NSString*)text {
5374 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
5375 message.accessibilityLabel = text;
5376 message.duration = 2.0;
5377 message.category = kBrowserViewControllerSnackbarCategory;
5378 [self.dispatcher showSnackbarMessage:message];
5379}
5380
sdefresnee65fd872016-12-19 13:38:135381#pragma mark - Show Mail Composer methods
5382
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575383- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5384 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135385 if (![MFMailComposeViewController canSendMail]) {
5386 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575387 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135388 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575389 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135390 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5391 return;
5392 }
stkhapuginc9eee7b2017-04-10 15:49:275393 MFMailComposeViewController* mailViewController =
5394 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135395 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575396 [mailViewController setToRecipients:[context toRecipients]];
5397 [mailViewController setSubject:[context subject]];
5398 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135399
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575400 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135401 if (!textFile.empty()) {
5402 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5403 NSData* data = [NSData dataWithContentsOfFile:filename];
5404 if (data) {
5405 NSString* displayName =
5406 base::SysUTF8ToNSString(textFile.BaseName().value());
5407 [mailViewController addAttachmentData:data
5408 mimeType:@"text/plain"
5409 fileName:displayName];
5410 }
5411 }
5412
5413 [mailViewController setMailComposeDelegate:self];
5414 [self presentViewController:mailViewController animated:YES completion:nil];
5415}
5416
5417#pragma mark - MFMailComposeViewControllerDelegate methods
5418
5419- (void)mailComposeController:(MFMailComposeViewController*)controller
5420 didFinishWithResult:(MFMailComposeResult)result
5421 error:(NSError*)error {
5422 [self dismissViewControllerAnimated:YES completion:nil];
5423}
5424
5425#pragma mark - StoreKitLauncher methods
5426
5427- (void)productViewControllerDidFinish:
5428 (SKStoreProductViewController*)viewController {
5429 [self dismissViewControllerAnimated:YES completion:nil];
5430}
5431
5432- (void)openAppStore:(NSString*)appId {
5433 if (![appId length])
5434 return;
5435 NSDictionary* product =
5436 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275437 SKStoreProductViewController* storeViewController =
5438 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135439 [storeViewController setDelegate:self];
5440 [storeViewController loadProductWithParameters:product completionBlock:nil];
5441 [self presentViewController:storeViewController animated:YES completion:nil];
5442}
5443
5444#pragma mark - TabDialogDelegate methods
5445
sdefresnee65fd872016-12-19 13:38:135446- (void)cancelDialogForTab:(Tab*)tab {
5447 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5448}
5449
5450#pragma mark - FKFeedbackPromptDelegate methods
5451
5452- (void)userTappedRateApp:(UIView*)view {
5453 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275454 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135455}
5456
5457- (void)userTappedSendFeedback:(UIView*)view {
5458 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275459 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205460 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135461}
5462
5463- (void)userTappedDismiss:(UIView*)view {
5464 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275465 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135466}
5467
5468#pragma mark - VoiceSearchBarDelegate
5469
5470- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275471 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135472 [self ensureVoiceSearchControllerCreated];
5473 return _voiceSearchController->IsTextToSpeechEnabled() &&
5474 _voiceSearchController->IsTextToSpeechSupported();
5475}
5476
5477- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275478 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135479 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5480}
5481
5482#pragma mark - VoiceSearchPresenter
5483
5484- (UIView*)voiceSearchButton {
5485 return _voiceSearchButton;
5486}
5487
5488- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5489 return [self currentLogoAnimationControllerOwner];
5490}
5491
Rohit Rao01e0e002017-08-14 20:49:435492#pragma mark - ActivityService Providers
5493
5494- (void)presentActivityServiceViewController:(UIViewController*)controller {
5495 [self presentViewController:controller animated:YES completion:nil];
5496}
5497
5498- (void)activityServiceDidEndPresenting {
5499 self.presenting = NO;
5500 [self.dialogPresenter tryToPresent];
5501}
5502
Rohit Raocda0a992017-08-16 15:37:115503#pragma mark - QRScanner Requirements
5504
5505- (void)presentQRScannerViewController:(UIViewController*)controller {
5506 [self presentViewController:controller animated:YES completion:nil];
5507}
5508
5509- (void)dismissQRScannerViewController:(UIViewController*)controller
5510 completion:(void (^)(void))completion {
5511 DCHECK_EQ(controller, self.presentedViewController);
5512 [self dismissViewControllerAnimated:YES completion:completion];
5513}
5514
sczsdd860eba2017-08-10 01:55:385515#pragma mark - TabHistoryPresenter
5516
sczs0a726d22017-08-21 22:40:135517- (UIView*)viewForTabHistoryPresentation {
5518 return self.view;
5519}
5520
sczsdd860eba2017-08-10 01:55:385521- (void)prepareForTabHistoryPresentation {
5522 DCHECK(self.visible || self.dismissingModal);
5523 [[self.tabModel currentTab].webController dismissKeyboard];
sczsf1620e52017-10-02 22:54:465524 [_toolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385525}
5526
Mike Doughertya1ec26402017-08-23 19:46:315527#pragma mark - CaptivePortalDetectorTabHelperDelegate
5528
Mike Dougherty4620cf8e2017-10-31 23:37:095529- (void)captivePortalDetectorTabHelper:
5530 (CaptivePortalDetectorTabHelper*)tabHelper
5531 connectWithLandingURL:(const GURL&)landingURL {
Mike Dougherty66e58812017-11-03 06:54:285532 [self addSelectedTabWithURL:landingURL transition:ui::PAGE_TRANSITION_TYPED];
Mike Doughertya1ec26402017-08-23 19:46:315533}
5534
Gregory Chatzinoffdf93d692017-09-09 01:32:275535#pragma mark - PageInfoPresentation
5536
Gregory Chatzinoffb6a01f72017-09-20 20:06:395537- (void)presentPageInfoView:(UIView*)pageInfoView {
5538 [pageInfoView setFrame:self.view.bounds];
5539 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275540}
5541
5542- (void)prepareForPageInfoPresentation {
5543 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:465544 [_toolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275545}
5546
Gregory Chatzinoffb6a01f72017-09-20 20:06:395547- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5548 return [self.view convertPoint:origin fromView:nil];
5549}
5550
Sylvain Defresnecacc3a52017-09-12 13:51:045551#pragma mark - WebStatePrinter
5552
5553- (void)printWebState:(web::WebState*)webState {
5554 if (webState == [_model currentTab].webState)
5555 [self printTab];
5556}
5557
Eugene But35ded552017-09-13 23:31:595558#pragma mark - RepostFormTabHelperDelegate
5559
5560- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
Sylvain Defresnee3c698122017-11-17 11:16:325561 presentRepostFormDialogForWebState:(web::WebState*)webState
5562 dialogAtPoint:(CGPoint)location
5563 completionHandler:(void (^)(BOOL))completion {
5564 _repostFormCoordinator =
5565 [[RepostFormCoordinator alloc] initWithBaseViewController:self
5566 dialogLocation:location
5567 webState:webState
5568 completionHandler:completion];
Eugene But35ded552017-09-13 23:31:595569 [_repostFormCoordinator start];
5570}
5571
5572- (void)repostFormTabHelperDismissRepostFormDialog:
5573 (RepostFormTabHelper*)helper {
5574 _repostFormCoordinator = nil;
5575}
5576
edchinf5150c682017-09-18 02:50:035577#pragma mark - TabStripPresentation
5578
5579- (BOOL)isTabStripFullyVisible {
5580 return ([self currentHeaderOffset] == 0.0f);
5581}
5582
5583- (void)showTabStripView:(UIView*)tabStripView {
5584 DCHECK([self isViewLoaded]);
5585 DCHECK(tabStripView);
5586 self.tabStripView = tabStripView;
5587 CGRect tabStripFrame = [self.tabStripView frame];
5588 tabStripFrame.origin = CGPointZero;
5589 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5590 // because the CGPointZero above will break reset the offset, but it's not
5591 // clear what removing that will do.
5592 tabStripFrame.origin.y = [self headerOffset];
5593 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5594 [self.tabStripView setFrame:tabStripFrame];
5595 [[self view] addSubview:tabStripView];
5596}
5597
edchincd32fdf2017-10-25 12:45:455598#pragma mark - ManageAccountsDelegate
5599
5600- (void)onManageAccounts {
5601 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5602 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5603 ->GetState());
edchin5b8aa052017-10-30 23:27:285604 [self.dispatcher showAccountsSettingsFromViewController:self];
edchincd32fdf2017-10-25 12:45:455605}
5606
5607- (void)onAddAccount {
5608 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5609 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5610 ->GetState());
edchinb082b2982017-11-03 19:41:215611 [self.dispatcher showAddAccountFromViewController:self];
edchincd32fdf2017-10-25 12:45:455612}
5613
5614- (void)onGoIncognito:(const GURL&)url {
5615 // The user taps on go incognito from the mobile U-turn webpage (the web page
5616 // that displays all users accounts available in the content area). As the
5617 // user chooses to go to incognito, the mobile U-turn page is no longer
5618 // neeeded. The current solution is to go back in history. This has the
5619 // advantage of keeping the current browsing session and give a good user
5620 // experience when the user comes back from incognito.
5621 [self.tabModel.currentTab goBack];
5622
5623 if (url.is_valid()) {
5624 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5625 initWithURL:url
5626 referrer:web::Referrer() // Strip referrer when switching modes.
5627 inIncognito:YES
5628 inBackground:NO
5629 appendTo:kLastTab];
5630 [self.dispatcher openURL:command];
5631 } else {
5632 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5633 }
5634}
5635
edchin95c927072017-11-04 00:35:075636#pragma mark - SyncPresenter
5637
5638- (void)showReauthenticateSignin {
5639 [self.dispatcher
edchin3b46e8d2017-11-07 22:48:125640 showSignin:
5641 [[ShowSigninCommand alloc]
5642 initWithOperation:AUTHENTICATION_OPERATION_REAUTHENTICATE
5643 accessPoint:signin_metrics::AccessPoint::
5644 ACCESS_POINT_UNKNOWN]
5645 baseViewController:self];
edchin95c927072017-11-04 00:35:075646}
5647
5648- (void)showSyncSettings {
edchina14d7182017-11-06 18:37:505649 [self.dispatcher showSyncSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075650}
5651
5652- (void)showSyncPassphraseSettings {
edchinec723062017-11-06 20:03:545653 [self.dispatcher showSyncPassphraseSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075654}
5655
edchin9e7a1112017-11-07 18:28:035656#pragma mark - SigninPresenter
5657
5658- (void)showSignin:(ShowSigninCommand*)command {
edchin3b46e8d2017-11-07 22:48:125659 [self.dispatcher showSignin:command baseViewController:self];
edchin9e7a1112017-11-07 18:28:035660}
5661
sdefresnee65fd872016-12-19 13:38:135662@end