blob: 04f67b39c194e98dbf833f06bf88bef4352be0bc [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"
66#include "ios/chrome/browser/experimental_flags.h"
67#import "ios/chrome/browser/favicon/favicon_loader.h"
68#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2369#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
70#include "ios/chrome/browser/feature_engagement/tracker_util.h"
sdefresnee65fd872016-12-19 13:38:1371#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
72#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3673#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1374#include "ios/chrome/browser/first_run/first_run.h"
75#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
Mark Cogan80aa28d2017-11-30 13:11:3476#include "ios/chrome/browser/infobars/infobar_container_delegate_ios.h"
sdefresnee65fd872016-12-19 13:38:1377#include "ios/chrome/browser/infobars/infobar_container_ios.h"
Mark Cogan80aa28d2017-11-30 13:11:3478#import "ios/chrome/browser/infobars/infobar_container_state_delegate.h"
sdefresnee65fd872016-12-19 13:38:1379#include "ios/chrome/browser/infobars/infobar_container_view.h"
Rohit Raoaf46af92017-08-10 12:52:3080#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
Mark Coganca30df62017-11-20 14:29:1181#import "ios/chrome/browser/language/url_language_histogram_factory.h"
sdefresnee65fd872016-12-19 13:38:1382#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
83#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
sdefresnee65fd872016-12-19 13:38:1384#import "ios/chrome/browser/open_url_util.h"
85#import "ios/chrome/browser/passwords/password_controller.h"
Tomasz Garbusb844e992017-09-29 12:44:5586#include "ios/chrome/browser/passwords/password_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1387#include "ios/chrome/browser/pref_names.h"
Rohit Rao44f204302017-08-10 14:49:5488#import "ios/chrome/browser/prerender/preload_controller_delegate.h"
89#import "ios/chrome/browser/prerender/prerender_service.h"
90#import "ios/chrome/browser/prerender/prerender_service_factory.h"
olivierrobin013ba672017-03-01 21:16:2491#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1392#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
93#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
94#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0595#include "ios/chrome/browser/sessions/session_util.h"
sdefresnee65fd872016-12-19 13:38:1396#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
97#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
edchincd32fdf2017-10-25 12:45:4598#import "ios/chrome/browser/signin/account_consistency_service_factory.h"
99#include "ios/chrome/browser/signin/account_reconcilor_factory.h"
sdefresnee65fd872016-12-19 13:38:13100#import "ios/chrome/browser/snapshots/snapshot_cache.h"
101#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
102#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
Mike Dougherty4620cf8e2017-10-31 23:37:09103#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
104#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper_delegate.h"
pkld6e73e52017-03-08 15:56:51105#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:28106#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13107#import "ios/chrome/browser/tabs/tab.h"
108#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:19109#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:13110#import "ios/chrome/browser/tabs/tab_model.h"
111#import "ios/chrome/browser/tabs/tab_model_observer.h"
Sylvain Defresne72c530e42017-08-25 15:28:16112#import "ios/chrome/browser/tabs/tab_private.h"
sdefresnee65fd872016-12-19 13:38:13113#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
Mark Coganca30df62017-11-20 14:29:11114#import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
115#import "ios/chrome/browser/translate/language_selection_handler.h"
Rohit Rao01e0e002017-08-14 20:49:43116#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
117#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
sdefresnee65fd872016-12-19 13:38:13118#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
Eugene But35ded552017-09-13 23:31:59119#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13120#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
121#import "ios/chrome/browser/ui/background_generator.h"
122#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
Gauthier Ambard65e949b092017-11-29 08:46:20123#include "ios/chrome/browser/ui/bookmarks/bookmark_model_bridge_observer.h"
sdefresnee65fd872016-12-19 13:38:13124#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13125#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
Cooper Knaak33f9f402017-08-09 18:04:38126#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
sdefresnee65fd872016-12-19 13:38:13127#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
Mark Cogan5e3da152017-07-11 15:57:30128#import "ios/chrome/browser/ui/commands/application_commands.h"
Mark Cogan6c58ea92017-07-06 13:08:24129#import "ios/chrome/browser/ui/commands/browser_commands.h"
edchin9badb062017-08-16 18:47:54130#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
Mark Cogandfcdea72017-07-18 13:47:38131#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13132#import "ios/chrome/browser/ui/commands/open_url_command.h"
133#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
edchin95c927072017-11-04 00:35:07134#import "ios/chrome/browser/ui/commands/show_signin_command.h"
edchin7f210cd2017-09-28 08:03:53135#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00136#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23137#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13138#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13139#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
140#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
141#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
142#import "ios/chrome/browser/ui/external_file_controller.h"
Louis Romerod11747a2017-10-20 20:10:35143#import "ios/chrome/browser/ui/external_search/external_search_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13144#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
145#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
Kurt Horimoto803840622017-10-28 01:20:37146#import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
Kurt Horimoto62e97c72017-11-03 19:51:47147#import "ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38148#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13149#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13150#import "ios/chrome/browser/ui/key_commands_provider.h"
Kurt Horimoto1945ef42017-10-26 03:57:26151#import "ios/chrome/browser/ui/location_bar_notification_names.h"
Rohit Rao9a8ad772017-10-30 22:35:59152#import "ios/chrome/browser/ui/main/main_feature_flags.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10153#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13154#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42155#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13156#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27157#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
158#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13159#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37160#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
Mark Coganca30df62017-11-20 14:29:11161#import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
sdefresnee65fd872016-12-19 13:38:13162#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11163#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
164#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13165#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03166#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13167#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13168#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53169#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
sczs40443972017-09-13 19:02:39170#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13171#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
edchin9e7a1112017-11-07 18:28:03172#import "ios/chrome/browser/ui/signin_interaction/public/signin_presenter.h"
edchin7f210cd2017-09-28 08:03:53173#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13174#import "ios/chrome/browser/ui/stack_view/card_view.h"
175#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
176#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13177#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03178#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
179#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
180#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
Gauthier Ambard76d826ac2017-11-16 16:27:34181#include "ios/chrome/browser/ui/toolbar/legacy_toolbar_coordinator.h"
Kurt Horimotoea429dd2017-11-28 02:24:30182#import "ios/chrome/browser/ui/toolbar/legacy_toolbar_ui_updater.h"
sdefresnee65fd872016-12-19 13:38:13183#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
184#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
Gauthier Ambard29939db12017-10-30 16:47:31185#import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
Kurt Horimotoea429dd2017-11-28 02:24:30186#import "ios/chrome/browser/ui/toolbar/toolbar_ui.h"
sczsc2b4f152017-10-11 01:44:24187#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
Peter Laurense0b80f12017-11-21 07:52:40188#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_configuration_provider.h"
189#import "ios/chrome/browser/ui/tools_menu/public/tools_menu_presentation_provider.h"
sczsbbad1632017-07-29 03:48:00190#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13191#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
Mark Coganca30df62017-11-20 14:29:11192#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13193#include "ios/chrome/browser/ui/ui_util.h"
194#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28195#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13196#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
197#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51198#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13199#import "ios/chrome/browser/web/error_page_content.h"
Danyao Wang85389a82017-10-25 18:56:27200#import "ios/chrome/browser/web/load_timing_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13201#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04202#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05203#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59204#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53205#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04206#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05207#import "ios/chrome/browser/web_state_list/web_state_list.h"
208#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57209#import "ios/chrome/browser/webui/net_export_tab_helper.h"
210#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
211#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13212#include "ios/chrome/grit/ios_chromium_strings.h"
213#include "ios/chrome/grit/ios_strings.h"
214#import "ios/net/request_tracker.h"
215#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
216#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
217#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
218#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
219#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
220#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
221#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
222#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
edchineeb4d422017-10-02 17:39:36223#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
sdefresnee65fd872016-12-19 13:38:13224#include "ios/web/public/navigation_item.h"
225#import "ios/web/public/navigation_manager.h"
226#include "ios/web/public/referrer_util.h"
227#include "ios/web/public/ssl_status.h"
228#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39229#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13230#include "ios/web/public/web_client.h"
231#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13232#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29233#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
Sylvain Defresnee7f2c8a2017-10-17 02:39:19234#import "ios/web/public/web_state/web_state.h"
sdefresnee65fd872016-12-19 13:38:13235#import "ios/web/public/web_state/web_state_delegate_bridge.h"
236#include "ios/web/public/web_thread.h"
237#import "ios/web/web_state/ui/crw_web_controller.h"
238#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17239#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13240#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
241#include "net/ssl/ssl_info.h"
242#include "net/url_request/url_request_context_getter.h"
243#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
244#include "ui/base/l10n/l10n_util.h"
245#include "ui/base/l10n/l10n_util_mac.h"
246#include "ui/base/page_transition_types.h"
247#include "url/gurl.h"
248
stkhapuginf58b10d02017-04-10 13:36:17249#if !defined(__has_feature) || !__has_feature(objc_arc)
250#error "This file requires ARC support."
251#endif
252
sdefresnee65fd872016-12-19 13:38:13253using base::UserMetricsAction;
254using bookmarks::BookmarkNode;
255
sdefresnee65fd872016-12-19 13:38:13256class InfoBarContainerDelegateIOS;
257
sdefresnee65fd872016-12-19 13:38:13258namespace {
259
260typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
261 // Note: these values must match the ContextMenuOption enum in histograms.xml.
262 ACTION_OPEN_IN_NEW_TAB = 0,
263 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
264 ACTION_COPY_LINK_ADDRESS = 2,
265 ACTION_SAVE_IMAGE = 6,
266 ACTION_OPEN_IMAGE = 7,
267 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
268 ACTION_SEARCH_BY_IMAGE = 11,
269 ACTION_OPEN_JAVASCRIPT = 21,
270 ACTION_READ_LATER = 22,
271 NUM_ACTIONS = 23,
272};
273
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28274void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13275 if (is_image) {
276 if (is_link) {
277 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
278 NUM_ACTIONS);
279 } else {
280 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
281 NUM_ACTIONS);
282 }
283 } else {
284 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
285 NUM_ACTIONS);
286 }
287}
288
edchinf5150c682017-09-18 02:50:03289// Returns the status bar background color.
290UIColor* StatusBarBackgroundColor() {
291 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
292}
293
294// Duration of the toolbar animation.
Kurt Horimoto62e97c72017-11-03 19:51:47295const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3;
edchinf5150c682017-09-18 02:50:03296
sdefresnee65fd872016-12-19 13:38:13297const CGFloat kVoiceSearchBarHeight = 59.0;
298
299// Dimensions to use when downsizing an image for search-by-image.
300const CGFloat kSearchByImageMaxImageArea = 90000.0;
301const CGFloat kSearchByImageMaxImageWidth = 600.0;
302const CGFloat kSearchByImageMaxImageHeight = 400.0;
303
sdefresnee65fd872016-12-19 13:38:13304enum HeaderBehaviour {
305 // The header moves completely out of the screen.
306 Hideable = 0,
307 // This header stays on screen and doesn't overlap with the content.
308 Visible,
309 // This header stay on screen and covers part of the content.
310 Overlap
311};
312
sdefresnee65fd872016-12-19 13:38:13313const CGFloat kIPadFindBarOverlap = 11;
314
315bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24316 // Most URLs are allowed in incognito; the following is an exception.
317 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13318}
319
edchineeb4d422017-10-02 17:39:36320// Snackbar category for browser view controller.
321NSString* const kBrowserViewControllerSnackbarCategory =
322 @"BrowserViewControllerSnackbarCategory";
323
rohitrao005a6432017-03-16 20:52:42324} // namespace
sdefresnee65fd872016-12-19 13:38:13325
stkhapugin952ecef2017-04-11 12:11:45326#pragma mark - HeaderDefinition helper
327
328@interface HeaderDefinition : NSObject
329
330// The header view.
331@property(nonatomic, strong) UIView* view;
332// How to place the view, and its behaviour when the headers move.
333@property(nonatomic, assign) HeaderBehaviour behaviour;
334// Reduces the height of a header to adjust for shadows.
335@property(nonatomic, assign) CGFloat heightAdjustement;
336// Nudges that particular header up by this number of points.
337@property(nonatomic, assign) CGFloat inset;
338
339- (instancetype)initWithView:(UIView*)view
340 headerBehaviour:(HeaderBehaviour)behaviour
341 heightAdjustment:(CGFloat)heightAdjustment
342 inset:(CGFloat)inset;
343
344+ (instancetype)definitionWithView:(UIView*)view
345 headerBehaviour:(HeaderBehaviour)behaviour
346 heightAdjustment:(CGFloat)heightAdjustment
347 inset:(CGFloat)inset;
348
349@end
350
351@implementation HeaderDefinition
352@synthesize view = _view;
353@synthesize behaviour = _behaviour;
354@synthesize heightAdjustement = _heightAdjustement;
355@synthesize inset = _inset;
356
357+ (instancetype)definitionWithView:(UIView*)view
358 headerBehaviour:(HeaderBehaviour)behaviour
359 heightAdjustment:(CGFloat)heightAdjustment
360 inset:(CGFloat)inset {
361 return [[self alloc] initWithView:view
362 headerBehaviour:behaviour
363 heightAdjustment:heightAdjustment
364 inset:inset];
365}
366
367- (instancetype)initWithView:(UIView*)view
368 headerBehaviour:(HeaderBehaviour)behaviour
369 heightAdjustment:(CGFloat)heightAdjustment
370 inset:(CGFloat)inset {
371 self = [super init];
372 if (self) {
373 _view = view;
374 _behaviour = behaviour;
375 _heightAdjustement = heightAdjustment;
376 _inset = inset;
377 }
378 return self;
379}
380
381@end
382
383#pragma mark - BVC
384
Rohit Rao01e0e002017-08-14 20:49:43385@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43386 AppRatingPromptDelegate,
Gauthier Ambard65e949b092017-11-29 08:46:20387 BookmarkModelBridgeObserver,
Mike Dougherty4620cf8e2017-10-31 23:37:09388 CaptivePortalDetectorTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13389 CRWNativeContentProvider,
390 CRWWebStateDelegate,
391 DialogPresenterDelegate,
Kurt Horimoto62e97c72017-11-03 19:51:47392 LegacyFullscreenControllerDelegate,
Mark Cogan80aa28d2017-11-30 13:11:34393 InfobarContainerStateDelegate,
sdefresnee65fd872016-12-19 13:38:13394 KeyCommandsPlumbing,
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57395 NetExportTabHelperDelegate,
edchincd32fdf2017-10-25 12:45:45396 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13397 MFMailComposeViewControllerDelegate,
398 NewTabPageControllerObserver,
399 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27400 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13401 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55402 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13403 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11404 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59405 RepostFormTabHelperDelegate,
Gauthier Ambard29939db12017-10-30 16:47:31406 SideSwipeControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13407 SKStoreProductViewControllerDelegate,
408 SnapshotOverlayProvider,
409 StoreKitLauncher,
edchin9e7a1112017-11-07 18:28:03410 SigninPresenter,
sdefresnee65fd872016-12-19 13:38:13411 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19412 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38413 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13414 TabModelObserver,
415 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03416 TabStripPresentation,
Peter Laurense0b80f12017-11-21 07:52:40417 ToolsMenuConfigurationProvider,
sdefresnee65fd872016-12-19 13:38:13418 UIGestureRecognizerDelegate,
419 UpgradeCenterClientProtocol,
420 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04421 VoiceSearchBarOwner,
422 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13423 // The dependency factory passed on initialization. Used to vend objects used
424 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27425 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13426
427 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27428 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13429
sczsf1620e52017-10-02 22:54:46430 // Facade objects used by |_toolbarCoordinator|.
431 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13432 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
433 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
434
sdefresnee65fd872016-12-19 13:38:13435 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27436 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13437
438 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27439 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13440
441 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27442 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13443
444 // Handles presentation of JavaScript dialogs.
445 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
446
justincohen75011c32017-04-28 16:31:39447 // Handles command dispatching.
448 CommandDispatcher* _dispatcher;
449
sdefresnee65fd872016-12-19 13:38:13450 // Keyboard commands provider. It offloads most of the keyboard commands
451 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27452 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13453
sdefresnee65fd872016-12-19 13:38:13454 // Used to inject Javascript implementing the PaymentRequest API and to
455 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27456 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13457
sdefresnee65fd872016-12-19 13:38:13458 // Used to display the Voice Search UI. Nil if not visible.
459 scoped_refptr<VoiceSearchController> _voiceSearchController;
460
gambard6299cc1d2017-02-21 13:06:03461 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27462 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03463
sdefresnee65fd872016-12-19 13:38:13464 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27465 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13466
sdefresnee65fd872016-12-19 13:38:13467 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27468 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13469
470 // Records the set of domains for which full screen alert has already been
471 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27472 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13473
474 // Adapter to let BVC be the delegate for WebState.
475 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
476
477 // YES if new tab is animating in.
478 BOOL _inNewTabAnimation;
479
480 // YES if Voice Search should be started when the new tab animation is
481 // finished.
482 BOOL _startVoiceSearchAfterNewTabAnimation;
483
484 // YES if the user interacts with the location bar.
485 BOOL _locationBarHasFocus;
486 // YES if a load was cancelled due to typing in the location bar.
487 BOOL _locationBarEditCancelledLoad;
488 // YES if waiting for a foreground tab due to expectNewForegroundTab.
489 BOOL _expectingForegroundTab;
490
Sylvain Defresne41170aa2017-06-15 10:25:20491 // Whether or not -shutdown has been called.
492 BOOL _isShutdown;
493
sdefresnee65fd872016-12-19 13:38:13494 // The ChromeBrowserState associated with this BVC.
495 ios::ChromeBrowserState* _browserState; // weak
496
497 // Whether or not Incognito* is enabled.
498 BOOL _isOffTheRecord;
499
500 // The last point within |_contentArea| that's received a touch.
501 CGPoint _lastTapPoint;
502
503 // The time at which |_lastTapPoint| was most recently set.
504 CFTimeInterval _lastTapTime;
505
506 // A single infobar container handles all infobars in all tabs. It keeps
507 // track of infobars for current tab (accessed via infobar helper of
508 // the current tab).
509 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
510
511 // Bridge class to deliver container change notifications to BVC.
512 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
513
514 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37515 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27516 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13517
518 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11519 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13520
sdefresnee65fd872016-12-19 13:38:13521 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27522 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13523
524 // Bridge to register for bookmark changes.
Gauthier Ambard65e949b092017-11-29 08:46:20525 std::unique_ptr<bookmarks::BookmarkModelBridge> _bookmarkModelBridge;
sdefresnee65fd872016-12-19 13:38:13526
527 // Cached pointer to the bookmarks model.
528 bookmarks::BookmarkModel* _bookmarkModel; // weak
529
530 // The controller that shows the bookmarking UI after the user taps the star
531 // button.
stkhapuginc9eee7b2017-04-10 15:49:27532 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13533
sdefresnee65fd872016-12-19 13:38:13534 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27535 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13536
Eugene But56efc322017-08-11 14:03:44537 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28538 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13539
540 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27541 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13542
Jean-François Geyelin3d47c212017-08-03 09:24:09543 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27544 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13545
Rohit Rao01e0e002017-08-14 20:49:43546 // Coordinator for the share menu (Activity Services).
547 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
548
sdefresnee65fd872016-12-19 13:38:13549 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27550 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38551
Rohit Raocda0a992017-08-16 15:37:11552 // Coordinator for the QR scanner.
553 QRScannerLegacyCoordinator* _qrScannerCoordinator;
554
sczsdd860eba2017-08-10 01:55:38555 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13556 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53557
558 // Coordinator for displaying Sad Tab.
559 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27560
561 // Coordinator for Page Info UI.
562 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59563
564 // Coordinator for displaying Repost Form dialog.
565 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22566
edchin7f210cd2017-09-28 08:03:53567 // Coordinator for displaying snackbars.
568 SnackbarCoordinator* _snackbarCoordinator;
569
sczsf1620e52017-10-02 22:54:46570 // Coordinator for the toolbar.
571 LegacyToolbarCoordinator* _toolbarCoordinator;
572
Kurt Horimotoea429dd2017-11-28 02:24:30573 // The toolbar UI updater for the toolbar managed by |_toolbarCoordinator|.
574 LegacyToolbarUIUpdater* _toolbarUIUpdater;
575
Louis Romerod11747a2017-10-20 20:10:35576 // Coordinator for the External Search UI.
577 ExternalSearchCoordinator* _externalSearchCoordinator;
578
Mark Coganca30df62017-11-20 14:29:11579 // Coordinator for the language selection UI.
580 LanguageSelectionCoordinator* _languageSelectionCoordinator;
581
Justin Cohenb3170c32017-09-19 01:55:22582 // Fake status bar view used to blend the toolbar into the status bar.
583 UIView* _fakeStatusBarView;
Sylvain Defresnef5d2d952017-11-14 11:15:31584
585 // Stores whether the Tab currently inserted was a pre-rendered Tab. This
586 // is used to determine whether the pre-rendering animation should be played
587 // or not.
588 BOOL _insertedTabWasPrerenderedTab;
sdefresnee65fd872016-12-19 13:38:13589}
590
591// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17592@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13593// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17594@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13595// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17596@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13597// Whether the current tab can enable the request desktop menu item.
598@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
599// Whether the sharing menu should be enabled.
600@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
601// Helper method to check web controller canShowFindBar method.
602@property(nonatomic, assign, readonly) BOOL canShowFindBar;
603// Whether the controller's view is currently available.
604// YES from viewWillAppear to viewWillDisappear.
605@property(nonatomic, assign, getter=isVisible) BOOL visible;
606// Whether the controller's view is currently visible.
607// YES from viewDidAppear to viewWillDisappear.
608@property(nonatomic, assign) BOOL viewVisible;
609// Whether the controller is currently dismissing a presented view controller.
610@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
611// Returns YES if the toolbar has not been scrolled out by fullscreen.
612@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
613 BOOL toolbarOnScreen;
614// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12615@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13616// Whether BVC prefers to hide the status bar. This value is used to determine
617// the response from the |prefersStatusBarHidden| method.
618@property(nonatomic, assign) BOOL hideStatusBar;
619// Whether the VoiceSearchBar should be displayed.
620@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
621// Coordinator for displaying a modal overlay with activity indicator to prevent
622// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17623@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13624 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39625// A block to be run when the |tabWasAdded:| method completes the animation
626// for the presentation of a new tab. Can be used to record performance metrics.
627@property(nonatomic, strong, nullable)
628 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42629// Coordinator for Recent Tabs.
630@property(nonatomic, strong)
631 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03632// Coordinator for tablet tab strip.
633@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
634// A weak reference to the view of the tab strip on tablet.
635@property(nonatomic, weak) UIView* tabStripView;
sdefresnee65fd872016-12-19 13:38:13636
liaoyukeea9f3ee62017-03-07 22:05:39637// The user agent type used to load the currently visible page. User agent type
638// is NONE if there is no visible page or visible page is a native page.
639@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
640
stkhapugin952ecef2017-04-11 12:11:45641// Returns the header views, all the chrome on top of the page, including the
642// ones that cannot be scrolled off screen by full screen.
643@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
644
Cooper Knaakd0a974cd2017-08-10 18:05:47645// Used to display the new tab tip in-product help promotion bubble. |nil| if
646// the new tab tip bubble has not yet been presented. Once the bubble is
647// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38648@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47649 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38650
Helen Yang9175bd52017-08-12 00:28:40651// Used to display the new incognito tab tip in-product help promotion bubble.
652@property(nonatomic, strong)
653 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
654
Justin Cohen9fe9ef672017-12-01 20:37:43655// Vertical offset for fullscreen toolbar.
656@property(nonatomic, strong) NSLayoutConstraint* toolbarOffsetConstraint;
657
sdefresnee65fd872016-12-19 13:38:13658// BVC initialization:
659// If the BVC is initialized with a valid browser state & tab model immediately,
660// the path is straightforward: functionality is enabled, and the UI is built
661// when -viewDidLoad is called.
662// If the BVC is initialized without a browser state or tab model, the tab model
663// and browser state may or may not be provided before -viewDidLoad is called.
664// In most cases, they will not, to improve startup performance.
665// In order to handle this, initialization of various aspects of BVC have been
666// broken out into the following functions, which have expectations (enforced
667// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
668
669// Registers for notifications.
670- (void)registerForNotifications;
671// Called when a tab is starting to load. If it's a link click or form
672// submission, the user is navigating away from any entries in the forward
673// history. Tell the toolbar so it can update the UI appropriately.
674// See the warning on [Tab webWillStartLoadingURL] about invocation of this
675// method sequence by malicious pages.
676- (void)pageLoadStarting:(NSNotification*)notify;
677// Called when a tab actually starts loading.
678- (void)pageLoadStarted:(NSNotification*)notify;
679// Called when a tab finishes loading. Update the Omnibox with the url and
680// stop any page load progess display.
681- (void)pageLoadComplete:(NSNotification*)notify;
682// Called when a tab is deselected in the model.
683// This notification also occurs when a tab is closed.
684- (void)tabDeselected:(NSNotification*)notify;
685// Animates sliding current tab and rotate-entering new tab while new tab loads
686// in background on the iPhone only.
687- (void)tabWasAdded:(NSNotification*)notify;
688
689// Updates non-view-related functionality with the given browser state and tab
690// model.
691// Does not matter whether or not the view has been loaded.
692- (void)updateWithTabModel:(TabModel*)model
693 browserState:(ios::ChromeBrowserState*)browserState;
694// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
695// the status bar to mimic this layout.
696- (void)installFakeStatusBar;
697// Builds the UI parts of tab strip and the toolbar. Does not matter whether
698// or not browser state and tab model are valid.
699- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50700// Sets up the constraints on the toolbar.
701- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13702// Updates view-related functionality with the given tab model and browser
703// state. The view must have been loaded. Uses |_browserState| and |_model|.
704- (void)addUIFunctionalityForModelAndBrowserState;
Justin Cohen4eeada32017-11-13 18:21:28705// Sets the correct frame and hierarchy for subviews and helper views. Only
706// insert views on |initialLayout|.
707- (void)setUpViewLayout:(BOOL)initialLayout;
sdefresnee65fd872016-12-19 13:38:13708// Makes |tab| the currently visible tab, displaying its view. Calls
709// -selectedTabChanged on the toolbar only if |newSelection| is YES.
710- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
711// Initializes the bookmark interaction controller if not already initialized.
712- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13713// Add all delegates to the provided |tab|.
714- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14715// Remove delegates from the provided |tab|.
716- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13717// Closes the current tab, with animation if applicable.
718- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13719// Show the bookmarks page.
720- (void)showAllBookmarks;
721// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23722- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13723// Dismisses the "rate this app" dialog.
724- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32725// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13726- (BOOL)isTabNativePage:(Tab*)tab;
727// Returns the view to use when animating a page in or out, positioning it to
728// fill the content area but not actually adding it to the view hierarchy.
729- (UIImageView*)pageOpenCloseAnimationView;
730// Returns the view to use when animating full screen NTP paper in, filling the
731// entire screen but not actually adding it to the view hierarchy.
732- (UIImageView*)pageFullScreenOpenCloseAnimationView;
733// Updates the toolbar display based on the current tab.
734- (void)updateToolbar;
Kurt Horimotoea429dd2017-11-28 02:24:30735// Starts or stops broadcasting the toolbar UI depending on whether the BVC is
736// visible and active.
737- (void)updateToolbarBroadcastState;
sdefresnee65fd872016-12-19 13:38:13738// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12739// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13740- (void)updateDialogPresenterActiveState;
741// Dismisses popups and modal dialogs that are displayed above the BVC upon size
742// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
743// is performed.
744// TODO(crbug.com/522721): Support size changes for all popups and modal
745// dialogs.
746- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47747
748// Returns a bubble associated with an in-product help promotion if
749// it is valid to show the promotion and |nil| otherwise. |feature| is the
750// base::Feature object associated with the given promotion. |direction| is the
751// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21752// arrow on the button. |text| is the text displayed by the bubble. This method
753// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47754- (BubbleViewControllerPresenter*)
755bubblePresenterForFeature:(const base::Feature&)feature
756 direction:(BubbleArrowDirection)direction
757 alignment:(BubbleAlignment)alignment
758 text:(NSString*)text;
759
Cooper Knaak120cee5e2017-08-10 20:57:00760// Waits to present a bubble associated with the new tab tip in-product help
761// promotion until the feature engagement tracker database is fully initialized.
762// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
763// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11764// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21765// determines it is not valid to present it. This method requires that
766// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00767- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11768// Optionally presents a bubble associated with the new tab tip in-product help
769// promotion. If the feature engagement tracker determines it is valid to show
770// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
771// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21772// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
773// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00774- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40775// Waits to present a bubble associated with the new incognito tab tip
776// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21777// fully initialized. This method requires that |self.browserState| is
778// not NULL.
Helen Yang9175bd52017-08-12 00:28:40779- (void)presentNewIncognitoTabTipBubbleOnInitialized;
780// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21781// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40782- (void)presentNewIncognitoTabTipBubble;
Gregory Chatzinoff541b8642017-10-25 00:25:21783// Presents the New Tab Tip or New Incognito Tab Tip Bubble if one is
784// eligible. Only one can be eligible per session (as enforced by the
785// FeatureEngagementTracker). If neither is eligible, neither bubble is
786// presented. This method requires that |self.browserState| is not NULL.
787- (void)presentBubblesIfEligible;
Cooper Knaak120cee5e2017-08-10 20:57:00788
sdefresnee65fd872016-12-19 13:38:13789// Update find bar with model data. If |shouldFocus| is set to YES, the text
790// field will become first responder.
791- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13792// Hide find bar.
793- (void)hideFindBarWithAnimation:(BOOL)animate;
794// Shows find bar. If |selectText| is YES, all text inside the Find Bar
795// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
796// set to be first responder.
797- (void)showFindBarWithAnimation:(BOOL)animate
798 selectText:(BOOL)selectText
799 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36800
sdefresnee65fd872016-12-19 13:38:13801// Adds a CardView on top of the contentArea either taking the size of the full
802// screen or just the size of the space under the header.
803// Returns the CardView that was added.
804- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
805// Called when either a tab finishes loading or when a tab with finished content
806// is added directly to the model via pre-rendering. The tab must be non-nil and
807// must be a member of the tab model controlled by this BrowserViewController.
808- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
809// Evaluates Javascript asynchronously using the current page context.
810- (void)openJavascript:(NSString*)javascript;
edchineeb4d422017-10-02 17:39:36811// Shows a self-dismissing snackbar displaying |message|.
812- (void)showSnackbar:(NSString*)message;
sdefresnee65fd872016-12-19 13:38:13813// Induces an intentional crash in the browser process.
814- (void)induceBrowserCrash;
815// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17816- (void)managePermissionAndSaveImage:(NSData*)data
817 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13818// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24819// saved as a temporary file on disk then saved in photos. Saving will happen
820// on a background sequence and the completion block will be invoked on that
821// sequence.
822- (void)saveImage:(NSData*)data
823 withFileExtension:(NSString*)fileExtension
824 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13825// Called when Chrome has been denied access to the photos or videos and the
826// user can change it.
827// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
828// settings. Dismiss previous alert if it has not been dismissed yet.
829- (void)displayImageErrorAlertWithSettingsOnMainQueue;
830// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
831// previous alert if it has not been dismissed yet.
832- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
833// Called when Chrome has been denied access to the photos or videos and the
834// user cannot change it.
835// Shows a privacy alert on the main queue, with errorContent as the message.
836// Dismisses previous alert if it has not been dismissed yet.
837- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
838// Called with the results of saving a picture in the photo album. If error is
839// nil the save succeeded.
840- (void)finishSavingImageWithError:(NSError*)error;
841// Provides a view that encompasses currently displayed infobar(s) or nil
842// if no infobar is presented.
843- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
844// Returns a vertical infobar offset relative to the tab content.
845- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
846// Provides a view that encompasses the voice search bar if it's displayed or
847// nil if the voice search bar isn't displayed.
848- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
849// Returns a vertical voice search bar offset relative to the tab content.
850- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
851// Lazily instantiates |_voiceSearchController|.
852- (void)ensureVoiceSearchControllerCreated;
853// Lazily instantiates |_voiceSearchBar| and adds it to the view.
854- (void)ensureVoiceSearchBarCreated;
855// Shows/hides the voice search bar.
856- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
857// The LogoAnimationControllerOwner to be used for the next logo transition
858// animation.
859- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13860// Returns the footer view if one exists (e.g. the voice search bar).
861- (UIView*)footerView;
862// Returns the height of the header view for the tab model's current tab.
863- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13864// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45865- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13866 atOffset:(CGFloat)headerOffset;
867// Returns the y coordinate for the footer's frame when animating the footer
868// in/out of fullscreen.
869- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
870// Called when the animation for setting the header view's offset is finished.
871// |completed| should indicate if the animation finished completely or was
872// interrupted. |offset| should indicate the header offset after the animation.
873// |dragged| should indicate if the header moved due to the user dragging.
Kurt Horimoto62e97c72017-11-03 19:51:47874- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:13875 headerAnimationCompleted:(BOOL)completed
876 offset:(CGFloat)offset;
877// Performs a search with the image at the given url. The referrer is used to
878// download the image.
879- (void)searchByImageAtURL:(const GURL&)url
880 referrer:(const web::Referrer)referrer;
881// Saves the image at the given URL on the system's album. The referrer is used
882// to download the image.
883- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
884
Mark Cogandfcdea72017-07-18 13:47:38885// Record the last tap point based on the |originPoint| (if any) passed in
886// |command|.
887- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13888// Get return the last stored |_lastTapPoint| if it's been set within the past
889// second.
890- (CGPoint)lastTapPoint;
891// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
892- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
893// Returns the native controller being used by |tab|'s web controller.
894- (id)nativeControllerForTab:(Tab*)tab;
895// Installs the BVC as overscroll actions controller of |nativeContent| if
896// needed. Sets the style of the overscroll actions toolbar.
897- (void)setOverScrollActionControllerToStaticNativeContent:
898 (StaticHtmlNativeContent*)nativeContent;
899// Whether the BVC should declare keyboard commands.
900- (BOOL)shouldRegisterKeyboardCommands;
901// Adds the given url to the reading list.
902- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
903@end
904
sdefresnee65fd872016-12-19 13:38:13905@implementation BrowserViewController
906
907@synthesize contentArea = _contentArea;
908@synthesize typingShield = _typingShield;
909@synthesize active = _active;
910@synthesize visible = _visible;
911@synthesize viewVisible = _viewVisible;
912@synthesize dismissingModal = _dismissingModal;
913@synthesize hideStatusBar = _hideStatusBar;
914@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
915@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39916@synthesize foregroundTabWasAddedCompletionBlock =
917 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:40918@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
919@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:42920@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03921@synthesize tabStripCoordinator = _tabStripCoordinator;
922@synthesize tabStripView = _tabStripView;
Justin Cohen9fe9ef672017-12-01 20:37:43923@synthesize toolbarOffsetConstraint = _toolbarOffsetConstraint;
sdefresnee65fd872016-12-19 13:38:13924
925#pragma mark - Object lifecycle
926
Mark Cogan5e3da152017-07-11 15:57:30927- (instancetype)
928 initWithTabModel:(TabModel*)model
929 browserState:(ios::ChromeBrowserState*)browserState
930 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
931applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:13932 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
933 if (self) {
934 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:17935
stkhapuginc9eee7b2017-04-10 15:49:27936 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:27937 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
938 presentingViewController:self];
justincohen75011c32017-04-28 16:31:39939 _dispatcher = [[CommandDispatcher alloc] init];
940 [_dispatcher startDispatchingToTarget:self
941 forProtocol:@protocol(UrlLoader)];
942 [_dispatcher startDispatchingToTarget:self
943 forProtocol:@protocol(WebToolbarDelegate)];
944 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:24945 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:30946 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
947 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:32948 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
949 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
950 // dispatched to the endpoint as well. Since this is potentially
951 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
952 DCHECK(!applicationCommandEndpoint ||
953 [applicationCommandEndpoint
954 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
955 [_dispatcher
956 startDispatchingToTarget:applicationCommandEndpoint
957 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:39958
edchin7f210cd2017-09-28 08:03:53959 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
960 _snackbarCoordinator.dispatcher = _dispatcher;
961 [_snackbarCoordinator start];
962
Mark Coganca30df62017-11-20 14:29:11963 _languageSelectionCoordinator =
964 [[LanguageSelectionCoordinator alloc] initWithBaseViewController:self];
965 _languageSelectionCoordinator.presenter =
966 [[VerticalAnimationContainer alloc] init];
967
sdefresnee65fd872016-12-19 13:38:13968 _javaScriptDialogPresenter.reset(
969 new JavaScriptDialogPresenterImpl(_dialogPresenter));
970 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
971 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:15972 [[UpgradeCenter sharedInstance] registerClient:self
973 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:13974 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:13975 if (model && browserState)
976 [self updateWithTabModel:model browserState:browserState];
977 if ([[NSUserDefaults standardUserDefaults]
978 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:27979 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:13980 }
981 }
982 return self;
983}
984
985- (instancetype)initWithNibName:(NSString*)nibNameOrNil
986 bundle:(NSBundle*)nibBundleOrNil {
987 NOTREACHED();
988 return nil;
989}
990
991- (instancetype)initWithCoder:(NSCoder*)aDecoder {
992 NOTREACHED();
993 return nil;
994}
995
996- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:20997 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:13998}
999
1000#pragma mark - Accessibility
1001
1002- (BOOL)accessibilityPerformEscape {
1003 [self dismissPopups];
1004 return YES;
1005}
1006
1007#pragma mark - Properties
1008
edchin3365c7d2017-09-01 22:20:371009- (id<ApplicationCommands,
1010 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371011 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531012 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371013 UrlLoader,
1014 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561015 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531016 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1017 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241018}
1019
sdefresnee65fd872016-12-19 13:38:131020- (void)setActive:(BOOL)active {
1021 if (_active == active) {
1022 return;
1023 }
1024 _active = active;
1025
1026 // If not active, display an activity indicator overlay over the view to
1027 // prevent interaction with the web page.
1028 // TODO(crbug.com/637093): This coordinator should be managed by the
1029 // coordinator used to present BrowserViewController, when implemented.
1030 if (active) {
1031 [self.activityOverlayCoordinator stop];
1032 self.activityOverlayCoordinator = nil;
1033 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171034 self.activityOverlayCoordinator =
1035 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131036 [self.activityOverlayCoordinator start];
1037 }
1038
1039 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091040 ActiveStateManager* active_state_manager =
1041 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131042 active_state_manager->SetActive(active);
1043 }
1044
1045 [_model setWebUsageEnabled:active];
1046 [self updateDialogPresenterActiveState];
Kurt Horimotoea429dd2017-11-28 02:24:301047 [self updateToolbarBroadcastState];
sdefresnee65fd872016-12-19 13:38:131048
1049 if (active) {
1050 // Make sure the tab (if any; it's possible to get here without a current
1051 // tab if the caller is about to create one) ends up on screen completely.
1052 Tab* currentTab = [_model currentTab];
1053 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441054 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131055 if (_expectingForegroundTab)
1056 [currentTab.webController setOverlayPreviewMode:YES];
1057 if (currentTab)
1058 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341059 } else {
1060 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131061 }
sdefresnee65fd872016-12-19 13:38:131062 [_paymentRequestManager enablePaymentRequest:active];
1063
1064 [self setNeedsStatusBarAppearanceUpdate];
1065}
1066
1067- (void)setPrimary:(BOOL)primary {
1068 [_model setPrimary:primary];
1069 if (primary) {
1070 [self updateDialogPresenterActiveState];
Kurt Horimotoea429dd2017-11-28 02:24:301071 [self updateToolbarBroadcastState];
sdefresnee65fd872016-12-19 13:38:131072 } else {
1073 self.dialogPresenter.active = false;
1074 }
1075}
1076
1077- (BOOL)isPlayingTTS {
1078 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1079}
1080
sdefresne6165c8742017-01-16 15:42:021081- (ios::ChromeBrowserState*)browserState {
1082 return _browserState;
1083}
1084
1085- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271086 return _model;
sdefresne6165c8742017-01-16 15:42:021087}
1088
sdefresnee65fd872016-12-19 13:38:131089- (SideSwipeController*)sideSwipeController {
1090 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271091 _sideSwipeController =
1092 [[SideSwipeController alloc] initWithTabModel:_model
1093 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131094 [_sideSwipeController setSnapshotDelegate:self];
Gauthier Ambard29939db12017-10-30 16:47:311095 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sdefresnee65fd872016-12-19 13:38:131096 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031097 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131098 }
1099 return _sideSwipeController;
1100}
1101
sdefresnee65fd872016-12-19 13:38:131102- (DialogPresenter*)dialogPresenter {
1103 return _dialogPresenter;
1104}
1105
sdefresnee65fd872016-12-19 13:38:131106- (BOOL)canUseDesktopUserAgent {
1107 Tab* tab = [_model currentTab];
1108 if ([self isTabNativePage:tab])
1109 return NO;
1110
1111 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441112 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131113}
1114
1115// Whether the sharing menu should be shown.
1116- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191117 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251118 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131119}
1120
1121- (BOOL)canShowFindBar {
1122 // Make sure web controller can handle find in page.
1123 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421124 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131125 return NO;
rohitrao005a6432017-03-16 20:52:421126 }
sdefresnee65fd872016-12-19 13:38:131127
rohitrao005a6432017-03-16 20:52:421128 auto* helper = FindTabHelper::FromWebState(tab.webState);
1129 return (helper && helper->CurrentPageSupportsFindInPage() &&
1130 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131131}
1132
liaoyukeea9f3ee62017-03-07 22:05:391133- (web::UserAgentType)userAgentType {
1134 web::WebState* webState = [_model currentTab].webState;
1135 if (!webState)
1136 return web::UserAgentType::NONE;
1137 web::NavigationItem* visibleItem =
1138 webState->GetNavigationManager()->GetVisibleItem();
1139 if (!visibleItem)
1140 return web::UserAgentType::NONE;
1141
1142 return visibleItem->GetUserAgentType();
1143}
1144
sdefresnee65fd872016-12-19 13:38:131145- (void)setVisible:(BOOL)visible {
1146 if (_visible == visible)
1147 return;
Peter Laurense0b80f12017-11-21 07:52:401148
sdefresnee65fd872016-12-19 13:38:131149 _visible = visible;
1150}
1151
1152- (void)setViewVisible:(BOOL)viewVisible {
1153 if (_viewVisible == viewVisible)
1154 return;
1155 _viewVisible = viewVisible;
1156 self.visible = viewVisible;
1157 [self updateDialogPresenterActiveState];
Kurt Horimotoea429dd2017-11-28 02:24:301158 [self updateToolbarBroadcastState];
sdefresnee65fd872016-12-19 13:38:131159}
1160
1161- (BOOL)isToolbarOnScreen {
1162 return [self headerHeight] - [self currentHeaderOffset] > 0;
1163}
1164
kkhorimotoa44349c12017-04-12 23:02:121165- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1166 if (_inNewTabAnimation == inNewTabAnimation)
1167 return;
1168 _inNewTabAnimation = inNewTabAnimation;
1169 [self updateDialogPresenterActiveState];
Kurt Horimotoea429dd2017-11-28 02:24:301170 [self updateToolbarBroadcastState];
kkhorimotoa44349c12017-04-12 23:02:121171}
1172
sdefresnee65fd872016-12-19 13:38:131173- (BOOL)isInNewTabAnimation {
1174 return _inNewTabAnimation;
1175}
1176
1177- (BOOL)shouldShowVoiceSearchBar {
1178 // On iPads, the voice search bar should only be shown for regular horizontal
1179 // size class configurations. It should always be shown for voice search
1180 // results Tabs on iPhones, including configurations with regular horizontal
1181 // size classes (i.e. landscape iPhone 6 Plus).
1182 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1183 UIUserInterfaceSizeClassCompact;
1184 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1185 (!IsIPadIdiom() || compactWidth);
1186}
1187
1188- (void)setHideStatusBar:(BOOL)hideStatusBar {
1189 if (_hideStatusBar == hideStatusBar)
1190 return;
1191 _hideStatusBar = hideStatusBar;
1192 [self setNeedsStatusBarAppearanceUpdate];
1193}
1194
1195#pragma mark - IBActions
1196
1197- (void)shieldWasTapped:(id)sender {
sczsf1620e52017-10-02 22:54:461198 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131199}
1200
Cooper Knaakd0a974cd2017-08-10 18:05:471201- (void)userEnteredTabSwitcher {
1202 if ([self.tabTipBubblePresenter isUserEngaged]) {
1203 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1204 }
1205}
1206
Cooper Knaake963d6702017-08-11 21:03:111207- (void)presentBubblesIfEligible {
1208 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401209 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111210}
1211
sdefresnee65fd872016-12-19 13:38:131212#pragma mark - UIViewController methods
1213
1214// Perform additional set up after loading the view, typically from a nib.
1215- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091216 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281217 initialViewsRect.origin.y += StatusBarHeight();
1218 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131219 UIViewAutoresizing initialViewAutoresizing =
1220 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1221
stkhapuginf58b10d02017-04-10 13:36:171222 self.contentArea =
1223 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131224 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171225 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131226 self.typingShield.autoresizingMask = initialViewAutoresizing;
Justin Cohen41b1f382017-12-04 15:22:081227 self.typingShield.accessibilityIdentifier = @"Typing Shield";
1228 self.typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
1229
sdefresnee65fd872016-12-19 13:38:131230 [self.typingShield addTarget:self
1231 action:@selector(shieldWasTapped:)
1232 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131233 self.view.autoresizingMask = initialViewAutoresizing;
1234 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1235 [self.view addSubview:self.contentArea];
1236 [self.view addSubview:self.typingShield];
1237 [super viewDidLoad];
1238
1239 // Install fake status bar for iPad iOS7
1240 [self installFakeStatusBar];
1241 [self buildToolbarAndTabStrip];
Justin Cohen4eeada32017-11-13 18:21:281242 [self setUpViewLayout:YES];
Justin Cohenba27610e2017-11-08 19:34:451243 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:501244 [self addConstraintsToToolbar];
1245 }
sdefresnee65fd872016-12-19 13:38:131246 // If the tab model and browser state are valid, finish initialization.
1247 if (_model && _browserState)
1248 [self addUIFunctionalityForModelAndBrowserState];
1249
1250 // Add a tap gesture recognizer to save the last tap location for the source
1251 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271252 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1253 initWithTarget:self
1254 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131255 [tapRecognizer setDelegate:self];
1256 [tapRecognizer setCancelsTouchesInView:NO];
1257 [_contentArea addGestureRecognizer:tapRecognizer];
1258}
1259
Justin Cohenb3170c32017-09-19 01:55:221260- (void)viewSafeAreaInsetsDidChange {
1261 [super viewSafeAreaInsetsDidChange];
1262 // Gate this behind iPhone X, since it's currently the only device that
1263 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501264 if (IsIPhoneX()) {
Justin Cohen4eeada32017-11-13 18:21:281265 [self setUpViewLayout:NO];
Jean-François Geyelined4cde72017-10-11 11:34:501266 }
Justin Cohenba27610e2017-11-08 19:34:451267 if (IsSafeAreaCompatibleToolbarEnabled()) {
Gauthier Ambard100670f72017-10-27 09:54:271268 // TODO(crbug.com/778236): Check if this call can be removed once the
1269 // Toolbar is a contained ViewController.
sczs42f7f7482017-11-08 01:13:271270 [_toolbarCoordinator.toolbarViewController viewSafeAreaInsetsDidChange];
Gauthier Ambard100670f72017-10-27 09:54:271271 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501272 }
Justin Cohenb3170c32017-09-19 01:55:221273}
1274
sdefresnee65fd872016-12-19 13:38:131275- (void)viewDidAppear:(BOOL)animated {
1276 [super viewDidAppear:animated];
1277 self.viewVisible = YES;
1278 [self updateDialogPresenterActiveState];
Kurt Horimotoea429dd2017-11-28 02:24:301279 [self updateToolbarBroadcastState];
Gregory Chatzinoff541b8642017-10-25 00:25:211280
1281 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1282 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1283 // check for |self.browserState| before calling the presenting the bubbles.
1284 if (self.browserState) {
1285 [self presentBubblesIfEligible];
1286 }
sdefresnee65fd872016-12-19 13:38:131287}
1288
1289- (void)viewWillAppear:(BOOL)animated {
1290 [super viewWillAppear:animated];
1291
sdefresnee65fd872016-12-19 13:38:131292 self.visible = YES;
1293
1294 // Restore hidden infobars.
Rohit Rao755c37b2017-11-10 14:05:521295 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131296 _infoBarContainer->RestoreInfobars();
1297 }
1298
1299 // If the controller is suspended, or has been paged out due to low memory,
1300 // updating the view will be handled when it's displayed again.
1301 if (![_model webUsageEnabled] || !self.contentArea)
1302 return;
1303 // Update the displayed tab (if any; the switcher may not have created one
1304 // yet) in case it changed while showing the switcher.
1305 Tab* currentTab = [_model currentTab];
1306 if (currentTab)
1307 [self displayTab:currentTab isNewSelection:YES];
1308}
1309
1310- (void)viewWillDisappear:(BOOL)animated {
1311 self.viewVisible = NO;
1312 [self updateDialogPresenterActiveState];
Kurt Horimotoea429dd2017-11-28 02:24:301313 [self updateToolbarBroadcastState];
sdefresnee65fd872016-12-19 13:38:131314 [[_model currentTab] wasHidden];
1315 [_bookmarkInteractionController dismissSnackbar];
Rohit Rao755c37b2017-11-10 14:05:521316 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131317 _infoBarContainer->SuspendInfobars();
1318 }
1319 [super viewWillDisappear:animated];
1320}
1321
1322- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1323 duration:(NSTimeInterval)duration {
1324 [super willRotateToInterfaceOrientation:orient duration:duration];
1325 [self dismissPopups];
1326 [self reshowFindBarIfNeededWithCoordinator:nil];
1327}
1328
1329- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1330 [super didRotateFromInterfaceOrientation:orient];
1331
1332 // This reinitializes the toolbar, including updating the Overlay View,
1333 // if there is one.
1334 [self updateToolbar];
Mark Cogan80aa28d2017-11-30 13:11:341335 [self infoBarContainerStateDidChangeAnimated:NO];
sdefresnee65fd872016-12-19 13:38:131336}
1337
1338- (BOOL)prefersStatusBarHidden {
1339 return self.hideStatusBar;
1340}
1341
1342// Called when in the foreground and the OS needs more memory. Release as much
1343// as possible.
1344- (void)didReceiveMemoryWarning {
1345 // Releases the view if it doesn't have a superview.
1346 [super didReceiveMemoryWarning];
1347
1348 // Release any cached data, images, etc that aren't in use.
1349 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1350 // but since the FaviconCache uses obj-c in the header, it can't be included
1351 // there.
1352 if (_browserState) {
1353 FaviconLoader* loader =
1354 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1355 _browserState);
1356 if (loader)
1357 loader->PurgeCache();
1358 }
1359
1360 if (![self isViewLoaded]) {
1361 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1362 // as the BrowserViewController.
1363 self.contentArea = nil;
1364 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271365 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131366 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271367 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421368 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461369 _toolbarCoordinator = nil;
Kurt Horimotoea429dd2017-11-28 02:24:301370 [_toolbarUIUpdater stopUpdating];
1371 _toolbarUIUpdater = nil;
stkhapuginc9eee7b2017-04-10 15:49:271372 _toolbarModelDelegate = nil;
1373 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031374 [self.tabStripCoordinator stop];
1375 self.tabStripCoordinator = nil;
1376 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271377 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131378 }
1379}
1380
1381- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1382 [super traitCollectionDidChange:previousTraitCollection];
1383 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1384 // because in some cases the presented view controller isn't a child of the
1385 // BVC in the view controller hierarchy (some intervening object isn't a
1386 // view controller).
1387 [self.presentedViewController
1388 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131389 // Update voice search bar visibility.
1390 [self updateVoiceSearchBarVisibilityAnimated:NO];
1391}
1392
1393- (void)viewWillTransitionToSize:(CGSize)size
1394 withTransitionCoordinator:
1395 (id<UIViewControllerTransitionCoordinator>)coordinator {
1396 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1397 [self dismissPopups];
1398 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1399}
1400
1401- (void)reshowFindBarIfNeededWithCoordinator:
1402 (id<UIViewControllerTransitionCoordinator>)coordinator {
1403 if (![_findBarController isFindInPageShown])
1404 return;
1405
1406 // Record focused state.
1407 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1408
1409 [self hideFindBarWithAnimation:NO];
1410
stkhapuginc9eee7b2017-04-10 15:49:271411 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131412 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1413 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271414 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131415 if (strongSelf)
1416 [strongSelf showFindBarWithAnimation:NO
1417 selectText:NO
1418 shouldFocus:isFocusedBeforeReshow];
1419 };
1420
1421 BOOL enqueued =
1422 [coordinator animateAlongsideTransition:nil completion:completion];
1423 if (!enqueued) {
1424 completion(nil);
1425 }
1426}
1427
1428- (void)dismissViewControllerAnimated:(BOOL)flag
1429 completion:(void (^)())completion {
Rohit Rao685807a52017-11-10 20:50:111430 // It is an error to call this method when no VC is being presented.
1431 DCHECK(!TabSwitcherPresentsBVCEnabled() || self.presentedViewController);
1432
Rohit Raoa668c022017-11-08 00:04:441433 // Some calling code invokes |dismissViewControllerAnimated:completion:|
1434 // multiple times. When the BVC is displayed using VC containment, multiple
1435 // calls are effectively idempotent because only the first call has any effect
1436 // and subsequent calls do nothing. However, when the BVC is presented,
1437 // subsequent calls end up dismissing the BVC itself. This is never what we
Rohit Rao685807a52017-11-10 20:50:111438 // want, so check for this case and return early. It is not enough to check
1439 // |self.dismissingModal| because some dismissals do not go through
1440 // -[BrowserViewController dismissViewControllerAnimated:completion:|.
Rohit Raoa668c022017-11-08 00:04:441441 // TODO(crbug.com/782338): Fix callers and remove this early return.
Rohit Rao685807a52017-11-10 20:50:111442 if (TabSwitcherPresentsBVCEnabled() &&
1443 (self.dismissingModal || self.presentedViewController.isBeingDismissed)) {
Rohit Raoa668c022017-11-08 00:04:441444 return;
1445 }
1446
sdefresnee65fd872016-12-19 13:38:131447 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271448 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131449 [super dismissViewControllerAnimated:flag
1450 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271451 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131452 [strongSelf setDismissingModal:NO];
1453 [strongSelf setPresenting:NO];
1454 if (completion)
1455 completion();
1456 [[strongSelf dialogPresenter] tryToPresent];
1457 }];
1458}
1459
1460- (void)presentViewController:(UIViewController*)viewControllerToPresent
1461 animated:(BOOL)flag
1462 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271463 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131464 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1465 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1466 // presented, to show a temporary view of the launch screen and then remove it
1467 // when the controller for the FRE has been presented. This fix should be
1468 // removed when the FRE startup code is rewritten.
1469 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1470 experimental_flags::AlwaysDisplayFirstRun()) &&
1471 !tests_hook::DisableFirstRun();
1472 // These if statements check that |presentViewController| is being called for
1473 // the FRE case.
1474 if (firstRunLaunch &&
1475 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1476 UINavigationController* navController =
1477 base::mac::ObjCCastStrict<UINavigationController>(
1478 viewControllerToPresent);
1479 if ([navController.topViewController
1480 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1481 self.hideStatusBar = YES;
1482
1483 // Load view from Launch Screen and add it to window.
1484 NSBundle* mainBundle = base::mac::FrameworkBundle();
1485 NSArray* topObjects =
1486 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1487 UIViewController* launchScreenController =
1488 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1489 // |launchScreenView| is loaded as an autoreleased object, and is retained
1490 // by the |completion| block below.
1491 UIView* launchScreenView = launchScreenController.view;
1492 launchScreenView.userInteractionEnabled = NO;
1493 launchScreenView.frame = self.view.window.bounds;
1494 [self.view.window addSubview:launchScreenView];
1495
1496 // Replace the completion handler sent to the superclass with one which
1497 // removes |launchScreenView| and resets the status bar. If |completion|
1498 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271499 __weak BrowserViewController* weakSelf = self;
1500 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131501 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271502 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131503 if (completion)
1504 completion();
stkhapuginc9eee7b2017-04-10 15:49:271505 };
sdefresnee65fd872016-12-19 13:38:131506 }
1507 }
1508
1509 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171510 if ([_sideSwipeController inSwipe]) {
1511 [_sideSwipeController resetContentView];
1512 }
sdefresnee65fd872016-12-19 13:38:131513
1514 [super presentViewController:viewControllerToPresent
1515 animated:flag
1516 completion:finalCompletionHandler];
1517}
1518
Mark Cogan80aa28d2017-11-30 13:11:341519- (BOOL)shouldAutorotate {
1520 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
1521 // Don't rotate if a voice search is being presented or dismissed. Once the
1522 // transition animations finish, only the Voice Search UIViewController's
1523 // |-shouldAutorotate| will be called.
1524 return NO;
1525 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
1526 // Don't auto rotate if side swipe controller view says not to.
1527 return NO;
1528 } else {
1529 return [super shouldAutorotate];
1530 }
1531}
1532
sdefresnee65fd872016-12-19 13:38:131533#pragma mark - Notification handling
1534
1535- (void)registerForNotifications {
1536 DCHECK(_model);
1537 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1538 [defaultCenter addObserver:self
1539 selector:@selector(pageLoadStarting:)
1540 name:kTabModelTabWillStartLoadingNotification
1541 object:_model];
1542 [defaultCenter addObserver:self
1543 selector:@selector(pageLoadStarted:)
1544 name:kTabModelTabDidStartLoadingNotification
1545 object:_model];
1546 [defaultCenter addObserver:self
1547 selector:@selector(pageLoadComplete:)
1548 name:kTabModelTabDidFinishLoadingNotification
1549 object:_model];
1550 [defaultCenter addObserver:self
1551 selector:@selector(tabDeselected:)
1552 name:kTabModelTabDeselectedNotification
1553 object:_model];
1554 [defaultCenter addObserver:self
1555 selector:@selector(tabWasAdded:)
1556 name:kTabModelNewTabWillOpenNotification
1557 object:_model];
1558}
1559
1560- (void)pageLoadStarting:(NSNotification*)notify {
1561 Tab* tab = notify.userInfo[kTabModelTabKey];
1562 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511563
1564 // Stop any Find in Page searches and close the find bar when navigating to a
1565 // new page.
1566 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361567
sdefresnee65fd872016-12-19 13:38:131568 if (tab == [_model currentTab]) {
1569 // TODO(pinkerton): Fill in here about hiding the forward button on
1570 // navigation.
1571 }
1572}
1573
1574- (void)pageLoadStarted:(NSNotification*)notify {
1575 Tab* tab = notify.userInfo[kTabModelTabKey];
1576 DCHECK(tab);
1577 if (tab == [_model currentTab]) {
1578 if (![self isTabNativePage:tab]) {
sczsf1620e52017-10-02 22:54:461579 [_toolbarCoordinator currentPageLoadStarted];
sdefresnee65fd872016-12-19 13:38:131580 }
1581 [self updateVoiceSearchBarVisibilityAnimated:NO];
1582 }
1583}
1584
1585- (void)pageLoadComplete:(NSNotification*)notify {
1586 // Update the UI, but only if the current tab.
1587 Tab* tab = notify.userInfo[kTabModelTabKey];
1588 if (tab == [_model currentTab]) {
1589 // There isn't any need to update the toolbar here. When the page finishes,
1590 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1591 }
1592
1593 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1594
1595 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1596}
1597
1598- (void)tabDeselected:(NSNotification*)notify {
1599 DCHECK(notify);
1600 Tab* tab = notify.userInfo[kTabModelTabKey];
1601 DCHECK(tab);
1602 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221603 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131604}
1605
1606- (void)tabWasAdded:(NSNotification*)notify {
1607 Tab* tab = notify.userInfo[kTabModelTabKey];
1608 DCHECK(tab);
1609
Eugene But56efc322017-08-11 14:03:441610 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131611
1612 // When adding new tabs, check what kind of reminder infobar should
1613 // be added to the new tab. Try to add only one of them.
1614 // This check is done when a new tab is added either through the Tools Menu
1615 // "New Tab" or through "New Tab" in Stack View Controller. This method
1616 // is called after a new tab has added and finished initial navigation.
1617 // If this is added earlier, the initial navigation may end up clearing
1618 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301619 web::WebState* webState = tab.webState;
1620 DCHECK(webState);
1621
1622 infobars::InfoBarManager* infoBarManager =
1623 InfoBarManagerImpl::FromWebState(webState);
1624 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131625 forTabId:[tab tabId]];
edchin9e7a1112017-11-07 18:28:031626 if (!ReSignInInfoBarDelegate::Create(_browserState, tab,
1627 self /* id<SigninPresenter> */)) {
edchin95c927072017-11-04 00:35:071628 DisplaySyncErrors(_browserState, tab, self /* id<SyncPresenter> */);
sdefresnee65fd872016-12-19 13:38:131629 }
1630
1631 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501632 // phone-specific. Call the foreground tab added completion block; for
1633 // iPhones, this will get executed after the animation has finished.
1634 if (IsIPadIdiom()) {
1635 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431636 // This callback is called before webState is activated (on
1637 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1638 // callback asynchronously to be sure the activation is complete.
1639 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011640 // Test existence again as the block may have been deleted.
1641 if (self.foregroundTabWasAddedCompletionBlock) {
1642 self.foregroundTabWasAddedCompletionBlock();
1643 self.foregroundTabWasAddedCompletionBlock = nil;
1644 }
Olivier Robinc7e46242017-09-06 07:55:431645 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501646 }
sdefresnee65fd872016-12-19 13:38:131647 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501648 }
sdefresnee65fd872016-12-19 13:38:131649
1650 // Do nothing if browsing is currently suspended. The BVC will set everything
1651 // up correctly when browsing resumes.
1652 if (!self.visible || ![_model webUsageEnabled])
1653 return;
1654
1655 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1656
1657 // Block that starts voice search at the end of new Tab animation if
1658 // necessary.
1659 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1660 if (_startVoiceSearchAfterNewTabAnimation) {
1661 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001662 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131663 }
1664 };
1665
kkhorimotoa44349c12017-04-12 23:02:121666 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131667 if (!inBackground) {
1668 UIView* animationParentView = _contentArea;
1669 // Create the new page image, and load with the new tab page snapshot.
1670 CGFloat newPageOffset = 0;
1671 UIImageView* newPage;
Sylvain Defresnee7f2c8a2017-10-17 02:39:191672 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
1673 !_isOffTheRecord && !IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131674 animationParentView = self.view;
1675 newPage = [self pageFullScreenOpenCloseAnimationView];
1676 } else {
1677 newPage = [self pageOpenCloseAnimationView];
1678 }
1679 newPageOffset = newPage.frame.origin.y;
1680
1681 [tab view].frame = _contentArea.bounds;
1682 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1683 [animationParentView addSubview:newPage];
1684 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521685 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
sdefresnee65fd872016-12-19 13:38:131686 newPage, -newPageOffset,
1687 newPage.frame.size.height - newPage.image.size.height, origin,
1688 _isOffTheRecord, NULL, ^{
1689 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121690 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421691 // Use the model's currentTab here because it is possible that it can
1692 // be reset to a new value before the new Tab animation finished (e.g.
1693 // if another Tab shows a dialog via |dialogPresenter|). However, that
1694 // tab's view hasn't been displayed yet because it was in a new tab
1695 // animation.
1696 Tab* currentTab = [_model currentTab];
1697 if (currentTab) {
1698 [self tabSelected:currentTab];
1699 }
sdefresnee65fd872016-12-19 13:38:131700 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391701
1702 if (self.foregroundTabWasAddedCompletionBlock) {
1703 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451704 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391705 }
sdefresnee65fd872016-12-19 13:38:131706 });
1707 } else {
1708 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1709 // snapshot before adding the views needed for the background animation.
1710 Tab* topTab = [_model currentTab];
1711 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1712 visibleFrameOnly:self.isToolbarOnScreen];
1713 // Add three layers in order on top of the contentArea for the animation:
1714 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271715 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131716 InstallBackgroundInView(background);
1717 [_contentArea addSubview:background];
1718
1719 // 2. A CardView displaying the data from the current tab.
1720 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1721 NSString* title = [topTab title];
1722 if (![title length])
1723 title = [topTab urlDisplayString];
1724 [topCard setTitle:title];
sdefresnee65fd872016-12-19 13:38:131725 [topCard setImage:image];
Sylvain Defresne7178d4c2017-09-14 13:22:371726 [topCard setFavicon:nil];
1727
1728 favicon::FaviconDriver* faviconDriver =
1729 favicon::WebFaviconDriver::FromWebState(topTab.webState);
1730 if (faviconDriver && faviconDriver->FaviconIsValid()) {
1731 gfx::Image favicon = faviconDriver->GetFavicon();
1732 if (!favicon.IsEmpty())
1733 [topCard setFavicon:favicon.ToUIImage()];
1734 }
sdefresnee65fd872016-12-19 13:38:131735
1736 // 3. A new, blank CardView to represent the new tab being added.
1737 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521738 page_animation_util::AnimateNewBackgroundPageWithCompletion(
sdefresnee65fd872016-12-19 13:38:131739 topCard, [_contentArea frame], IsPortrait(), ^{
1740 [background removeFromSuperview];
1741 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121742 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131743 // Resnapshot the top card if it has its own toolbar, as the toolbar
1744 // will be captured in the new tab animation, but isn't desired for
1745 // the stack view snapshots.
1746 id nativeController = [self nativeControllerForTab:topTab];
1747 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1748 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1749 startVoiceSearchIfNecessaryBlock();
1750 });
peterlaurens9f1b6e02017-06-22 17:46:451751 // Reset the foreground tab completion block so that it can never be
1752 // called more than once regardless of foreground/background tab
1753 // appearances.
1754 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131755 }
1756}
1757
1758#pragma mark - UI Configuration and Layout
1759
1760- (void)updateWithTabModel:(TabModel*)model
1761 browserState:(ios::ChromeBrowserState*)browserState {
1762 DCHECK(model);
1763 DCHECK(browserState);
1764 DCHECK(!_model);
1765 DCHECK(!_browserState);
1766 _browserState = browserState;
1767 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271768 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381769
sdefresnee65fd872016-12-19 13:38:131770 [_model addObserver:self];
1771
1772 if (!_isOffTheRecord) {
1773 [DefaultIOSWebViewFactory
1774 registerWebViewFactory:[ChromeWebViewFactory class]];
1775 }
1776 NSUInteger count = [_model count];
1777 for (NSUInteger index = 0; index < count; ++index)
1778 [self installDelegatesForTab:[_model tabAtIndex:index]];
1779
1780 [self registerForNotifications];
1781
gambardbdc07cc2017-02-03 16:43:111782 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121783 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271784 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131785
sdefresnedc432f42017-01-17 14:36:591786 // Register for bookmark changed notification (BookmarkModel may be null
1787 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131788 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591789 if (_bookmarkModel) {
Gauthier Ambard65e949b092017-11-29 08:46:201790 _bookmarkModelBridge.reset(
1791 new bookmarks::BookmarkModelBridge(self, _bookmarkModel));
sdefresnedc432f42017-01-17 14:36:591792 }
sdefresnee65fd872016-12-19 13:38:131793}
1794
sdefresnee65fd872016-12-19 13:38:131795- (void)browserStateDestroyed {
1796 [self setActive:NO];
sdefresnee65fd872016-12-19 13:38:131797 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271798 _paymentRequestManager = nil;
sczsf1620e52017-10-02 22:54:461799 [_toolbarCoordinator browserStateDestroyed];
sdefresnee65fd872016-12-19 13:38:131800 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381801
1802 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431803 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111804 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381805 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271806 [_pageInfoCoordinator disconnect];
Louis Romerod11747a2017-10-20 20:10:351807 [_externalSearchCoordinator disconnect];
edchinf5150c682017-09-18 02:50:031808 [self.tabStripCoordinator stop];
1809 self.tabStripCoordinator = nil;
1810 self.tabStripView = nil;
sczsdd860eba2017-08-10 01:55:381811
sdefresnee65fd872016-12-19 13:38:131812 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391813 [_dispatcher stopDispatchingToTarget:self];
1814 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131815}
1816
1817- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221818 CGFloat statusBarHeight = StatusBarHeight();
1819 CGRect statusBarFrame =
1820 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1821 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1822 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131823 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221824 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1825 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1826 [_fakeStatusBarView layer].zPosition = 99;
1827 [[self view] addSubview:_fakeStatusBarView];
1828 } else {
1829 // Add a white bar on phone so that the status bar on the NTP is white.
1830 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1831 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131832 }
1833}
1834
1835// Create the UI elements. May or may not have valid browser state & tab model.
1836- (void)buildToolbarAndTabStrip {
1837 DCHECK([self isViewLoaded]);
1838 DCHECK(!_toolbarModelDelegate);
1839
Rohit Rao44f204302017-08-10 14:49:541840 // Initialize the prerender service before creating the toolbar controller.
1841 PrerenderService* prerenderService =
1842 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1843 if (prerenderService) {
1844 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131845 }
1846
1847 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201848 _toolbarModelDelegate.reset(
1849 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131850 _toolbarModelIOS.reset([_dependencyFactory
1851 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
Peter Laurense0b80f12017-11-21 07:52:401852 _toolbarCoordinator = [[LegacyToolbarCoordinator alloc]
1853 initWithBaseViewController:self
1854 toolsMenuConfigurationProvider:self
1855 dispatcher:self.dispatcher];
1856
Gauthier Ambard29939db12017-10-30 16:47:311857 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
Peter Laurense0b80f12017-11-21 07:52:401858
sczsf1620e52017-10-02 22:54:461859 _toolbarCoordinator.tabModel = _model;
Gauthier Ambard82c8cc52017-10-26 15:59:051860 [_toolbarCoordinator
sczs32cfde82017-11-14 20:43:221861 setToolbarController:
1862 [_dependencyFactory
1863 newToolbarControllerWithDelegate:self
1864 urlLoader:self
1865 dispatcher:self.dispatcher]];
sczsf1620e52017-10-02 22:54:461866 [_dispatcher startDispatchingToTarget:_toolbarCoordinator
justincohen75011c32017-04-28 16:31:391867 forProtocol:@protocol(OmniboxFocuser)];
sczsf1620e52017-10-02 22:54:461868 [_toolbarCoordinator setTabCount:[_model count]];
Kurt Horimotoea429dd2017-11-28 02:24:301869 [self updateToolbarBroadcastState];
stkhapuginc9eee7b2017-04-10 15:49:271870 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461871 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:051872 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131873
sdefresnee65fd872016-12-19 13:38:131874 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031875 self.tabStripCoordinator =
1876 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1877 self.tabStripCoordinator.browserState = _browserState;
1878 self.tabStripCoordinator.dispatcher = _dispatcher;
1879 self.tabStripCoordinator.tabModel = _model;
1880 self.tabStripCoordinator.presentationProvider = self;
1881 self.tabStripCoordinator.animationWaitDuration =
Kurt Horimoto62e97c72017-11-03 19:51:471882 kLegacyFullscreenControllerToolbarAnimationDuration;
edchinf5150c682017-09-18 02:50:031883 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131884 }
1885
1886 // Create infobar container.
1887 if (!_infoBarContainerDelegate) {
1888 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1889 _infoBarContainer.reset(
1890 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1891 }
1892}
1893
Jean-François Geyelined4cde72017-10-11 11:34:501894- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:111895 NSLayoutYAxisAnchor* topAnchor;
1896 // On iPad, the toolbar is underneath the tab strip.
1897 // On iPhone, it is underneath the top of the screen.
1898 if (IsIPadIdiom()) {
1899 topAnchor = self.tabStripView.bottomAnchor;
1900 } else {
1901 topAnchor = [self view].topAnchor;
1902 }
1903
Gauthier Ambard100670f72017-10-27 09:54:271904 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501905
Justin Cohen9fe9ef672017-12-01 20:37:431906 self.toolbarOffsetConstraint =
1907 [_toolbarCoordinator.toolbarViewController.view.topAnchor
1908 constraintEqualToAnchor:topAnchor];
Jean-François Geyelined4cde72017-10-11 11:34:501909 [NSLayoutConstraint activateConstraints:@[
Justin Cohen9fe9ef672017-12-01 20:37:431910 self.toolbarOffsetConstraint,
sczs42f7f7482017-11-08 01:13:271911 [_toolbarCoordinator.toolbarViewController.view.leadingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501912 constraintEqualToAnchor:[self view].leadingAnchor],
sczs42f7f7482017-11-08 01:13:271913 [_toolbarCoordinator.toolbarViewController.view.trailingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501914 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501915 ]];
1916 [[self view] layoutIfNeeded];
1917}
1918
sdefresnee65fd872016-12-19 13:38:131919// Enable functionality that only makes sense if the views are loaded and
1920// both browser state and tab model are valid.
1921- (void)addUIFunctionalityForModelAndBrowserState {
1922 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051923 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131924 DCHECK(_model);
1925 DCHECK([self isViewLoaded]);
1926
1927 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1928
Rohit Raoaf46af92017-08-10 12:52:301929 infobars::InfoBarManager* infoBarManager = nullptr;
1930 if (_model.currentTab) {
1931 DCHECK(_model.currentTab.webState);
1932 infoBarManager =
1933 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1934 }
sdefresnee65fd872016-12-19 13:38:131935 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1936
sczsdd860eba2017-08-10 01:55:381937 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431938 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1939 initWithBaseViewController:self];
1940 _activityServiceCoordinator.dispatcher = _dispatcher;
1941 _activityServiceCoordinator.tabModel = _model;
1942 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:461943 _activityServiceCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051944 [_toolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:431945 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:431946
Rohit Raocda0a992017-08-16 15:37:111947 _qrScannerCoordinator =
1948 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
1949 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:051950 _qrScannerCoordinator.loadProvider =
1951 [_toolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:111952 _qrScannerCoordinator.presentationProvider = self;
1953
sczsdd860eba2017-08-10 01:55:381954 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:131955 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:381956 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:461957 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051958 [_toolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:381959 _tabHistoryCoordinator.tabModel = _model;
1960 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:461961 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard82c8cc52017-10-26 15:59:051962 [_toolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:381963
sczs6ae47ad2017-09-06 17:26:531964 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:201965 _sadTabCoordinator.baseViewController = self;
1966 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:531967
Gregory Chatzinoffdf93d692017-09-09 01:32:271968 _pageInfoCoordinator =
1969 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
1970 _pageInfoCoordinator.browserState = _browserState;
1971 _pageInfoCoordinator.dispatcher = _dispatcher;
1972 _pageInfoCoordinator.loader = self;
1973 _pageInfoCoordinator.presentationProvider = self;
1974 _pageInfoCoordinator.tabModel = _model;
1975
Louis Romerod11747a2017-10-20 20:10:351976 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
1977 _externalSearchCoordinator.dispatcher = _dispatcher;
1978
mathp9b4c11d2017-07-06 20:24:131979 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:271980 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:131981 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:201982 browserState:_browserState
1983 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:051984 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:191985 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:131986 }
1987}
1988
1989// Set the frame for the various views. View must be loaded.
Justin Cohen4eeada32017-11-13 18:21:281990- (void)setUpViewLayout:(BOOL)initialLayout {
sdefresnee65fd872016-12-19 13:38:131991 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:131992 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:131993 CGFloat minY = [self headerOffset];
1994
Justin Cohenb3170c32017-09-19 01:55:221995 // Update the fake toolbar background height.
1996 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
1997 fakeStatusBarFrame.size.height = StatusBarHeight();
1998 _fakeStatusBarView.frame = fakeStatusBarFrame;
1999
edchinf5150c682017-09-18 02:50:032000 if (self.tabStripView) {
2001 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:132002 }
2003
2004 // Position the toolbar next, either at the top of the browser view or
2005 // directly under the tabstrip.
Justin Cohen4eeada32017-11-13 18:21:282006 if (initialLayout)
2007 [self addChildViewController:_toolbarCoordinator.toolbarViewController];
sczs42f7f7482017-11-08 01:13:272008 CGRect toolbarFrame = _toolbarCoordinator.toolbarViewController.view.frame;
sdefresnee65fd872016-12-19 13:38:132009 toolbarFrame.origin = CGPointMake(0, minY);
2010 toolbarFrame.size.width = widthOfView;
Justin Cohenba27610e2017-11-08 19:34:452011 if (!IsSafeAreaCompatibleToolbarEnabled()) {
sczs42f7f7482017-11-08 01:13:272012 [_toolbarCoordinator.toolbarViewController.view setFrame:toolbarFrame];
Jean-François Geyelined4cde72017-10-11 11:34:502013 }
sdefresnee65fd872016-12-19 13:38:132014
2015 // Place the infobar container above the content area.
2016 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
Justin Cohen4eeada32017-11-13 18:21:282017 if (initialLayout)
2018 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
sdefresnee65fd872016-12-19 13:38:132019
2020 // Place the toolbar controller above the infobar container.
Justin Cohen4eeada32017-11-13 18:21:282021 if (initialLayout)
2022 [[self view] insertSubview:_toolbarCoordinator.toolbarViewController.view
2023 aboveSubview:infoBarContainerView];
sdefresnee65fd872016-12-19 13:38:132024 minY += CGRectGetHeight(toolbarFrame);
Justin Cohen4eeada32017-11-13 18:21:282025 if (initialLayout)
2026 [_toolbarCoordinator.toolbarViewController
2027 didMoveToParentViewController:self];
sdefresnee65fd872016-12-19 13:38:132028
2029 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
2030 // content slightly.
sczs8c837782017-10-03 02:57:242031 minY -= 0.0;
sdefresnee65fd872016-12-19 13:38:132032
2033 // Adjust the content area to be under the toolbar, for fullscreen or below
2034 // the toolbar is not fullscreen.
2035 CGRect contentFrame = [_contentArea frame];
2036 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222037 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2038 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132039 [_contentArea setFrame:contentFrame];
2040
2041 // Adjust the infobar container to be either at the bottom of the screen
2042 // (iPhone) or on the lower toolbar edge (iPad).
2043 CGRect infoBarFrame = contentFrame;
2044 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2045 infoBarFrame.size.height = 0;
2046 [infoBarContainerView setFrame:infoBarFrame];
2047
2048 // Attach the typing shield to the content area but have it hidden.
2049 [_typingShield setFrame:[_contentArea frame]];
Justin Cohen41b1f382017-12-04 15:22:082050 if (initialLayout) {
Justin Cohen4eeada32017-11-13 18:21:282051 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
Justin Cohen41b1f382017-12-04 15:22:082052 [_typingShield setHidden:YES];
2053 }
sdefresnee65fd872016-12-19 13:38:132054}
2055
sdefresnee65fd872016-12-19 13:38:132056- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2057 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442058 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132059
kkhorimotoa44349c12017-04-12 23:02:122060 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132061 // Hide findbar. |updateToolbar| will restore the findbar later.
2062 [self hideFindBarWithAnimation:NO];
2063
2064 // Make new content visible, resizing it first as the orientation may
2065 // have changed from the last time it was displayed.
2066 [[tab view] setFrame:_contentArea.bounds];
2067 [_contentArea displayContentView:[tab view]];
2068 }
2069 [self updateToolbar];
2070
2071 if (newSelection)
sczsf1620e52017-10-02 22:54:462072 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132073
2074 // Notify the Tab that it was displayed.
2075 [tab wasShown];
2076}
2077
2078- (void)initializeBookmarkInteractionController {
2079 if (_bookmarkInteractionController)
2080 return;
edchinbb8ba892017-09-12 15:44:032081 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2082 initWithBrowserState:_browserState
2083 loader:self
2084 parentController:self
2085 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132086}
2087
2088// Update the state of back and forward buttons, hiding the forward button if
2089// there is nowhere to go. Assumes the model's current tab is up to date.
2090- (void)updateToolbar {
2091 // If the BVC has been partially torn down for low memory, wait for the
2092 // view rebuild to handle toolbar updates.
2093 if (!(_toolbarModelIOS && _browserState))
2094 return;
2095
2096 Tab* tab = [_model currentTab];
2097 if (![tab navigationManager])
2098 return;
sczsf1620e52017-10-02 22:54:462099 [_toolbarCoordinator updateToolbarState];
2100 [_toolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132101
Sylvain Defresnef5d2d952017-11-14 11:15:312102 if (_insertedTabWasPrerenderedTab && !_toolbarModelIOS->IsLoading())
sczsf1620e52017-10-02 22:54:462103 [_toolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132104
rohitrao005a6432017-03-16 20:52:422105 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2106 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132107 [self showFindBarWithAnimation:NO
2108 selectText:YES
2109 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362110 }
sdefresnee65fd872016-12-19 13:38:132111
2112 // Hide the toolbar if displaying phone NTP.
2113 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552114 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132115 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552116 if (item) {
2117 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132118 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2119 hideToolbar = isNTP && !_isOffTheRecord &&
sczsf1620e52017-10-02 22:54:462120 ![_toolbarCoordinator isOmniboxFirstResponder] &&
2121 ![_toolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132122 }
sczs42f7f7482017-11-08 01:13:272123 [_toolbarCoordinator.toolbarViewController.view setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132124 }
2125}
2126
Kurt Horimotoea429dd2017-11-28 02:24:302127- (void)updateToolbarBroadcastState {
2128 BOOL shouldBroadcast =
2129 self.active && self.viewVisible && !self.inNewTabAnimation;
2130 BOOL broadcasting = _toolbarUIUpdater != nil;
2131 if (shouldBroadcast == broadcasting)
2132 return;
2133 if (shouldBroadcast) {
2134 _toolbarUIUpdater = [[LegacyToolbarUIUpdater alloc]
2135 initWithToolbarUI:[[ToolbarUIState alloc] init]
2136 toolbarOwner:self
2137 webStateList:[_model webStateList]];
2138 [_toolbarUIUpdater startUpdating];
2139 } else {
2140 [_toolbarUIUpdater stopUpdating];
2141 _toolbarUIUpdater = nil;
2142 }
2143}
2144
sdefresnee65fd872016-12-19 13:38:132145- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122146 self.dialogPresenter.active =
2147 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132148}
2149
2150- (void)dismissPopups {
Peter Laurense0b80f12017-11-21 07:52:402151 [self.dispatcher dismissToolsMenu];
Gregory Chatzinoffdf93d692017-09-09 01:32:272152 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282153 [_tabHistoryCoordinator dismissHistoryPopup];
Cooper Knaakd0a974cd2017-08-10 18:05:472154 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382155}
2156
Cooper Knaakd0a974cd2017-08-10 18:05:472157- (BubbleViewControllerPresenter*)
2158bubblePresenterForFeature:(const base::Feature&)feature
2159 direction:(BubbleArrowDirection)direction
2160 alignment:(BubbleAlignment)alignment
2161 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212162 DCHECK(self.browserState);
2163 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382164 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472165 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382166 }
2167 // Capture |weakSelf| instead of the feature engagement tracker object
2168 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2169 // the feature engagement tracker will remain pointing to invalid memory if
2170 // its owner (the ChromeBrowserState) is deallocated.
2171 __weak BrowserViewController* weakSelf = self;
2172 void (^dismissalCallback)(void) = ^() {
2173 BrowserViewController* strongSelf = weakSelf;
2174 if (strongSelf) {
2175 feature_engagement::TrackerFactory::GetForBrowserState(
2176 strongSelf.browserState)
2177 ->Dismissed(feature);
2178 }
2179 };
2180
Cooper Knaakd0a974cd2017-08-10 18:05:472181 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382182 [[BubbleViewControllerPresenter alloc] initWithText:text
2183 arrowDirection:direction
2184 alignment:alignment
2185 dismissalCallback:dismissalCallback];
2186
Cooper Knaakd0a974cd2017-08-10 18:05:472187 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132188}
2189
Cooper Knaak120cee5e2017-08-10 20:57:002190- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212191 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002192 // If the tab tip bubble has already been presented and the user is still
2193 // considered engaged, it can't be overwritten or set to |nil| or else it will
2194 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2195 // can be safely overwritten or set to |nil|.
2196 if (!self.tabTipBubblePresenter.isUserEngaged) {
2197 __weak BrowserViewController* weakSelf = self;
2198 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2199 [weakSelf presentNewTabTipBubble];
2200 };
2201
2202 // Because the new tab tip occurs on startup, the feature engagement
2203 // tracker's database is not guaranteed to be loaded by this time. For the
2204 // bubble to appear properly, a callback is used to guarantee the event data
2205 // is loaded before the check to see if the promotion should be displayed.
2206 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2207 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2208 }
2209}
2210
2211- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212212 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002213 NSString* text =
2214 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2215 CGPoint tabSwitcherAnchor;
2216 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032217 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002218 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032219 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002220 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2221 } else {
sczsf1620e52017-10-02 22:54:462222 DCHECK([_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002223 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
sczsf1620e52017-10-02 22:54:462224 tabSwitcherAnchor = [_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002225 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2226 }
Cooper Knaake963d6702017-08-11 21:03:112227 // If the feature engagement tracker does not consider it valid to display
2228 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2229 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002230 self.tabTipBubblePresenter =
2231 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2232 direction:BubbleArrowDirectionUp
2233 alignment:BubbleAlignmentTrailing
2234 text:text];
2235 [self.tabTipBubblePresenter presentInViewController:self
2236 view:self.view
2237 anchorPoint:tabSwitcherAnchor];
2238}
2239
Helen Yang9175bd52017-08-12 00:28:402240- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212241 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402242 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2243 // user is still considered engaged.
2244 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2245 __weak BrowserViewController* weakSelf = self;
2246 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2247 [weakSelf presentNewIncognitoTabTipBubble];
2248 };
2249
2250 // Use a callback in case the new incognito tab tip should be shown on
2251 // startup. This ensures that the tracker's database will be fully loaded
2252 // before checking if the promotion should be displayed.
2253 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2254 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2255 }
2256}
2257
2258- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212259 DCHECK(self.browserState);
sczsf1620e52017-10-02 22:54:462260 DCHECK([_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402261 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2262 NSString* text = l10n_util::GetNSStringWithFixup(
2263 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
sczsf1620e52017-10-02 22:54:462264 CGPoint toolsButtonAnchor = [_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402265 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2266 self.incognitoTabTipBubblePresenter =
2267 [self bubblePresenterForFeature:feature_engagement::
2268 kIPHNewIncognitoTabTipFeature
2269 direction:BubbleArrowDirectionUp
2270 alignment:BubbleAlignmentTrailing
2271 text:text];
2272 [self.incognitoTabTipBubblePresenter
2273 presentInViewController:self
2274 view:self.view
2275 anchorPoint:toolsButtonAnchor];
2276 // Only trigger the tools menu button animation if the bubble is shown.
2277 if (self.incognitoTabTipBubblePresenter) {
sczsf1620e52017-10-02 22:54:462278 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402279 }
2280}
2281
sdefresnee65fd872016-12-19 13:38:132282#pragma mark - Tap handling
2283
Mark Cogandfcdea72017-07-18 13:47:382284- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032285 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2286 _lastTapPoint = CGPointZero;
2287 } else {
2288 _lastTapPoint =
2289 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132290 }
Mark Cogane01ebce2017-07-12 19:31:032291 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132292}
2293
2294- (CGPoint)lastTapPoint {
2295 if (CACurrentMediaTime() - _lastTapTime < 1) {
2296 return _lastTapPoint;
2297 }
2298 return CGPointZero;
2299}
2300
2301- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2302 UIView* view = gestureRecognizer.view;
2303 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2304 _lastTapPoint =
2305 [[view superview] convertPoint:viewCoordinate toView:self.view];
2306 _lastTapTime = CACurrentMediaTime();
2307}
2308
2309- (BOOL)addTabIfNoTabWithNormalBrowserState {
2310 if (![_model count]) {
2311 if (!_isOffTheRecord) {
2312 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2313 transition:ui::PAGE_TRANSITION_TYPED];
2314 return YES;
2315 }
2316 }
2317 return NO;
2318}
2319
2320#pragma mark - Tab creation and selection
2321
2322// Called when either a tab finishes loading or when a tab with finished content
2323// is added directly to the model via pre-rendering.
2324- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2325 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2326
2327 // Persist the session on a delay.
2328 [_model saveSessionImmediately:NO];
2329}
2330
2331- (Tab*)addSelectedTabWithURL:(const GURL&)url
2332 postData:(TemplateURLRef::PostContent*)postData
2333 transition:(ui::PageTransition)transition {
2334 return [self addSelectedTabWithURL:url
2335 postData:postData
2336 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492337 transition:transition
2338 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132339}
2340
2341- (Tab*)addSelectedTabWithURL:(const GURL&)url
2342 transition:(ui::PageTransition)transition {
2343 return [self addSelectedTabWithURL:url
2344 atIndex:[_model count]
2345 transition:transition];
2346}
2347
2348- (Tab*)addSelectedTabWithURL:(const GURL&)url
2349 atIndex:(NSUInteger)position
2350 transition:(ui::PageTransition)transition {
2351 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492352 atIndex:position
2353 transition:transition
2354 tabAddedCompletion:nil];
2355}
2356
2357- (Tab*)addSelectedTabWithURL:(const GURL&)url
2358 atIndex:(NSUInteger)position
2359 transition:(ui::PageTransition)transition
2360 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2361 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132362 postData:NULL
2363 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492364 transition:transition
2365 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132366}
2367
2368- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2369 postData:(TemplateURLRef::PostContent*)postData
2370 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492371 transition:(ui::PageTransition)transition
2372 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132373 if (position == NSNotFound)
2374 position = [_model count];
2375 DCHECK(position <= [_model count]);
2376
2377 web::NavigationManager::WebLoadParams params(URL);
2378 params.transition_type = transition;
2379 if (postData) {
2380 // Extract the content type and post params from |postData| and add them
2381 // to the load params.
2382 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2383 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2384 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172385 params.post_data.reset(data);
2386 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132387 }
Olivier Robind508a5632017-07-19 16:29:492388
2389 if (tabAddedCompletion) {
2390 if (self.foregroundTabWasAddedCompletionBlock) {
2391 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2392 self.foregroundTabWasAddedCompletionBlock;
2393 self.foregroundTabWasAddedCompletionBlock = ^{
2394 oldForegroundTabWasAddedCompletionBlock();
2395 tabAddedCompletion();
2396 };
2397 } else {
2398 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2399 }
2400 }
2401
sdefresnea6395912017-03-01 01:14:352402 Tab* tab = [_model insertTabWithLoadParams:params
2403 opener:nil
2404 openedByDOM:NO
2405 atIndex:position
2406 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132407 return tab;
2408}
2409
olivierrobin889af53f2017-03-01 14:56:322410// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132411- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322412 web::WebState* webState = tab.webState;
2413 if (!webState)
2414 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392415 web::NavigationItem* visibleItem =
2416 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322417 if (!visibleItem)
2418 return NO;
2419 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132420}
2421
2422- (void)expectNewForegroundTab {
2423 _expectingForegroundTab = YES;
2424}
2425
2426- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2427 CGRect viewBounds, remainder;
2428 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2429 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172430 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132431}
2432
2433- (UIImageView*)pageOpenCloseAnimationView {
2434 CGRect frame = [_contentArea bounds];
2435
2436 frame.size.height = frame.size.height - [self headerHeight];
2437 frame.origin.y = [self headerHeight];
2438
stkhapuginf58b10d02017-04-10 13:36:172439 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132440 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2441 pageView.center = center;
2442
2443 pageView.backgroundColor = [UIColor whiteColor];
2444 return pageView;
2445}
2446
2447- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142448 // Unregistration happens when the Tab is removed from the TabModel.
Sylvain Defresnef5d2d952017-11-14 11:15:312449 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
2450
2451 // There should be no pre-rendered Tabs in TabModel.
2452 PrerenderService* prerenderService =
2453 PrerenderServiceFactory::GetForBrowserState(_browserState);
2454 DCHECK(!prerenderService ||
2455 !prerenderService->IsWebStatePrerendered(tab.webState));
edchincd32fdf2017-10-25 12:45:452456
2457 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2458 if (PasswordTabHelper* passwordTabHelper =
2459 PasswordTabHelper::FromWebState(tab.webState)) {
edchin6941b8492017-12-01 18:59:592460 passwordTabHelper->SetBaseViewController(self);
edchincd32fdf2017-10-25 12:45:452461 passwordTabHelper->SetDispatcher(self.dispatcher);
2462 passwordTabHelper->SetPasswordControllerDelegate(self);
2463 }
2464
sdefresnee65fd872016-12-19 13:38:132465 tab.dialogDelegate = self;
2466 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132467 tab.passKitDialogProvider = self;
Kurt Horimotoa5a922a2017-11-07 00:21:092468 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472469 tab.legacyFullscreenControllerDelegate = self;
Kurt Horimoto803840622017-10-28 01:20:372470 }
sdefresnee65fd872016-12-19 13:38:132471 if (!IsIPadIdiom()) {
2472 tab.overscrollActionsControllerDelegate = self;
2473 }
olivierrobin9ce77b82017-01-12 17:29:192474 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132475 tab.tabSnapshottingDelegate = self;
2476 // Install the proper CRWWebController delegates.
2477 tab.webController.nativeProvider = self;
2478 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512479 // BrowserViewController presents SKStoreKitViewController on behalf of a
2480 // tab.
2481 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2482 if (tabHelper)
2483 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132484 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532485 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292486 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532487 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292488 }
Sylvain Defresnecacc3a52017-09-12 13:51:042489 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592490 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572491 NetExportTabHelper::CreateForWebState(tab.webState, self);
Mike Dougherty4620cf8e2017-10-31 23:37:092492 CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
edchincd32fdf2017-10-25 12:45:452493
Mark Coganca30df62017-11-20 14:29:112494 // The language detection helper accepts a callback from the translate
2495 // client, so must be created after it.
2496 // This will explode if the webState doesn't have a JS injection manager
2497 // (this only comes up in unit tests), so check for that and bypass the
2498 // init of the translation helpers if needed.
2499 // TODO(crbug.com/785238): Remove the need for this check.
2500 if (tab.webState->GetJSInjectionReceiver()) {
2501 ChromeIOSTranslateClient::CreateForWebState(tab.webState,
2502 _languageSelectionCoordinator);
2503 language::IOSLanguageDetectionTabHelper::CreateForWebState(
2504 tab.webState,
2505 ChromeIOSTranslateClient::FromWebState(tab.webState)
2506 ->GetTranslateDriver()
2507 ->CreateLanguageDetectionCallback(),
2508 UrlLanguageHistogramFactory::GetForBrowserState(self.browserState));
2509 }
2510
edchincd32fdf2017-10-25 12:45:452511 if (AccountConsistencyService* accountConsistencyService =
2512 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2513 self.browserState)) {
2514 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552515 }
sdefresnee65fd872016-12-19 13:38:132516}
2517
sdefresne49cf2862017-03-15 13:46:142518- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112519 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
edchincd32fdf2017-10-25 12:45:452520
2521 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2522 if (PasswordTabHelper* passwordTabHelper =
2523 PasswordTabHelper::FromWebState(tab.webState))
2524 passwordTabHelper->SetDispatcher(nil);
2525
sdefresne49cf2862017-03-15 13:46:142526 tab.dialogDelegate = nil;
2527 tab.snapshotOverlayProvider = nil;
2528 tab.passKitDialogProvider = nil;
Kurt Horimotoa5a922a2017-11-07 00:21:092529 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472530 tab.legacyFullscreenControllerDelegate = nil;
Kurt Horimoto803840622017-10-28 01:20:372531 }
sdefresne49cf2862017-03-15 13:46:142532 if (!IsIPadIdiom()) {
2533 tab.overscrollActionsControllerDelegate = nil;
2534 }
2535 tab.tabHeadersDelegate = nil;
2536 tab.tabSnapshottingDelegate = nil;
2537 tab.webController.nativeProvider = nil;
2538 tab.webController.swipeRecognizerProvider = nil;
2539 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2540 if (tabHelper)
2541 tabHelper->SetLauncher(nil);
2542 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452543 if (AccountConsistencyService* accountConsistencyService =
2544 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2545 self.browserState)) {
2546 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2547 }
sdefresne49cf2862017-03-15 13:46:142548}
2549
sdefresnee65fd872016-12-19 13:38:132550// Called when a tab is selected in the model. Make any required view changes.
2551// The notification will not be sent when the tab is already the selected tab.
2552- (void)tabSelected:(Tab*)tab {
2553 DCHECK(tab);
2554
2555 // Ignore changes while the tab stack view is visible (or while suspended).
2556 // The display will be refreshed when this view becomes active again.
2557 if (!self.visible || ![_model webUsageEnabled])
2558 return;
2559
2560 [self displayTab:tab isNewSelection:YES];
2561
kkhorimotoa44349c12017-04-12 23:02:122562 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132563 // Now that the new tab has been displayed, return to normal. Rather than
2564 // keep a reference to the previous tab, just turn off preview mode for all
2565 // tabs (since doing so is a no-op for the tabs that don't have it set).
2566 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272567 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132568 [tab.webController setOverlayPreviewMode:NO];
2569 }
2570 }
2571}
2572
edchinf5150c682017-09-18 02:50:032573- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
2574 return [self.tabStripCoordinator placeholderView];
2575}
2576
Sylvain Defresne41170aa2017-06-15 10:25:202577- (void)shutdown {
2578 DCHECK(!_isShutdown);
2579 _isShutdown = YES;
edchinf5150c682017-09-18 02:50:032580 [self.tabStripCoordinator stop];
2581 self.tabStripCoordinator = nil;
sczs42f7f7482017-11-08 01:13:272582 [_toolbarCoordinator stop];
2583 _toolbarCoordinator = nil;
edchinf5150c682017-09-18 02:50:032584 self.tabStripView = nil;
Sylvain Defresne41170aa2017-06-15 10:25:202585 _infoBarContainer = nil;
2586 _readingListMenuNotifier = nil;
Gauthier Ambard65e949b092017-11-29 08:46:202587 _bookmarkModelBridge.reset();
Sylvain Defresne41170aa2017-06-15 10:25:202588 [_model removeObserver:self];
2589 [[UpgradeCenter sharedInstance] unregisterClient:self];
2590 [[NSNotificationCenter defaultCenter] removeObserver:self];
Gauthier Ambard82c8cc52017-10-26 15:59:052591 [_toolbarCoordinator setToolbarDelegate:nil];
Sylvain Defresne41170aa2017-06-15 10:25:202592 if (_voiceSearchController)
2593 _voiceSearchController->SetDelegate(nil);
2594 [_rateThisAppDialog setDelegate:nil];
2595 [_model closeAllTabs];
Mohamad Ahmadibec07eb2017-09-12 19:38:462596 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202597}
2598
sdefresnee65fd872016-12-19 13:38:132599#pragma mark - SnapshotOverlayProvider methods
2600
2601- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2602 NSMutableArray* overlays = [NSMutableArray array];
2603 if (![_model webUsageEnabled]) {
2604 return overlays;
2605 }
2606 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2607 if (voiceSearchView) {
2608 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272609 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132610 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272611 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132612 [overlays addObject:voiceSearchOverlay];
2613 }
2614 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2615 if (infoBarView) {
2616 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272617 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132618 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272619 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132620 [overlays addObject:infoBarOverlay];
2621 }
2622 return overlays;
2623}
2624
2625#pragma mark -
2626
2627- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2628 if (IsIPadIdiom()) {
2629 // Not using overlays on iPad because the content is pushed down by
2630 // infobar and the transition between snapshot and fresh page can
2631 // cause both snapshot and real infobars to appear at the same time.
2632 return nil;
2633 }
2634 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302635 if (currentTab && tab == currentTab) {
2636 DCHECK(currentTab.webState);
2637 infobars::InfoBarManager* infoBarManager =
2638 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132639 if (infoBarManager->infobar_count() > 0) {
2640 DCHECK(_infoBarContainer);
2641 return _infoBarContainer->view();
2642 }
2643 }
2644 return nil;
2645}
2646
2647- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272648 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132649 // There is no UI representation for non-current tabs or there is
2650 // no _infoBarContainer instantiated yet.
2651 // Return offset outside of tab.
2652 return CGRectGetMaxY(self.view.frame);
2653 } else if (IsIPadIdiom()) {
2654 // The infobars on iPad are display at the top of a tab.
2655 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2656 } else {
2657 // The infobars on iPhone are displayed at the bottom of a tab.
2658 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2659 return CGRectGetMaxY(visibleFrame) -
2660 CGRectGetHeight(_infoBarContainer->view().frame);
2661 }
2662}
2663
2664- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2665 Tab* currentTab = [_model currentTab];
2666 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2667 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2668 return _voiceSearchBar;
2669 }
2670 return nil;
2671}
2672
2673- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2674 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2675 // There is no UI representation for non-current tabs or there is
2676 // no visible voice search. Return offset outside of tab.
2677 return CGRectGetMaxY(self.view.frame);
2678 } else {
2679 // The voice search bar on iPhone is displayed at the bottom of a tab.
2680 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2681 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2682 }
2683}
2684
2685- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272686 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132687 VoiceSearchProvider* provider =
2688 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2689 if (provider) {
2690 _voiceSearchController =
2691 provider->CreateVoiceSearchController(_browserState);
sczsf1620e52017-10-02 22:54:462692 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:052693 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:132694 }
2695 }
2696}
2697
2698- (void)ensureVoiceSearchBarCreated {
2699 if (_voiceSearchBar)
2700 return;
2701
2702 CGFloat width = CGRectGetWidth([[self view] bounds]);
2703 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2704 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272705 _voiceSearchBar = ios::GetChromeBrowserProvider()
2706 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002707 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132708 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2709 [_voiceSearchBar setHidden:YES];
2710 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2711 UIViewAutoresizingFlexibleWidth];
2712 [self.view insertSubview:_voiceSearchBar
2713 belowSubview:_infoBarContainer->view()];
2714}
2715
2716- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2717 // Voice search bar exists and is shown/hidden.
2718 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272719 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132720 return;
2721
2722 // Voice search bar doesn't exist and thus is not visible.
2723 if (!_voiceSearchBar && !show)
2724 return;
2725
2726 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272727 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132728 else
stkhapuginc9eee7b2017-04-10 15:49:272729 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132730}
2731
2732- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2733 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2734 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2735 self.shouldShowVoiceSearchBar) {
2736 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2737 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272738 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132739 }
2740 id currentNativeController =
2741 [self nativeControllerForTab:self.tabModel.currentTab];
2742 Protocol* possibleOwnerProtocol =
2743 @protocol(LogoAnimationControllerOwnerOwner);
2744 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2745 [currentNativeController logoAnimationControllerOwner]) {
2746 // If the current native controller is showing a GLIF view (e.g. the NTP
2747 // when there is no doodle), use that GLIFControllerOwner.
2748 return [currentNativeController logoAnimationControllerOwner];
2749 }
2750 return nil;
2751}
2752
2753#pragma mark - PassKitDialogProvider methods
2754
2755- (void)presentPassKitDialog:(NSData*)data {
2756 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272757 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132758 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272759 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132760 if (error || !data) {
2761 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302762 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132763 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302764 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132765 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2766 // NULL, replace if with DCHECK).
2767 if (infoBarManager)
2768 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2769 }
2770 } else {
2771 PKAddPassesViewController* passKitViewController =
2772 [_dependencyFactory newPassKitViewControllerForPass:pass];
2773 if (passKitViewController) {
2774 [self presentViewController:passKitViewController
2775 animated:YES
2776 completion:^{
2777 }];
2778 }
2779 }
2780}
2781
2782- (UIStatusBarStyle)preferredStatusBarStyle {
2783 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2784 : UIStatusBarStyleDefault;
2785}
2786
Tomasz Garbusb844e992017-09-29 12:44:552787#pragma mark - PasswordControllerDelegate methods
2788
2789- (BOOL)displaySignInNotification:(UIViewController*)viewController
2790 fromTabId:(NSString*)tabId {
2791 // Check if the call comes from currently visible tab.
2792 if ([tabId isEqual:[_model currentTab].tabId]) {
2793 [self addChildViewController:viewController];
2794 [self.view addSubview:viewController.view];
2795 [viewController didMoveToParentViewController:self];
2796 return YES;
2797 } else {
2798 return NO;
2799 }
2800}
2801
sdefresnee65fd872016-12-19 13:38:132802#pragma mark - CRWWebStateDelegate methods.
2803
eugenebut75a06fa72017-01-09 17:09:552804- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512805 createNewWebStateForURL:(const GURL&)URL
2806 openerURL:(const GURL&)openerURL
2807 initiatedByUser:(BOOL)initiatedByUser {
2808 // Check if requested web state is a popup and block it if necessary.
2809 if (!initiatedByUser) {
2810 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2811 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102812 // It's possible for a page to inject a popup into a window created via
2813 // window.open before its initial load is committed. Rather than relying
2814 // on the last committed or pending NavigationItem's referrer policy, just
2815 // use ReferrerPolicyDefault.
2816 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2817 // policy once referrer policies are correctly recorded in
2818 // NavigationItems.
2819 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512820 helper->HandlePopup(URL, referrer);
2821 return nil;
2822 }
2823 }
2824
2825 // Requested web state should not be blocked from opening.
2826 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2827 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2828
2829 // Tabs open by DOM are always renderer initiated.
2830 web::NavigationManager::WebLoadParams params(GURL{});
2831 params.transition_type = ui::PAGE_TRANSITION_LINK;
2832 params.is_renderer_initiated = true;
2833 Tab* childTab = [[self tabModel]
2834 insertTabWithLoadParams:params
2835 opener:currentTab
2836 openedByDOM:YES
2837 atIndex:TabModelConstants::kTabPositionAutomatically
2838 inBackground:NO];
2839 return childTab.webState;
2840}
2841
eugenebutb46b2122017-03-14 02:43:262842- (void)closeWebState:(web::WebState*)webState {
2843 // Only allow a web page to close itself if it was opened by DOM, or if there
2844 // are no navigation items.
2845 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582846 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262847
2848 if (![self tabModel])
2849 return;
2850
2851 NSUInteger index = [[self tabModel] indexOfTab:tab];
2852 if (index != NSNotFound)
2853 [[self tabModel] closeTabAtIndex:index];
2854}
2855
eugenebut275f5892017-03-09 22:20:512856- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552857 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2858 switch (params.disposition) {
2859 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2860 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2861 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352862 insertTabWithURL:params.url
2863 referrer:params.referrer
2864 transition:params.transition
2865 opener:LegacyTabHelper::GetTabForWebState(webState)
2866 openedByDOM:NO
2867 atIndex:TabModelConstants::kTabPositionAutomatically
2868 inBackground:(params.disposition ==
2869 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552870 return tab.webState;
2871 }
2872 case WindowOpenDisposition::CURRENT_TAB: {
2873 web::NavigationManager::WebLoadParams loadParams(params.url);
2874 loadParams.referrer = params.referrer;
2875 loadParams.transition_type = params.transition;
2876 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2877 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2878 return webState;
2879 }
eugenebutd0984e82017-02-22 23:47:512880 case WindowOpenDisposition::NEW_POPUP: {
2881 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352882 insertTabWithURL:params.url
2883 referrer:params.referrer
2884 transition:params.transition
2885 opener:LegacyTabHelper::GetTabForWebState(webState)
2886 openedByDOM:YES
2887 atIndex:TabModelConstants::kTabPositionAutomatically
2888 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512889 return tab.webState;
2890 }
eugenebut75a06fa72017-01-09 17:09:552891 default:
2892 NOTIMPLEMENTED();
2893 return nullptr;
2894 };
2895}
2896
Mike Dougherty4e6b3a32017-08-23 18:49:212897- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132898 handleContextMenu:(const web::ContextMenuParams&)params {
2899 // Prevent context menu from displaying for a tab which is no longer the
2900 // current one.
2901 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212902 return;
sdefresnee65fd872016-12-19 13:38:132903 }
2904
2905 // No custom context menu if no valid url is available in |params|.
2906 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212907 return;
sdefresnee65fd872016-12-19 13:38:132908 }
2909
2910 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:132911
stkhapuginc9eee7b2017-04-10 15:49:272912 _contextMenuCoordinator =
2913 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2914 params:params];
sdefresnee65fd872016-12-19 13:38:132915
2916 NSString* title = nil;
2917 ProceduralBlock action = nil;
2918
stkhapuginc9eee7b2017-04-10 15:49:272919 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132920 GURL link = params.link_url;
2921 bool isLink = link.is_valid();
2922 GURL imageUrl = params.src_url;
2923 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:192924 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:132925
2926 if (isLink) {
2927 if (link.SchemeIs(url::kJavaScriptScheme)) {
2928 // Open
2929 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2930 action = ^{
2931 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2932 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2933 };
2934 [_contextMenuCoordinator addItemWithTitle:title action:action];
2935 }
2936
2937 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192938 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132939
sdefresnee65fd872016-12-19 13:38:132940 // Open in New Tab.
2941 title = l10n_util::GetNSStringWithFixup(
2942 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2943 action = ^{
2944 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002945 // The "New Tab" item in the context menu opens a new tab in the current
2946 // browser state. |isOffTheRecord| indicates whether or not the current
2947 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132948 [weakSelf webPageOrderedOpen:link
2949 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002950 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132951 inBackground:YES
2952 appendTo:kCurrentTab];
2953 };
2954 [_contextMenuCoordinator addItemWithTitle:title action:action];
2955 if (!_isOffTheRecord) {
2956 // Open in Incognito Tab.
2957 title = l10n_util::GetNSStringWithFixup(
2958 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2959 action = ^{
2960 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2961 [weakSelf webPageOrderedOpen:link
2962 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132963 inIncognito:YES
2964 inBackground:NO
2965 appendTo:kCurrentTab];
2966 };
2967 [_contextMenuCoordinator addItemWithTitle:title action:action];
2968 }
olivierrobin51d4cf42017-01-17 13:32:352969 }
gambard65d69152017-03-23 17:44:222970 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352971 NSString* innerText = params.link_text;
2972 if ([innerText length] > 0) {
2973 // Add to reading list.
2974 title = l10n_util::GetNSStringWithFixup(
2975 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2976 action = ^{
2977 Record(ACTION_READ_LATER, isImage, isLink);
2978 [weakSelf addToReadingListURL:link title:innerText];
2979 };
2980 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532981 }
sdefresnee65fd872016-12-19 13:38:132982 }
2983 // Copy Link.
2984 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2985 action = ^{
2986 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282987 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:132988 };
2989 [_contextMenuCoordinator addItemWithTitle:title action:action];
2990 }
2991 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192992 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132993 // Save Image.
gambard98b4ddf2017-04-18 07:14:052994 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:132995 action = ^{
2996 Record(ACTION_SAVE_IMAGE, isImage, isLink);
2997 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
2998 };
2999 [_contextMenuCoordinator addItemWithTitle:title action:action];
3000 // Open Image.
3001 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
3002 action = ^{
3003 Record(ACTION_OPEN_IMAGE, isImage, isLink);
3004 [weakSelf loadURL:imageUrl
3005 referrer:referrer
3006 transition:ui::PAGE_TRANSITION_LINK
3007 rendererInitiated:YES];
3008 };
3009 [_contextMenuCoordinator addItemWithTitle:title action:action];
3010 // Open Image In New Tab.
3011 title = l10n_util::GetNSStringWithFixup(
3012 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
3013 action = ^{
3014 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
3015 [weakSelf webPageOrderedOpen:imageUrl
3016 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133017 inBackground:true
3018 appendTo:kCurrentTab];
3019 };
3020 [_contextMenuCoordinator addItemWithTitle:title action:action];
3021
3022 TemplateURLService* service =
3023 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103024 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133025 if (defaultURL && !defaultURL->image_url().empty() &&
3026 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3027 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3028 defaultURL->short_name());
3029 action = ^{
3030 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3031 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3032 };
3033 [_contextMenuCoordinator addItemWithTitle:title action:action];
3034 }
3035 }
3036
3037 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133038}
3039
eugenebutb739bdc2017-01-25 06:32:483040- (void)webState:(web::WebState*)webState
3041 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3042 // Display the action sheet with the arrow pointing at the top center of the
3043 // web contents.
sdefresne0452a9d2017-02-09 15:33:283044 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483045 UIView* view = webState->GetView();
3046 CGPoint dialogLocation =
3047 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283048 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033049 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173050 helper->PresentDialog(dialogLocation,
3051 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053052 handler(shouldContinue);
3053 }));
eugenebutb739bdc2017-01-25 06:32:483054}
3055
sdefresnee65fd872016-12-19 13:38:133056- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3057 (web::WebState*)webState {
3058 return _javaScriptDialogPresenter.get();
3059}
3060
eugenebut63232102017-01-19 16:19:403061- (void)webState:(web::WebState*)webState
3062 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3063 proposedCredential:(NSURLCredential*)proposedCredential
3064 completionHandler:(void (^)(NSString* username,
3065 NSString* password))handler {
3066 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3067 proposedCredential:proposedCredential
3068 webState:webState
3069 completionHandler:handler];
3070}
3071
Kurt Horimoto62e97c72017-11-03 19:51:473072#pragma mark - LegacyFullscreenControllerDelegate methods
sdefresnee65fd872016-12-19 13:38:133073
[email protected]a08e2bfb2017-11-24 19:16:453074- (void)redrawHeader {
3075 for (HeaderDefinition* header in self.headerViews) {
3076 [header.view setNeedsLayout];
3077 }
3078}
3079
sdefresnee65fd872016-12-19 13:38:133080- (CGFloat)headerOffset {
3081 if (IsIPadIdiom())
3082 return StatusBarHeight();
3083 return 0.0;
3084}
3085
stkhapugin952ecef2017-04-11 12:11:453086- (NSArray<HeaderDefinition*>*)headerViews {
3087 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:133088 if (![self isViewLoaded])
3089 return results;
3090
3091 if (!IsIPadIdiom()) {
sczs42f7f7482017-11-08 01:13:273092 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453093 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273094 definitionWithView:_toolbarCoordinator
3095 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453096 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243097 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453098 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133099 }
3100 } else {
edchinf5150c682017-09-18 02:50:033101 if (self.tabStripView) {
3102 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
3103 headerBehaviour:Hideable
3104 heightAdjustment:0.0
3105 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133106 }
sczs42f7f7482017-11-08 01:13:273107 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453108 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273109 definitionWithView:_toolbarCoordinator
3110 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453111 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243112 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453113 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133114 }
3115 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:453116 [results addObject:[HeaderDefinition
3117 definitionWithView:[_findBarController view]
3118 headerBehaviour:Overlap
3119 heightAdjustment:0.0
3120 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:133121 }
3122 }
stkhapugin952ecef2017-04-11 12:11:453123 return [results copy];
sdefresnee65fd872016-12-19 13:38:133124}
3125
3126- (UIView*)footerView {
3127 return _voiceSearchBar;
3128}
3129
3130- (CGFloat)headerHeight {
3131 return [self headerHeightForTab:[_model currentTab]];
3132}
3133
3134- (CGFloat)headerHeightForTab:(Tab*)tab {
3135 id nativeController = [self nativeControllerForTab:tab];
3136 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3137 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3138 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3139 // On iPhone, don't add any header height for ToolbarOwner native
3140 // controllers when they're displaying their own toolbar.
3141 return 0;
3142 }
3143
stkhapugin952ecef2017-04-11 12:11:453144 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133145
3146 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453147 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133148 if (header.view && header.behaviour == Hideable) {
3149 height += CGRectGetHeight([header.view frame]) -
3150 header.heightAdjustement - header.inset;
3151 }
3152 }
3153
3154 return height - StatusBarHeight();
3155}
3156
3157- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193158 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133159}
3160
3161- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453162 NSArray<HeaderDefinition*>* headers = [self headerViews];
3163 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133164 return 0.0;
3165
3166 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3167 // API documentation.
Sylvain Defresnef5d2d952017-11-14 11:15:313168 if (_insertedTabWasPrerenderedTab)
sdefresnee65fd872016-12-19 13:38:133169 return [self headerHeight];
3170
3171 UIView* topHeader = headers[0].view;
3172 return -(topHeader.frame.origin.y - [self headerOffset]);
3173}
3174
3175- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3176 UIView* footer = [self footerView];
3177 CGFloat headerHeight = [self headerHeight];
3178 if (!footer || headerHeight == 0)
3179 return 0.0;
3180
3181 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3182 CGFloat offset = headerOffset * footerHeight / headerHeight;
3183 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3184}
3185
Kurt Horimoto62e97c72017-11-03 19:51:473186- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:133187 headerAnimationCompleted:(BOOL)completed
3188 offset:(CGFloat)offset {
3189 if (completed)
justincohen04c27772016-12-21 20:16:593190 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133191}
3192
stkhapugin952ecef2017-04-11 12:11:453193- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133194 atOffset:(CGFloat)headerOffset {
3195 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453196 for (HeaderDefinition* header in headers) {
Justin Cohen9fe9ef672017-12-01 20:37:433197 CGFloat yOrigin = height - headerOffset - header.inset;
3198 // Make sure the toolbarView's constraints are also updated. Leaving the
3199 // -setFrame call to minimize changes in this CL -- otherwise the way
3200 // toolbar_view manages it's alpha changes would also need to be updated.
3201 // TODO(crbug.com/778822): This can be cleaned up when the new fullscreen
3202 // is enabled.
3203 if (IsSafeAreaCompatibleToolbarEnabled() &&
3204 header.view == _toolbarCoordinator.toolbarViewController.view) {
3205 self.toolbarOffsetConstraint.constant = yOrigin;
3206 }
sdefresnee65fd872016-12-19 13:38:133207 CGRect frame = [header.view frame];
Justin Cohen9fe9ef672017-12-01 20:37:433208 frame.origin.y = yOrigin;
sdefresnee65fd872016-12-19 13:38:133209 [header.view setFrame:frame];
3210 if (header.behaviour != Overlap)
3211 height += CGRectGetHeight(frame);
3212 }
3213}
3214
Kurt Horimoto62e97c72017-11-03 19:51:473215- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133216 drawHeaderViewFromOffset:(CGFloat)headerOffset
3217 animate:(BOOL)animate {
3218 if ([_sideSwipeController inSwipe])
3219 return;
3220
3221 CGRect footerFrame = CGRectZero;
3222 UIView* footer = nil;
3223 // Only animate the voice search bar if the tab is a voice search results tab.
3224 if ([_model currentTab].isVoiceSearchResultsTab) {
3225 footer = [self footerView];
3226 footerFrame = footer.frame;
3227 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3228 }
3229
stkhapugin952ecef2017-04-11 12:11:453230 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133231 void (^block)(void) = ^{
3232 [self setFramesForHeaders:headers atOffset:headerOffset];
3233 footer.frame = footerFrame;
3234 };
3235 void (^completion)(BOOL) = ^(BOOL finished) {
3236 [self fullScreenController:fullScreenController
3237 headerAnimationCompleted:finished
3238 offset:headerOffset];
3239 };
3240 if (animate) {
Kurt Horimoto62e97c72017-11-03 19:51:473241 [UIView
3242 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3243 delay:0.0
3244 options:UIViewAnimationOptionBeginFromCurrentState
3245 animations:block
3246 completion:completion];
sdefresnee65fd872016-12-19 13:38:133247 } else {
3248 block();
3249 completion(YES);
3250 }
3251}
3252
Kurt Horimoto62e97c72017-11-03 19:51:473253- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133254 drawHeaderViewFromOffset:(CGFloat)headerOffset
3255 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3256 changeTopContentPadding:(BOOL)changeTopContentPadding
3257 scrollingToOffset:(CGFloat)contentOffset {
3258 DCHECK(webViewProxy);
3259 if ([_sideSwipeController inSwipe])
3260 return;
3261
3262 CGRect footerFrame;
3263 UIView* footer = nil;
3264 // Only animate the voice search bar if the tab is a voice search results tab.
3265 if ([_model currentTab].isVoiceSearchResultsTab) {
3266 footer = [self footerView];
3267 footerFrame = footer.frame;
3268 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3269 }
3270
stkhapugin952ecef2017-04-11 12:11:453271 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133272 void (^block)(void) = ^{
3273 [self setFramesForHeaders:headers atOffset:headerOffset];
3274 footer.frame = footerFrame;
3275 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3276 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3277 if (changeTopContentPadding)
3278 webViewProxy.topContentPadding = contentOffset;
3279 };
3280 void (^completion)(BOOL) = ^(BOOL finished) {
3281 [self fullScreenController:fullScreenController
3282 headerAnimationCompleted:finished
3283 offset:headerOffset];
3284 };
3285
Kurt Horimoto62e97c72017-11-03 19:51:473286 [UIView
3287 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3288 delay:0.0
3289 options:UIViewAnimationOptionBeginFromCurrentState
3290 animations:block
3291 completion:completion];
sdefresnee65fd872016-12-19 13:38:133292}
3293
3294#pragma mark - VoiceSearchBarOwner
3295
3296- (id<VoiceSearchBar>)voiceSearchBar {
3297 return _voiceSearchBar;
3298}
3299
3300#pragma mark - Install OverScrollActionController method.
3301- (void)setOverScrollActionControllerToStaticNativeContent:
3302 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343303 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133304 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173305 [[OverscrollActionsController alloc]
3306 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133307 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053308 OverscrollStyle style = _isOffTheRecord
3309 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3310 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133311 controller.style = style;
3312 nativeContent.overscrollActionsController = controller;
3313 }
3314}
3315
3316#pragma mark - OverscrollActionsControllerDelegate methods.
3317
3318- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053319 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133320 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053321 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383322 [self.dispatcher
3323 openNewTab:[OpenNewTabCommand
3324 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133325 break;
rohitrao922b7111c2017-01-03 14:31:053326 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243327 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133328 break;
Kurt Horimoto4ce19322017-11-28 19:10:473329 case OverscrollAction::REFRESH:
3330 [self reload];
sdefresnee65fd872016-12-19 13:38:133331 break;
rohitrao922b7111c2017-01-03 14:31:053332 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133333 NOTREACHED();
3334 break;
3335 }
3336}
3337
3338- (BOOL)shouldAllowOverscrollActions {
3339 return YES;
3340}
3341
3342- (UIView*)headerView {
sczs42f7f7482017-11-08 01:13:273343 return _toolbarCoordinator.toolbarViewController.view;
sdefresnee65fd872016-12-19 13:38:133344}
3345
3346- (UIView*)toolbarSnapshotView {
sczs42f7f7482017-11-08 01:13:273347 return [_toolbarCoordinator.toolbarViewController.view
3348 snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133349}
3350
3351- (CGFloat)overscrollActionsControllerHeaderInset:
3352 (OverscrollActionsController*)controller {
3353 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3354 return [self headerHeight];
3355 else
3356 return 0;
3357}
3358
3359- (CGFloat)overscrollHeaderHeight {
3360 return [self headerHeight] + StatusBarHeight();
3361}
3362
3363#pragma mark - TabSnapshottingDelegate methods.
3364
3365- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3366 CGRect pageContentArea = _contentArea.bounds;
3367 if ([_model webUsageEnabled])
3368 pageContentArea = tab.view.bounds;
3369 CGFloat headerHeight = [self headerHeightForTab:tab];
3370 id nativeController = [self nativeControllerForTab:tab];
3371 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3372 headerHeight += [nativeController toolbarHeight];
3373 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3374 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3375}
3376
3377#pragma mark - NewTabPageObserver methods.
3378
3379- (void)selectedPanelDidChange {
3380 [self updateToolbar];
3381}
3382
3383#pragma mark - CRWNativeContentProvider methods
3384
3385- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3386 withError:(NSError*)error
3387 isPost:(BOOL)isPost {
3388 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173389 [[ErrorPageContent alloc] initWithLoader:self
3390 browserState:self.browserState
3391 url:url
3392 error:error
3393 isPost:isPost
3394 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133395 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3396 return errorPageContent;
3397}
3398
3399- (BOOL)hasControllerForURL:(const GURL&)url {
Marti Wong64481ec2017-10-31 03:38:003400 base::StringPiece host = url.host_piece();
olivierrobin5c861c22017-04-07 15:56:453401 if (host == kChromeUIOfflineHost) {
3402 // Only allow offline URL that are fully specified.
3403 return reading_list::IsOfflineURLValid(
3404 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3405 }
sdefresnee65fd872016-12-19 13:38:133406
Justin Cohen8679e852017-08-14 16:35:253407 if (host == kChromeUIBookmarksHost) {
Marti Wong64481ec2017-10-31 03:38:003408 return IsBookmarksHostEnabled();
Justin Cohen8679e852017-08-14 16:35:253409 }
3410
3411 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133412}
3413
olivierrobind43eecb2017-01-27 20:35:263414- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3415 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133416 DCHECK(url.SchemeIs(kChromeUIScheme));
3417
3418 id<CRWNativeContent> nativeController = nil;
Marti Wong64481ec2017-10-31 03:38:003419 base::StringPiece url_host = url.host_piece();
Justin Cohen49715952017-08-22 14:12:193420 if (url_host == kChromeUINewTabHost ||
Marti Wong64481ec2017-10-31 03:38:003421 (url_host == kChromeUIBookmarksHost && IsBookmarksHostEnabled())) {
Gauthier Ambardd8890452017-09-29 12:07:463422 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3423 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3424 if (@available(iOS 11.0, *)) {
3425 safeAreaInset = self.view.safeAreaInsets;
3426 }
3427 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3428
sdefresnee65fd872016-12-19 13:38:133429 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173430 [[NewTabPageController alloc] initWithUrl:url
3431 loader:self
sczsf1620e52017-10-02 22:54:463432 focuser:_toolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173433 ntpObserver:self
3434 browserState:_browserState
3435 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463436 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453437 tabModel:_model
justincohen75011c32017-04-28 16:31:393438 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463439 dispatcher:self.dispatcher
3440 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133441 pageController.swipeRecognizerProvider = self.sideSwipeController;
3442
3443 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233444 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133445
Marti Wong64481ec2017-10-31 03:38:003446 if (IsBookmarksHostEnabled()) {
sdefresnee65fd872016-12-19 13:38:133447 // New Tab Page can have multiple panels. Each panel is addressable
3448 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3449 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3450 // the Bookmark Manager, etc.
3451 // The utility functions NewTabPage::IdentifierFromFragment() and
3452 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3453 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3454 // without changing the URL since the URL may be chrome://bookmarks/#123.
3455 // If the URL is chrome://newtab/, pre-select the panel based on the
3456 // #fragment.
3457 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233458 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133459 : NewTabPage::IdentifierFromFragment(url.ref());
3460 }
3461 [pageController selectPanel:panelType];
3462 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453463 } else if (url_host == kChromeUIOfflineHost &&
3464 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133465 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173466 [[OfflinePageNativeContent alloc] initWithLoader:self
3467 browserState:_browserState
3468 webState:webState
3469 URL:url];
sdefresnee65fd872016-12-19 13:38:133470 [self setOverScrollActionControllerToStaticNativeContent:
3471 staticNativeController];
3472 nativeController = staticNativeController;
3473 } else if (url_host == kChromeUIExternalFileHost) {
3474 // Return an instance of the |ExternalFileController| only if the file is
3475 // still in the sandbox.
3476 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3477 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173478 nativeController =
3479 [[ExternalFileController alloc] initWithURL:url
3480 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133481 }
peterlaurens44615d02017-05-23 20:23:093482 } else if (url_host == kChromeUICrashHost) {
3483 // There is no native controller for kChromeUICrashHost, it is instead
3484 // handled as any other renderer crash by the SadTabTabHelper.
3485 // nativeController must be set to nil to prevent defaulting to a
3486 // PageNotAvailableController.
3487 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133488 } else {
3489 DCHECK(![self hasControllerForURL:url]);
3490 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173491 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133492 }
3493 // If a native controller is vended before its tab is added to the tab model,
3494 // use the temporary key and add it under the new tab's tabId in the
3495 // TabModelObserver callback. This happens:
3496 // - when there is no current tab (occurs when vending the NTP controller for
3497 // the first tab that is opened),
3498 // - when the current tab's url doesn't match |url| (occurs when a native
3499 // controller is opened in a new tab)
3500 // - when the current tab's url matches |url| and there is already a native
3501 // controller of the appropriate type vended to it (occurs when a native
3502 // controller is opened in a new tab from a tab with a matching URL, e.g.
3503 // opening an NTP when an NTP is already displayed in the current tab).
3504 // For normal page loads, history navigations, tab restorations, and crash
3505 // recoveries, the tab will already exist in the tab model and the tabId can
3506 // be used as the native controller key.
3507 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3508 // that native controllers vended here always correspond to the current tab.
3509 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193510 if (!currentTab.webState ||
3511 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443512 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133513 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443514 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133515 }
sdefresnee65fd872016-12-19 13:38:133516 return nativeController;
3517}
3518
3519- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443520 id nativeController = tab.webController.nativeController;
3521 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133522}
3523
3524#pragma mark - DialogPresenterDelegate methods
3525
3526- (void)dialogPresenter:(DialogPresenter*)presenter
3527 willShowDialogForWebState:(web::WebState*)webState {
3528 for (Tab* iteratedTab in self.tabModel) {
3529 if ([iteratedTab webState] == webState) {
3530 self.tabModel.currentTab = iteratedTab;
3531 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3532 break;
3533 }
3534 }
3535}
3536
3537#pragma mark - Context menu methods
3538
3539- (void)searchByImageAtURL:(const GURL&)url
3540 referrer:(const web::Referrer)referrer {
3541 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273542 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113543 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173544 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3545 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113546 DCHECK(data);
3547 dispatch_async(dispatch_get_main_queue(), ^{
3548 [weakSelf searchByImageData:data atURL:image_source_url];
3549 });
3550 };
3551 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133552 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3553 web::PolicyForNavigation(url, referrer));
3554}
3555
3556- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3557 NSData* imageData = data;
3558 UIImage* image = [UIImage imageWithData:imageData];
3559 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3560 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3561 // kSearchByImageMaxImageHeight).
3562 if (image &&
3563 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3564 (image.size.width > kSearchByImageMaxImageWidth ||
3565 image.size.height > kSearchByImageMaxImageHeight)) {
3566 CGSize newImageSize =
3567 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3568 image = [image gtm_imageByResizingToSize:newImageSize
3569 preserveAspectRatio:YES
3570 trimToFit:NO];
3571 imageData = UIImageJPEGRepresentation(image, 1.0);
3572 }
3573
3574 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3575 std::string byteString(bytes, [imageData length]);
3576
3577 TemplateURLService* templateUrlService =
3578 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103579 const TemplateURL* defaultURL =
3580 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133581 DCHECK(!defaultURL->image_url().empty());
3582 DCHECK(defaultURL->image_url_ref().IsValid(
3583 templateUrlService->search_terms_data()));
3584 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3585 search_args.image_url = imageURL;
3586 search_args.image_thumbnail_content = byteString;
3587
3588 // Generate the URL and populate |post_content| with the content type and
3589 // HTTP body for the request.
3590 TemplateURLRef::PostContent post_content;
3591 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3592 search_args, templateUrlService->search_terms_data(), &post_content));
3593 [self addSelectedTabWithURL:result
3594 postData:&post_content
3595 transition:ui::PAGE_TRANSITION_TYPED];
3596}
3597
3598- (void)saveImageAtURL:(const GURL&)url
3599 referrer:(const web::Referrer&)referrer {
3600 DCHECK(url.is_valid());
3601
gambard9efce7a2017-02-09 18:53:173602 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3603 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113604 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133605
gambardbbf85c42017-06-29 11:15:343606 if ([data length] == 0) {
3607 [self displayPrivacyErrorAlertOnMainQueue:
3608 l10n_util::GetNSString(
3609 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3610 return;
3611 }
3612
gambard9efce7a2017-02-09 18:53:173613 base::FilePath::StringType extension;
3614
3615 bool extensionSuccess =
3616 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3617 if (!extensionSuccess || extension.length() == 0) {
3618 extension = "png";
3619 }
3620
3621 NSString* fileExtension =
3622 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3623 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113624 };
3625 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133626 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3627 web::PolicyForNavigation(url, referrer));
3628}
3629
gambard9efce7a2017-02-09 18:53:173630- (void)managePermissionAndSaveImage:(NSData*)data
3631 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133632 switch ([PHPhotoLibrary authorizationStatus]) {
3633 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173634 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133635 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3636 // Call -saveImage again to check if chrome needs to display an error or
3637 // saves the image.
3638 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173639 [self managePermissionAndSaveImage:data
3640 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133641 }];
3642 break;
stkhapuginf58b10d02017-04-10 13:36:173643 }
sdefresnee65fd872016-12-19 13:38:133644
3645 // The application doesn't have permission to access photo and the user
3646 // cannot grant it.
3647 case PHAuthorizationStatusRestricted:
3648 [self displayPrivacyErrorAlertOnMainQueue:
3649 l10n_util::GetNSString(
3650 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3651 break;
3652
3653 // The application doesn't have permission to access photo and the user
3654 // can grant it.
3655 case PHAuthorizationStatusDenied:
3656 [self displayImageErrorAlertWithSettingsOnMainQueue];
3657 break;
3658
3659 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243660 default:
3661 __weak BrowserViewController* weakSelf = self;
3662 [self saveImage:data
3663 withFileExtension:fileExtension
3664 completion:^(BOOL success, NSError* error) {
3665 [weakSelf finishSavingImageWithError:error];
3666 }];
sdefresnee65fd872016-12-19 13:38:133667 break;
sdefresnee65fd872016-12-19 13:38:133668 }
3669}
3670
Sylvain Defresnefd3ecf22017-07-12 18:47:243671- (void)saveImage:(NSData*)data
3672 withFileExtension:(NSString*)fileExtension
3673 completion:(void (^)(BOOL, NSError*))completion {
3674 base::PostTaskWithTraits(
3675 FROM_HERE,
3676 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3677 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3678 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373679 base::AssertBlockingAllowed();
sdefresnee65fd872016-12-19 13:38:133680
Sylvain Defresnefd3ecf22017-07-12 18:47:243681 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3682 stringByAppendingString:fileExtension];
3683 NSURL* fileURL = [NSURL
3684 fileURLWithPath:[NSTemporaryDirectory()
3685 stringByAppendingPathComponent:fileName]];
3686 NSError* error = nil;
3687 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3688 if (error) {
3689 if (completion)
3690 completion(NO, error);
3691 return;
3692 }
sdefresnee65fd872016-12-19 13:38:133693
Sylvain Defresnefd3ecf22017-07-12 18:47:243694 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3695 [PHAssetChangeRequest
3696 creationRequestForAssetFromImageAtFileURL:fileURL];
3697 }
3698 completionHandler:^(BOOL success, NSError* error) {
3699 base::PostTaskWithTraits(
3700 FROM_HERE,
3701 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3702 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3703 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373704 base::AssertBlockingAllowed();
Sylvain Defresnefd3ecf22017-07-12 18:47:243705 if (completion)
3706 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133707
Sylvain Defresnefd3ecf22017-07-12 18:47:243708 // Cleanup the temporary file.
3709 NSError* deleteFileError = nil;
3710 [[NSFileManager defaultManager]
3711 removeItemAtURL:fileURL
3712 error:&deleteFileError];
3713 }));
3714 }];
3715 }));
sdefresnee65fd872016-12-19 13:38:133716}
3717
3718- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3719 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3720 BOOL canGoToSetting =
3721 [[UIApplication sharedApplication] canOpenURL:settingURL];
3722 if (canGoToSetting) {
3723 dispatch_async(dispatch_get_main_queue(), ^{
3724 [self displayImageErrorAlertWithSettings:settingURL];
3725 });
3726 } else {
3727 [self displayPrivacyErrorAlertOnMainQueue:
3728 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3729 }
3730}
3731
3732- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3733 // Dismiss current alert.
3734 [_alertCoordinator stop];
3735
3736 NSString* title =
3737 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3738 NSString* message = l10n_util::GetNSString(
3739 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3740
stkhapuginc9eee7b2017-04-10 15:49:273741 _alertCoordinator =
3742 [[AlertCoordinator alloc] initWithBaseViewController:self
3743 title:title
3744 message:message];
sdefresnee65fd872016-12-19 13:38:133745
3746 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3747 action:nil
3748 style:UIAlertActionStyleCancel];
3749
3750 [_alertCoordinator
3751 addItemWithTitle:l10n_util::GetNSString(
3752 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3753 action:^{
3754 OpenUrlWithCompletionHandler(settingURL, nil);
3755 }
3756 style:UIAlertActionStyleDefault];
3757
3758 [_alertCoordinator start];
3759}
3760
3761- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3762 dispatch_async(dispatch_get_main_queue(), ^{
3763 NSString* title =
3764 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3765 [self showErrorAlertWithStringTitle:title message:errorContent];
3766 });
3767}
3768
3769// This callback is triggered when the image is effectively saved onto the photo
3770// album, or if the save failed for some reason.
3771- (void)finishSavingImageWithError:(NSError*)error {
3772 // Was there an error?
3773 if (error) {
3774 // Saving photo failed even though user has granted access to Photos.
3775 // Display the error information from the NSError object for user.
3776 NSString* errorMessage = [NSString
3777 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3778 [error domain], [error code]];
3779 // This code may be execute outside of the main thread. Make sure to display
3780 // the error on the main thread.
3781 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3782 } else {
3783 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3784 // the photo application. The current behaviour is to create the photo there
3785 // but not providing any link to it is suboptimal. That's what Safari is
3786 // doing, and what the PM want, but it doesn't make it right.
3787 }
3788}
3789
sdefresnee65fd872016-12-19 13:38:133790#pragma mark - Showing popups
3791
sdefresnee65fd872016-12-19 13:38:133792- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133793 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3794
3795 ReadingListModel* readingModel =
3796 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013797 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3798 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133799
pinkerton07e27842017-03-02 15:29:023800 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
edchineeb4d422017-10-02 17:39:363801 [self showSnackbar:l10n_util::GetNSString(
3802 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133803}
3804
3805#pragma mark - Keyboard commands management
3806
3807- (BOOL)shouldRegisterKeyboardCommands {
3808 if ([self presentedViewController])
3809 return NO;
3810
3811 if (_voiceSearchController && _voiceSearchController->IsVisible())
3812 return NO;
3813
3814 // If there is no first responder, try to make the webview the first
3815 // responder.
3816 if (!GetFirstResponder()) {
Eugene But08be7d02017-10-02 15:49:303817 web::WebState* webState = _model.currentTab.webState;
3818 if (webState)
3819 [webState->GetWebViewProxy() becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133820 }
3821
3822 return YES;
3823}
3824
3825- (KeyCommandsProvider*)keyCommandsProvider {
3826 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273827 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133828 }
stkhapuginc9eee7b2017-04-10 15:49:273829 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133830}
3831
3832#pragma mark - KeyCommandsPlumbing
3833
3834- (BOOL)isOffTheRecord {
3835 return _isOffTheRecord;
3836}
3837
3838- (NSUInteger)tabsCount {
3839 return [_model count];
3840}
3841
lpromero47ea8862017-01-13 17:51:063842- (BOOL)canGoBack {
3843 return [_model currentTab].canGoBack;
3844}
3845
3846- (BOOL)canGoForward {
3847 return [_model currentTab].canGoForward;
3848}
3849
sdefresnee65fd872016-12-19 13:38:133850- (void)focusTabAtIndex:(NSUInteger)index {
3851 if ([_model count] > index) {
3852 [_model setCurrentTab:[_model tabAtIndex:index]];
3853 }
3854}
3855
3856- (void)focusNextTab {
3857 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3858 NSInteger modelCount = [_model count];
3859 if (currentTabIndex < modelCount - 1) {
3860 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3861 [_model setCurrentTab:nextTab];
3862 } else {
3863 [_model setCurrentTab:[_model tabAtIndex:0]];
3864 }
3865}
3866
3867- (void)focusPreviousTab {
3868 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3869 if (currentTabIndex > 0) {
3870 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3871 [_model setCurrentTab:previousTab];
3872 } else {
3873 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3874 [_model setCurrentTab:lastTab];
3875 }
3876}
3877
3878- (void)reopenClosedTab {
3879 sessions::TabRestoreService* const tabRestoreService =
3880 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3881 if (!tabRestoreService || tabRestoreService->entries().empty())
3882 return;
3883
3884 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3885 tabRestoreService->entries().front();
3886 // Only handle the TAB type.
3887 if (entry->type != sessions::TabRestoreService::TAB)
3888 return;
3889
Mark Cogandfcdea72017-07-18 13:47:383890 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133891 TabRestoreServiceDelegateImplIOS* const delegate =
3892 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3893 _browserState);
3894 tabRestoreService->RestoreEntryById(delegate, entry->id,
3895 WindowOpenDisposition::CURRENT_TAB);
3896}
3897
3898- (void)focusOmnibox {
sczsf1620e52017-10-02 22:54:463899 [_toolbarCoordinator focusOmnibox];
sdefresnee65fd872016-12-19 13:38:133900}
3901
3902#pragma mark - UIResponder
3903
3904- (NSArray*)keyCommands {
3905 if (![self shouldRegisterKeyboardCommands]) {
3906 return nil;
3907 }
3908 return [self.keyCommandsProvider
3909 keyCommandsForConsumer:self
edchin8e4cfe032017-10-25 13:25:543910 baseViewController:self
Mark Cogan6c58ea92017-07-06 13:08:243911 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133912 editingText:![self isFirstResponder]];
3913}
3914
3915#pragma mark -
3916
3917// Induce an intentional crash in the browser process.
3918- (void)induceBrowserCrash {
3919 CHECK(false);
3920 // Call another function, so that the above CHECK can't be tail-call
3921 // optimized. This ensures that this method's name will show up in the stack
3922 // for easier identification.
3923 CHECK(true);
3924}
3925
3926- (void)loadURL:(const GURL&)url
3927 referrer:(const web::Referrer&)referrer
3928 transition:(ui::PageTransition)transition
3929 rendererInitiated:(BOOL)rendererInitiated {
3930 [[OmniboxGeolocationController sharedInstance]
3931 locationBarDidSubmitURL:url
3932 transition:transition
3933 browserState:_browserState];
3934
3935 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3936 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3937 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3938 }
3939
3940 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243941 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133942 // restart.
3943 if (url.host() == kChromeUIBrowserCrashHost) {
3944 [self induceBrowserCrash];
3945 // In debug the app can continue working even after the CHECK. Adding a
3946 // return avoids the crash url to be added to the history.
3947 return;
3948 }
3949
Danyao Wang85389a82017-10-25 18:56:273950 bool typed_or_generated_transition =
3951 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
3952 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
3953
Rohit Rao44f204302017-08-10 14:49:543954 PrerenderService* prerenderService =
3955 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3956 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593957 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543958 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593959 DCHECK(newWebState);
3960
sdefresnee65fd872016-12-19 13:38:133961 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593962 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133963 DCHECK(oldTab);
3964 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593965
kkhorimotod804c5732017-03-15 23:44:523966 bool canPruneItems =
3967 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593968
kkhorimotod804c5732017-03-15 23:44:523969 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523970 [newTab navigationManager]->CopyStateFromAndPrune(
3971 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593972
Sylvain Defresnef5d2d952017-11-14 11:15:313973 // Set _insertedTabWasPrerenderedTab to YES while the Tab is inserted
3974 // so that the correct toolbar height is used and animation are played.
3975 _insertedTabWasPrerenderedTab = YES;
sdefresne2c600c52017-04-04 16:49:593976 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3977 std::move(newWebState));
Sylvain Defresnef5d2d952017-11-14 11:15:313978 _insertedTabWasPrerenderedTab = NO;
sdefresnee65fd872016-12-19 13:38:133979
Danyao Wang85389a82017-10-25 18:56:273980 if (typed_or_generated_transition) {
3981 LoadTimingTabHelper::FromWebState(newTab.webState)
3982 ->DidPromotePrerenderTab();
3983 }
sdefresnee65fd872016-12-19 13:38:133984
sdefresne2f7781c2017-03-02 19:12:463985 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133986 return;
3987 }
3988 }
3989
3990 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:543991 if (prerenderService) {
3992 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:133993 }
3994
sdefresnee65fd872016-12-19 13:38:133995 // Some URLs are not allowed while in incognito. If we are in incognito and
3996 // load a disallowed URL, instead create a new tab not in the incognito state.
3997 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
3998 [self webPageOrderedOpen:url
3999 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134000 inIncognito:NO
4001 inBackground:NO
4002 appendTo:kCurrentTab];
4003 return;
4004 }
4005
Danyao Wang85389a82017-10-25 18:56:274006 if (typed_or_generated_transition) {
4007 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
4008 ->DidInitiatePageLoad();
4009 }
4010
mrefaata84d5a02017-06-08 17:13:294011 // If this is a reload initiated from the omnibox.
4012 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
4013 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
4014 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
4015 [[_model currentTab] navigationManager]->Reload(
4016 web::ReloadType::NORMAL, true /* check_for_repost */);
4017 return;
4018 }
4019
sdefresnee65fd872016-12-19 13:38:134020 web::NavigationManager::WebLoadParams params(urlToLoad);
4021 params.referrer = referrer;
4022 params.transition_type = transition;
4023 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104024 Tab* currentTab = [_model currentTab];
4025 DCHECK(currentTab);
4026 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4027 currentTab.navigationManager->LoadURLWithParams(params);
4028 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4029 // to be animated on screen because the transition animator will handle the
4030 // animations. When a Tab stops being a voice search Tab, the voice search
4031 // bar should be animated away.
4032 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4033 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134034}
4035
4036- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544037 PrerenderService* prerenderService =
4038 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4039 if (prerenderService) {
4040 prerenderService->CancelPrerender();
4041 }
sdefresnee65fd872016-12-19 13:38:134042 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:184043 if ([self currentWebState])
4044 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134045}
4046
4047- (web::WebState*)currentWebState {
4048 return [[_model currentTab] webState];
4049}
4050
sdefresnee65fd872016-12-19 13:38:134051// Load a new URL on a new page/tab.
4052- (void)webPageOrderedOpen:(const GURL&)URL
4053 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134054 inBackground:(BOOL)inBackground
4055 appendTo:(OpenPosition)appendTo {
4056 Tab* adjacentTab = nil;
4057 if (appendTo == kCurrentTab)
4058 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354059 [_model insertTabWithURL:URL
4060 referrer:referrer
4061 transition:ui::PAGE_TRANSITION_LINK
4062 opener:adjacentTab
4063 openedByDOM:NO
4064 atIndex:TabModelConstants::kTabPositionAutomatically
4065 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134066}
4067
4068- (void)webPageOrderedOpen:(const GURL&)url
4069 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134070 inIncognito:(BOOL)inIncognito
4071 inBackground:(BOOL)inBackground
4072 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004073 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234074 // feature_engagement::Tracker based on |inIncognito|.
4075 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004076
sdefresnee65fd872016-12-19 13:38:134077 if (inIncognito == _isOffTheRecord) {
4078 [self webPageOrderedOpen:url
4079 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134080 inBackground:inBackground
4081 appendTo:appendTo];
4082 return;
4083 }
4084 // When sending an open command that switches modes, ensure the tab
4085 // ends up appended to the end of the model, not just next to what is
4086 // currently selected in the other mode. This is done with the |append|
4087 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274088 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134089 initWithURL:url
4090 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134091 inIncognito:inIncognito
4092 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274093 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154094 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134095}
4096
4097- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054098 WebStateList* webStateList = [_model webStateList];
4099 webStateList->ReplaceWebStateAt(
4100 webStateList->active_index(),
4101 session_util::CreateWebStateWithNavigationEntries(
4102 [_model browserState], sessionTab->current_navigation_index,
4103 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134104}
4105
4106- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:434107 DCHECK(javascript);
4108 javascript = [javascript stringByRemovingPercentEncoding];
4109 web::WebState* webState = [[_model currentTab] webState];
4110 if (webState) {
4111 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
4112 }
sdefresnee65fd872016-12-19 13:38:134113}
4114
4115#pragma mark - WebToolbarDelegate methods
4116
Gauthier Ambard45963ce22017-11-17 15:49:114117- (void)locationBarDidBecomeFirstResponder {
sdefresnee65fd872016-12-19 13:38:134118 if (_locationBarHasFocus)
4119 return; // TODO(crbug.com/244366): This should not be necessary.
4120 _locationBarHasFocus = YES;
4121 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524122 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134123 object:nil];
4124 [_sideSwipeController setEnabled:NO];
4125 if ([[_model currentTab].webController wantsKeyboardShield]) {
4126 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
4127 [_typingShield setAlpha:0.0];
4128 [_typingShield setHidden:NO];
4129 [UIView animateWithDuration:0.3
4130 animations:^{
4131 [_typingShield setAlpha:1.0];
4132 }];
4133 }
4134 [[OmniboxGeolocationController sharedInstance]
4135 locationBarDidBecomeFirstResponder:_browserState];
4136}
4137
Gauthier Ambard45963ce22017-11-17 15:49:114138- (void)locationBarDidResignFirstResponder {
sdefresnee65fd872016-12-19 13:38:134139 if (!_locationBarHasFocus)
4140 return; // TODO(crbug.com/244366): This should not be necessary.
4141 _locationBarHasFocus = NO;
4142 [_sideSwipeController setEnabled:YES];
4143 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524144 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134145 object:nil];
4146 [UIView animateWithDuration:0.3
4147 animations:^{
4148 [_typingShield setAlpha:0.0];
4149 }
4150 completion:^(BOOL finished) {
4151 // This can happen if one quickly resigns the omnibox and then taps
4152 // on the omnibox again during this animation. If the animation is
4153 // interrupted and the toolbar controller is first responder, it's safe
4154 // to assume the |_typingShield| shouldn't be hidden here.
sczsf1620e52017-10-02 22:54:464155 if (!finished && [_toolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134156 return;
4157 [_typingShield setHidden:YES];
4158 }];
4159 [[OmniboxGeolocationController sharedInstance]
4160 locationBarDidResignFirstResponder:_browserState];
4161
4162 // If a load was cancelled by an omnibox edit, but nothing is loading when
4163 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4164 if (_locationBarEditCancelledLoad) {
4165 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294166
4167 web::WebState* webState = [_model currentTab].webState;
4168 if (!_toolbarModelIOS->IsLoading() && webState)
4169 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4170 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134171 }
4172}
4173
Gauthier Ambard45963ce22017-11-17 15:49:114174- (void)locationBarBeganEdit {
sdefresnee65fd872016-12-19 13:38:134175 // On handsets, if a page is currently loading it should be stopped.
4176 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354177 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134178 _locationBarEditCancelledLoad = YES;
4179 }
4180}
4181
sdefresnee65fd872016-12-19 13:38:134182- (ToolbarModelIOS*)toolbarModelIOS {
4183 return _toolbarModelIOS.get();
4184}
4185
sdefresnee65fd872016-12-19 13:38:134186- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4187 CGRect frame = [_contentArea frame];
4188 if (!fullScreen) {
4189 // Changing the origin here is unnecessary, it's set in page_animation_util.
4190 frame.size.height -= [self headerHeight];
4191 }
4192
4193 CGFloat shortAxis = frame.size.width;
4194 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524195 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134196 CGFloat aspectRatio = frame.size.height / frame.size.width;
4197 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4198 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4199 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4200 CGRect cardFrame = {frame.origin, cardSize};
4201
4202 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174203 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134204 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4205 : CardCloseButtonSide::LEADING;
4206 [_contentArea addSubview:card];
4207 return card;
4208}
4209
Peter Laurense0b80f12017-11-21 07:52:404210#pragma mark - Tools Menu Configuration delegate
4211
4212- (void)prepareForToolsMenuPresentationByCoordinator:
4213 (ToolsMenuCoordinator*)coordinator {
4214 DCHECK(_browserState);
4215 DCHECK(self.visible || self.dismissingModal);
4216
4217 // Dismiss the omnibox (if open).
4218 [_toolbarCoordinator cancelOmniboxEdit];
4219 // Dismiss the soft keyboard (if open).
4220 [[_model currentTab].webController dismissKeyboard];
4221 // Dismiss Find in Page focus.
4222 [self updateFindBar:NO shouldFocus:NO];
4223
4224 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4225 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4226 }
4227}
4228
4229- (ToolsMenuConfiguration*)menuConfigurationForToolsMenuCoordinator:
4230 (ToolsMenuCoordinator*)coordinator {
4231 ToolsMenuConfiguration* configuration =
4232 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4233 baseViewController:self];
4234 configuration.requestStartTime = [NSDate date].timeIntervalSinceReferenceDate;
4235
4236 if ([_model count] == 0)
4237 [configuration setNoOpenedTabs:YES];
4238
4239 if (_isOffTheRecord)
4240 [configuration setInIncognito:YES];
4241
4242 if (!_readingListMenuNotifier) {
4243 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4244 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4245 _browserState)];
4246 }
4247
4248 feature_engagement::Tracker* engagementTracker =
4249 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4250 if (engagementTracker->ShouldTriggerHelpUI(
4251 feature_engagement::kIPHBadgedReadingListFeature)) {
4252 [configuration setShowReadingListNewBadge:YES];
4253 [configuration setEngagementTracker:engagementTracker];
4254 }
4255 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4256
4257 [configuration setUserAgentType:self.userAgentType];
4258
4259 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4260 [configuration setHighlightNewIncognitoTabCell:YES];
4261 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4262 }
4263
4264 return configuration;
4265}
4266
4267- (BOOL)shouldHighlightBookmarkButtonForToolsMenuCoordinator:
4268 (ToolsMenuCoordinator*)coordinator {
4269 return [_model currentTab] ? _toolbarModelIOS->IsCurrentTabBookmarked() : NO;
4270}
4271
4272- (BOOL)shouldShowFindBarForToolsMenuCoordinator:
4273 (ToolsMenuCoordinator*)coordinator {
4274 return [_model currentTab] ? self.canShowFindBar : NO;
4275}
4276
4277- (BOOL)shouldShowShareMenuForToolsMenuCoordinator:
4278 (ToolsMenuCoordinator*)coordinator {
4279 return [_model currentTab] ? self.canShowShareMenu : NO;
4280}
4281
4282- (BOOL)isTabLoadingForToolsMenuCoordinator:(ToolsMenuCoordinator*)coordinator {
4283 return ([_model currentTab] && !IsIPadIdiom()) ? _toolbarModelIOS->IsLoading()
4284 : NO;
4285}
4286
Mark Cogan6ebbde02017-07-07 12:50:134287#pragma mark - BrowserCommands
4288
4289- (void)goBack {
4290 [[_model currentTab] goBack];
4291}
4292
4293- (void)goForward {
4294 [[_model currentTab] goForward];
4295}
4296
Mark Coganb9aac6432017-07-07 13:26:354297- (void)stopLoading {
4298 [_model currentTab].webState->Stop();
4299}
4300
4301- (void)reload {
4302 web::WebState* webState = [_model currentTab].webState;
4303 if (webState) {
4304 // |check_for_repost| is true because the reload is explicitly initiated
4305 // by the user.
4306 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4307 true /* check_for_repost */);
4308 }
4309}
4310
Mark Cogan8e791022017-07-10 09:55:354311- (void)bookmarkPage {
4312 [self initializeBookmarkInteractionController];
4313 [_bookmarkInteractionController
4314 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064315 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354316}
4317
Mark Cogandfcdea72017-07-18 13:47:384318- (void)openNewTab:(OpenNewTabCommand*)command {
4319 if (self.isOffTheRecord != command.incognito) {
edchin3ab78ff2017-11-13 19:13:144320 // Must take a snapshot of the tab before we switch the incognito mode
4321 // because the currentTab will change after the switch.
4322 Tab* currentTab = [_model currentTab];
4323 if (currentTab) {
4324 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4325 }
Mark Cogandfcdea72017-07-18 13:47:384326 // Not for this browser state, send it on its way.
4327 [self.dispatcher switchModesAndOpenNewTab:command];
4328 return;
4329 }
4330
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004331 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234332 // events to the feature_engagement::Tracker based on |command.userInitiated|
4333 // and |command.incognito|.
4334 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004335
Mark Cogandfcdea72017-07-18 13:47:384336 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4337 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494338 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4339 self.foregroundTabWasAddedCompletionBlock;
Louis Romero960a12f2017-11-30 19:08:594340 __weak BrowserViewController* weakSelf = self;
Mark Cogandfcdea72017-07-18 13:47:384341 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494342 if (oldForegroundTabWasAddedCompletionBlock) {
4343 oldForegroundTabWasAddedCompletionBlock();
4344 }
Mark Cogandfcdea72017-07-18 13:47:384345 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4346 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4347 if (offTheRecord) {
4348 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4349 timeDelta);
4350 } else {
4351 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4352 }
Louis Romero960a12f2017-11-30 19:08:594353 if (command.shouldFocusOmnibox) {
4354 [weakSelf focusOmnibox];
4355 }
Mark Cogandfcdea72017-07-18 13:47:384356 };
4357
4358 [self setLastTapPoint:command];
Rohit Rao2e22b8d2017-11-07 19:54:544359 // When the tab switcher presentation experiment is enabled, the new tab can
4360 // be opened before BVC has been made visible onscreen. Test for this case by
4361 // checking if the parent container VC is currently in the process of being
4362 // presented.
4363 DCHECK(self.visible || self.dismissingModal ||
4364 (TabSwitcherPresentsBVCEnabled() &&
4365 self.parentViewController.isBeingPresented));
edchin3ab78ff2017-11-13 19:13:144366
4367 // In most cases, we want to take a snapshot of the current tab before opening
4368 // a new tab. However, if the current tab is not fully visible (did not finish
4369 // |-viewDidAppear:|, then we must not take an empty snapshot, replacing an
4370 // existing snapshot for the tab. This can happen when a new regular tab is
4371 // opened from an incognito tab. A different BVC is displayed, which may not
4372 // have enough time to finish appearing before a snapshot is requested.
Mark Cogandfcdea72017-07-18 13:47:384373 Tab* currentTab = [_model currentTab];
edchin3ab78ff2017-11-13 19:13:144374 if (currentTab && self.viewVisible) {
Mark Cogandfcdea72017-07-18 13:47:384375 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4376 }
4377 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4378 transition:ui::PAGE_TRANSITION_TYPED];
4379}
4380
Mark Cogan123895002017-07-20 12:54:064381- (void)printTab {
4382 Tab* currentTab = [_model currentTab];
4383 // The UI should prevent users from printing non-printable pages. However, a
4384 // redirection to an un-printable page can happen before it is reflected in
4385 // the UI.
4386 if (![currentTab viewForPrinting]) {
4387 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364388 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064389 return;
4390 }
4391 DCHECK(_browserState);
4392 if (!_printController) {
4393 _printController = [[PrintController alloc]
4394 initWithContextGetter:_browserState->GetRequestContext()];
4395 }
4396 [_printController printView:[currentTab viewForPrinting]
4397 withTitle:[currentTab title]
4398 viewController:self];
4399}
4400
Mark Coganfa25b052017-07-20 17:31:034401- (void)addToReadingList:(ReadingListAddCommand*)command {
4402 [self addToReadingListURL:[command URL] title:[command title]];
4403}
4404
sczs3a8c8602017-08-01 20:14:084405- (void)showReadingList {
4406 _readingListCoordinator = [[ReadingListCoordinator alloc]
4407 initWithBaseViewController:self
4408 browserState:self.browserState
4409 loader:self];
4410
4411 [_readingListCoordinator start];
4412}
4413
Jean-François Geyelinedef9552017-08-07 09:56:564414- (void)preloadVoiceSearch {
4415 // Preload VoiceSearchController and views and view controllers needed
4416 // for voice search.
4417 [self ensureVoiceSearchControllerCreated];
4418 _voiceSearchController->PrepareToAppear();
4419}
4420
edchinc5720722017-08-14 22:06:314421#if !defined(NDEBUG)
4422- (void)viewSource {
4423 Tab* tab = [_model currentTab];
4424 DCHECK(tab);
4425 CRWWebController* webController = tab.webController;
4426 NSString* script = @"document.documentElement.outerHTML;";
4427 __weak Tab* weakTab = tab;
4428 __weak BrowserViewController* weakSelf = self;
4429 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4430 Tab* strongTab = weakTab;
4431 if (!strongTab)
4432 return;
4433 if (![result isKindOfClass:[NSString class]])
4434 result = @"Not an HTML page";
4435 std::string base64HTML;
4436 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4437 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194438 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314439 web::ReferrerPolicyDefault);
4440
4441 [[weakSelf tabModel]
4442 insertTabWithURL:URL
4443 referrer:referrer
4444 transition:ui::PAGE_TRANSITION_LINK
4445 opener:strongTab
4446 openedByDOM:YES
4447 atIndex:TabModelConstants::kTabPositionAutomatically
4448 inBackground:NO];
4449 };
4450 [webController executeJavaScript:script
4451 completionHandler:completionHandlerBlock];
4452}
4453#endif // !defined(NDEBUG)
4454
edchin2134c042017-08-18 13:57:354455// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4456- (void)showRateThisAppDialog {
4457 DCHECK(!_rateThisAppDialog);
4458
4459 // Store the current timestamp whenever this dialog is shown.
4460 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4461 base::Time::Now().ToInternalValue());
4462
Gregory Chatzinofff39ec5162017-10-05 20:28:534463 // iOS11 no longer supports the itms link to the app store. So, use a deep
4464 // link for iOS11 and the itms link for prior versions.
4465 NSURL* storeURL;
4466 if (base::ios::IsRunningOnIOS11OrLater()) {
4467 storeURL =
4468 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4469 @"google-chrome-the-fast-and-secure-web-browser/"
4470 @"id535886823?action=write-review")];
4471 } else {
4472 storeURL = [NSURL
4473 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4474 @"MZStore.woa/wa/"
4475 @"viewContentsUserReviews?type=Purple+Software&id="
4476 @"535886823&pt=9008&ct=rating")];
4477 }
edchin2134c042017-08-18 13:57:354478
4479 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014480 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354481
4482 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4483 [_rateThisAppDialog setAppStoreURL:storeURL];
4484 [_rateThisAppDialog setDelegate:self];
4485 [_rateThisAppDialog show];
4486}
4487
Gregory Chatzinoff3f40c1542017-08-30 07:50:044488- (void)showFindInPage {
4489 if (!self.canShowFindBar)
4490 return;
4491
4492 if (!_findBarController) {
4493 _findBarController =
4494 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4495 _findBarController.dispatcher = self.dispatcher;
4496 }
4497
4498 Tab* tab = [_model currentTab];
4499 DCHECK(tab);
4500 auto* helper = FindTabHelper::FromWebState(tab.webState);
4501 DCHECK(!helper->IsFindUIActive());
4502 helper->SetFindUIActive(true);
4503 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4504}
4505
4506- (void)closeFindInPage {
4507 __weak BrowserViewController* weakSelf = self;
4508 Tab* currentTab = [_model currentTab];
4509 if (currentTab) {
4510 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4511 [weakSelf updateFindBar:NO shouldFocus:NO];
4512 });
4513 }
4514}
4515
4516- (void)searchFindInPage {
4517 DCHECK([_model currentTab]);
4518 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4519 __weak BrowserViewController* weakSelf = self;
4520 helper->StartFinding(
4521 [_findBarController searchTerm], ^(FindInPageModel* model) {
4522 BrowserViewController* strongSelf = weakSelf;
4523 if (!strongSelf) {
4524 return;
4525 }
4526 [strongSelf->_findBarController updateResultsCount:model];
4527 });
4528
4529 if (!_isOffTheRecord)
4530 helper->PersistSearchTerm();
4531}
4532
4533- (void)findNextStringInPage {
4534 Tab* currentTab = [_model currentTab];
4535 DCHECK(currentTab);
4536 // TODO(crbug.com/603524): Reshow find bar if necessary.
4537 FindTabHelper::FromWebState(currentTab.webState)
4538 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4539 [_findBarController updateResultsCount:model];
4540 });
4541}
4542
4543- (void)findPreviousStringInPage {
4544 Tab* currentTab = [_model currentTab];
4545 DCHECK(currentTab);
4546 // TODO(crbug.com/603524): Reshow find bar if necessary.
4547 FindTabHelper::FromWebState(currentTab.webState)
4548 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4549 [_findBarController updateResultsCount:model];
4550 });
4551}
4552
edchinf84b2502017-08-31 21:30:454553- (void)showHelpPage {
4554 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4555 [self webPageOrderedOpen:helpUrl
4556 referrer:web::Referrer()
4557 inBackground:NO
4558 appendTo:kCurrentTab];
4559}
4560
edchinb59b5602017-09-01 15:00:204561- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104562 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204563 [self showAllBookmarks];
4564 } else {
4565 [self initializeBookmarkInteractionController];
4566 [_bookmarkInteractionController presentBookmarks];
4567 }
4568}
4569
edchin8ee0807d2017-09-01 23:52:474570- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104571 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474572 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4573 } else {
4574 if (!self.recentTabsCoordinator) {
4575 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4576 initWithBaseViewController:self];
4577 self.recentTabsCoordinator.loader = self;
4578 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4579 self.recentTabsCoordinator.browserState = _browserState;
4580 }
4581 [self.recentTabsCoordinator start];
4582 }
4583}
4584
Mark Cogan6de7e9a2017-09-06 12:57:214585- (void)requestDesktopSite {
4586 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4587}
4588
4589- (void)requestMobileSite {
4590 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4591}
4592
sdefresnee65fd872016-12-19 13:38:134593#pragma mark - Command Handling
4594
sdefresnee65fd872016-12-19 13:38:134595- (void)closeCurrentTab {
4596 Tab* currentTab = [_model currentTab];
4597 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4598 if (tabIndex == NSNotFound)
4599 return;
4600
jif7fed8122017-02-08 13:15:254601 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4602 // iPad.
sdefresnee65fd872016-12-19 13:38:134603 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4604 exitingPage.image =
4605 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4606
4607 // Close the actual tab, and add its image as a subview.
4608 [_model closeTabAtIndex:tabIndex];
4609
4610 // Do not animate close in iPad.
4611 if (!IsIPadIdiom()) {
4612 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524613 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134614 exitingPage, 0, YES, IsPortrait(), ^{
4615 [exitingPage removeFromSuperview];
4616 });
4617 }
4618}
4619
Elodie Banelaa5ab432017-09-28 14:42:014620- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
4621 dismissOmnibox:(BOOL)dismissOmnibox {
Rohit Rao01e0e002017-08-14 20:49:434622 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134623 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4624 [_bookmarkInteractionController dismissSnackbar];
Elodie Banelaa5ab432017-09-28 14:42:014625 if (dismissOmnibox) {
sczsf1620e52017-10-02 22:54:464626 [_toolbarCoordinator cancelOmniboxEdit];
Elodie Banelaa5ab432017-09-28 14:42:014627 }
sdefresnee65fd872016-12-19 13:38:134628 [_dialogPresenter cancelAllDialogs];
Gregory Chatzinoffdf93d692017-09-09 01:32:274629 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:474630 [self.tabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134631 if (_voiceSearchController)
4632 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364633
4634 Tab* currentTab = [_model currentTab];
4635 [currentTab dismissModals];
4636
rohitrao005a6432017-03-16 20:52:424637 if (currentTab) {
4638 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4639 if (findHelper) {
4640 findHelper->StopFinding(^{
4641 [self updateFindBar:NO shouldFocus:NO];
4642 });
4643 }
4644 }
rohitraob2bf3cb2017-02-10 14:10:364645
sdefresnee65fd872016-12-19 13:38:134646 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134647 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274648 _printController = nil;
Peter Laurense0b80f12017-11-21 07:52:404649 [self.dispatcher dismissToolsMenu];
sdefresnee65fd872016-12-19 13:38:134650 [_contextMenuCoordinator stop];
4651 [self dismissRateThisAppDialog];
4652
4653 if (self.presentedViewController) {
4654 // Dismisses any other modal controllers that may be present, e.g. Recent
4655 // Tabs.
Rohit Raoa1f1bac2017-11-07 16:27:514656 //
sdefresnee65fd872016-12-19 13:38:134657 // Note that currently, some controllers like the bookmark ones were already
4658 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
Rohit Raoa1f1bac2017-11-07 16:27:514659 // but are still reported as the presentedViewController. Calling
4660 // |dismissViewControllerAnimated:completion:| again would dismiss the BVC
4661 // itself, so instead check the value of |self.dismissingModal| and only
4662 // call dismiss if one of the above calls has not already triggered a
4663 // dismissal.
4664 //
4665 // To ensure the completion is called, nil is passed to the call to dismiss,
4666 // and the completion is called explicitly below.
4667 if (!TabSwitcherPresentsBVCEnabled() || !self.dismissingModal) {
4668 [self dismissViewControllerAnimated:NO completion:nil];
4669 }
sdefresnee65fd872016-12-19 13:38:134670 // Dismissed controllers will be so after a delay. Queue the completion
4671 // callback after that.
4672 if (completion) {
4673 dispatch_after(
4674 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4675 dispatch_get_main_queue(), ^{
4676 completion();
4677 });
4678 }
4679 } else if (completion) {
4680 // If no view controllers are presented, we should be ok with dispatching
4681 // the completion block directly.
4682 dispatch_async(dispatch_get_main_queue(), completion);
4683 }
4684}
4685
sdefresnee65fd872016-12-19 13:38:134686#pragma mark - Find Bar
4687
4688- (void)hideFindBarWithAnimation:(BOOL)animate {
4689 [_findBarController hideFindBarView:animate];
4690}
4691
4692- (void)showFindBarWithAnimation:(BOOL)animate
4693 selectText:(BOOL)selectText
4694 shouldFocus:(BOOL)shouldFocus {
4695 DCHECK(_findBarController);
4696 Tab* tab = [_model currentTab];
4697 DCHECK(tab);
4698 CRWWebController* webController = tab.webController;
4699
4700 CGRect referenceFrame = CGRectZero;
4701 if (IsIPadIdiom()) {
4702 referenceFrame = webController.visibleFrame;
4703 referenceFrame.origin.y -= kIPadFindBarOverlap;
4704 } else {
4705 referenceFrame = _contentArea.frame;
4706 }
4707
sczsf1620e52017-10-02 22:54:464708 CGRect omniboxFrame = [_toolbarCoordinator visibleOmniboxFrame];
sdefresnee65fd872016-12-19 13:38:134709 [_findBarController addFindBarView:animate
4710 intoView:self.view
4711 withFrame:referenceFrame
4712 alignWithFrame:omniboxFrame
4713 selectText:selectText];
4714 [self updateFindBar:YES shouldFocus:shouldFocus];
4715}
4716
sdefresnee65fd872016-12-19 13:38:134717- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324718 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4719 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4720 // in a crash.
4721 if (![_model currentTab]) {
4722 return;
4723 }
rohitrao005a6432017-03-16 20:52:424724 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4725 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134726 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424727 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134728 }
4729
4730 [self setFramesForHeaders:[self headerViews]
4731 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424732 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134733 initialUpdate:initialUpdate
4734 focusTextfield:shouldFocus];
4735 } else {
4736 [self hideFindBarWithAnimation:YES];
4737 }
4738}
4739
4740- (void)showAllBookmarks {
4741 DCHECK(self.visible || self.dismissingModal);
4742 GURL URL(kChromeUIBookmarksURL);
4743 Tab* tab = [_model currentTab];
4744 web::NavigationManager::WebLoadParams params(URL);
4745 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234746 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134747}
4748
Gauthier Ambardf520c022017-08-29 07:42:234749- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134750 DCHECK(self.visible || self.dismissingModal);
4751 GURL url(kChromeUINewTabURL);
4752 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4753 if (fragment != "") {
4754 GURL::Replacements replacement;
4755 replacement.SetRefStr(fragment);
4756 url = url.ReplaceComponents(replacement);
4757 }
4758 Tab* tab = [_model currentTab];
4759 web::NavigationManager::WebLoadParams params(url);
4760 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234761 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134762}
4763
sdefresnee65fd872016-12-19 13:38:134764- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274765 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134766 base::RecordAction(base::UserMetricsAction(
4767 "IOSRateThisAppDialogDismissedProgramatically"));
4768 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274769 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134770 }
4771}
4772
Jean-François Geyelin5d2e184c2017-07-28 19:48:004773- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4774 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134775 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124776 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134777 _startVoiceSearchAfterNewTabAnimation = YES;
4778 return;
4779 }
4780
4781 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4782 // dismiss the keyboard.
4783 [self closeFindInPage];
4784 [[_model currentTab].webController dismissKeyboard];
4785
4786 // Ensure that voice search objects are created.
4787 [self ensureVoiceSearchControllerCreated];
4788 [self ensureVoiceSearchBarCreated];
4789
4790 // Present voice search.
4791 [_voiceSearchBar prepareToPresentVoiceSearch];
4792 _voiceSearchController->StartRecognition(self, [_model currentTab]);
sczsf1620e52017-10-02 22:54:464793 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:134794}
4795
4796#pragma mark - ToolbarOwner
4797
Kurt Horimotoea429dd2017-11-28 02:24:304798- (CGFloat)toolbarHeight {
4799 return [self headerHeight];
4800}
4801
Gauthier Ambard04ddb512017-11-07 09:14:164802- (CGRect)toolbarFrame {
sczs42f7f7482017-11-08 01:13:274803 return _toolbarCoordinator.toolbarViewController.view.frame;
Gauthier Ambard04ddb512017-11-07 09:14:164804}
4805
Gauthier Ambard996d9b12017-11-06 09:39:214806- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
4807 id<ToolbarSnapshotProviding> toolbarSnapshotProvider = nil;
sczs42f7f7482017-11-08 01:13:274808 if (_toolbarCoordinator.toolbarViewController.view.hidden) {
Gauthier Ambard996d9b12017-11-06 09:39:214809 Tab* currentTab = [_model currentTab];
4810 if (currentTab.webState &&
4811 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
4812 // Use the native content controller's toolbar when the BVC's is hidden.
4813 id nativeController = [self nativeControllerForTab:currentTab];
4814 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4815 toolbarSnapshotProvider = [nativeController toolbarSnapshotProvider];
4816 }
4817 }
4818 } else {
4819 toolbarSnapshotProvider = _toolbarCoordinator;
4820 }
4821 return toolbarSnapshotProvider;
4822}
4823
sdefresnee65fd872016-12-19 13:38:134824#pragma mark - TabModelObserver methods
4825
4826// Observer method, tab inserted.
4827- (void)tabModel:(TabModel*)model
4828 didInsertTab:(Tab*)tab
4829 atIndex:(NSUInteger)modelIndex
4830 inForeground:(BOOL)fg {
4831 DCHECK(tab);
4832 [self installDelegatesForTab:tab];
4833
4834 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194835 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134836 }
4837}
4838
4839// Observer method, active tab changed.
4840- (void)tabModel:(TabModel*)model
4841 didChangeActiveTab:(Tab*)newTab
4842 previousTab:(Tab*)previousTab
4843 atIndex:(NSUInteger)index {
4844 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4845 // Currently this observer method is always called with a non-nil |newTab|,
4846 // but that may change in the future. Remove this DCHECK when it does.
4847 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274848 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304849 DCHECK(newTab.webState);
4850 infobars::InfoBarManager* infoBarManager =
4851 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134852 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4853 }
4854 [self updateVoiceSearchBarVisibilityAnimated:NO];
4855
Mohamad Ahmadi7d09ec32017-07-11 22:32:194856 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134857
4858 [self tabSelected:newTab];
sdefresnee65fd872016-12-19 13:38:134859}
4860
4861// Observer method, tab changed.
4862- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4863 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4864 if (tab == [_model currentTab]) {
4865 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134866 }
4867}
4868
sdefresne49cf2862017-03-15 13:46:144869// Observer method, tab replaced.
4870- (void)tabModel:(TabModel*)model
4871 didReplaceTab:(Tab*)oldTab
4872 withTab:(Tab*)newTab
4873 atIndex:(NSUInteger)index {
4874 [self uninstallDelegatesForTab:oldTab];
4875 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264876
michaeldo79909fb2017-05-09 23:42:504877 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304878 infobars::InfoBarManager* infoBarManager = nullptr;
4879 if (newTab) {
4880 DCHECK(newTab.webState);
4881 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4882 }
michaeldo79909fb2017-05-09 23:42:504883 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4884 }
4885
kkhorimotofa0844cc2017-03-20 17:01:264886 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4887 if (self.active && model.currentTab == newTab)
4888 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464889
4890 if (newTab)
4891 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144892}
4893
sdefresnee65fd872016-12-19 13:38:134894// A tab has been removed, remove its views from display if necessary.
4895- (void)tabModel:(TabModel*)model
4896 didRemoveTab:(Tab*)tab
4897 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144898 [self uninstallDelegatesForTab:tab];
4899
kkhorimoto496fdd72017-06-12 19:56:314900 // Cancel dialogs for |tab|'s WebState.
4901 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4902
sdefresnee65fd872016-12-19 13:38:134903 // Ignore changes while the tab stack view is visible (or while suspended).
4904 // The display will be refreshed when this view becomes active again.
4905 if (!self.visible || !model.webUsageEnabled)
4906 return;
4907
4908 // Remove the find bar for now.
4909 [self hideFindBarWithAnimation:NO];
4910}
4911
4912- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4913 if (tab == [model currentTab]) {
4914 [_contentArea displayContentView:nil];
sczsf1620e52017-10-02 22:54:464915 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:134916 }
4917
Mohamad Ahmadi7d09ec32017-07-11 22:32:194918 [_paymentRequestManager stopTrackingWebState:tab.webState];
4919
sdefresnee65fd872016-12-19 13:38:134920 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4921 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194922 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134923 }
4924}
4925
4926// Called when the number of tabs changes. Update the toolbar accordingly.
4927- (void)tabModelDidChangeTabCount:(TabModel*)model {
4928 DCHECK(model == _model);
sczsf1620e52017-10-02 22:54:464929 [_toolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134930}
4931
4932#pragma mark - Upgrade Detection
4933
4934- (void)showUpgrade:(UpgradeCenter*)center {
4935 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274936 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134937 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304938 DCHECK(tab.webState);
4939 infobars::InfoBarManager* infoBarManager =
4940 InfoBarManagerImpl::FromWebState(tab.webState);
4941 DCHECK(infoBarManager);
4942 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134943 }
4944}
4945
Mark Cogan80aa28d2017-11-30 13:11:344946#pragma mark - InfobarContainerStateDelegate
sdefresnee65fd872016-12-19 13:38:134947
Mark Cogan80aa28d2017-11-30 13:11:344948- (void)infoBarContainerStateDidChangeAnimated:(BOOL)animated {
sdefresnee65fd872016-12-19 13:38:134949 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4950 DCHECK(infoBarContainerView);
4951 CGRect containerFrame = infoBarContainerView.frame;
4952 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4953 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4954 containerFrame.size.height = height;
4955 BOOL isViewVisible = self.visible;
4956 [UIView animateWithDuration:0.1
4957 animations:^{
4958 [infoBarContainerView setFrame:containerFrame];
4959 }
4960 completion:^(BOOL finished) {
4961 if (!isViewVisible)
4962 return;
4963 UIAccessibilityPostNotification(
4964 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4965 }];
4966}
4967
Mark Cogan80aa28d2017-11-30 13:11:344968#pragma mark - UIGestureRecognizerDelegate
sdefresnee65fd872016-12-19 13:38:134969
4970// Always return yes, as this tap should work with various recognizers,
4971// including UITextTapRecognizer, UILongPressGestureRecognizer,
4972// UIScrollViewPanGestureRecognizer and others.
4973- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4974 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4975 (UIGestureRecognizer*)otherGestureRecognizer {
4976 return YES;
4977}
4978
4979// Tap gestures should only be recognized within |_contentArea|.
4980- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4981 CGPoint location = [gesture locationInView:self.view];
4982
4983 // Only allow touches on descendant views of |_contentArea|.
4984 UIView* hitView = [self.view hitTest:location withEvent:nil];
4985 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4986}
4987
4988#pragma mark - SideSwipeController Delegate Methods
4989
4990- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4991 DCHECK(!IsIPadIdiom());
4992 // Update frame incase orientation changed while |_contentArea| was out of
4993 // the view hierarchy.
4994 [_contentArea setFrame:[sideSwipeView frame]];
4995
Justin Cohen16ad60e2017-11-10 14:56:264996 [self.view insertSubview:_contentArea aboveSubview:_fakeStatusBarView];
sdefresnee65fd872016-12-19 13:38:134997 [self updateVoiceSearchBarVisibilityAnimated:NO];
4998 [self updateToolbar];
4999
5000 // Reset horizontal stack view.
5001 [sideSwipeView removeFromSuperview];
5002 [_sideSwipeController setInSwipe:NO];
5003 [_infoBarContainer->view() setHidden:NO];
5004}
5005
5006- (UIView*)contentView {
5007 return _contentArea;
5008}
5009
sdefresnee65fd872016-12-19 13:38:135010- (BOOL)preventSideSwipe {
Peter Laurense0b80f12017-11-21 07:52:405011 if ([_toolbarCoordinator isShowingToolsMenu])
sdefresnee65fd872016-12-19 13:38:135012 return YES;
5013
5014 if (_voiceSearchController && _voiceSearchController->IsVisible())
5015 return YES;
5016
sdefresnee65fd872016-12-19 13:38:135017 if (!self.active)
5018 return YES;
5019
5020 return NO;
5021}
5022
5023- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5024 if (visible) {
5025 [self updateVoiceSearchBarVisibilityAnimated:NO];
5026 [self updateToolbar];
5027 [_infoBarContainer->view() setHidden:NO];
5028 } else {
5029 // Hide UI accessories such as find bar and first visit overlays
5030 // for welcome page.
5031 [self hideFindBarWithAnimation:NO];
5032 [_infoBarContainer->view() setHidden:YES];
5033 [_voiceSearchBar setHidden:YES];
5034 }
5035}
5036
5037- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5038 BOOL seenToolbar = NO;
5039 BOOL seenInfoBarContainer = NO;
5040 BOOL seenContentArea = NO;
5041 for (UIView* view in views.subviews) {
sczs42f7f7482017-11-08 01:13:275042 if (view == _toolbarCoordinator.toolbarViewController.view)
sdefresnee65fd872016-12-19 13:38:135043 seenToolbar = YES;
5044 else if (view == _infoBarContainer->view())
5045 seenInfoBarContainer = YES;
5046 else if (view == _contentArea)
5047 seenContentArea = YES;
5048 if ((seenToolbar && !seenInfoBarContainer) ||
5049 (seenInfoBarContainer && !seenContentArea))
5050 return NO;
5051 }
5052 return YES;
5053}
5054
5055#pragma mark - PreloadControllerDelegate methods
5056
rohitraoeeb5293b2017-06-15 14:40:025057- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445058 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135059}
5060
rohitraoeeb5293b2017-06-15 14:40:025061- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5062 return [self hasControllerForURL:url];
5063}
5064
Gauthier Ambard65e949b092017-11-29 08:46:205065// TODO(crbug.com/788705): BVC doesn't need to implement
5066// BookmarkModelBridgeObserver once the new toolbar is turned on.
5067#pragma mark - BookmarkModelBridgeObserver
sdefresnee65fd872016-12-19 13:38:135068
5069// If an added or removed bookmark is the same as the current url, update the
5070// toolbar so the star highlight is kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205071- (void)bookmarkNodeChildrenChanged:(const BookmarkNode*)bookmarkNode {
5072 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:135073}
5074
5075// If all bookmarks are removed, update the toolbar so the star highlight is
5076// kept in sync.
Gauthier Ambard65e949b092017-11-29 08:46:205077- (void)bookmarkModelRemovedAllNodes {
sdefresnee65fd872016-12-19 13:38:135078 [self updateToolbar];
5079}
5080
Gauthier Ambard65e949b092017-11-29 08:46:205081// In case we are on a bookmarked page before the model is loaded.
5082- (void)bookmarkModelLoaded {
5083 [self updateToolbar];
5084}
5085
5086- (void)bookmarkNodeChanged:(const BookmarkNode*)bookmarkNode {
5087 // No-op -- required by BookmarkModelBridgeObserver but not used.
5088}
5089
5090- (void)bookmarkNode:(const BookmarkNode*)bookmarkNode
5091 movedFromParent:(const BookmarkNode*)oldParent
5092 toParent:(const BookmarkNode*)newParent {
5093 // No-op -- required by BookmarkModelBridgeObserver but not used.
5094}
5095
5096- (void)bookmarkNodeDeleted:(const BookmarkNode*)node
5097 fromFolder:(const BookmarkNode*)folder {
5098 // No-op -- required by BookmarkModelBridgeObserver but not used.
5099}
5100
5101#pragma mark - Alerts
5102
sdefresnee65fd872016-12-19 13:38:135103- (void)showErrorAlertWithStringTitle:(NSString*)title
5104 message:(NSString*)message {
5105 // Dismiss current alert.
5106 [_alertCoordinator stop];
5107
stkhapuginc9eee7b2017-04-10 15:49:275108 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5109 message:message
5110 viewController:self];
sdefresnee65fd872016-12-19 13:38:135111 [_alertCoordinator start];
5112}
5113
edchineeb4d422017-10-02 17:39:365114- (void)showSnackbar:(NSString*)text {
5115 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
5116 message.accessibilityLabel = text;
5117 message.duration = 2.0;
5118 message.category = kBrowserViewControllerSnackbarCategory;
5119 [self.dispatcher showSnackbarMessage:message];
5120}
5121
sdefresnee65fd872016-12-19 13:38:135122#pragma mark - Show Mail Composer methods
5123
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575124- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5125 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135126 if (![MFMailComposeViewController canSendMail]) {
5127 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575128 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135129 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575130 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135131 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5132 return;
5133 }
stkhapuginc9eee7b2017-04-10 15:49:275134 MFMailComposeViewController* mailViewController =
5135 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135136 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575137 [mailViewController setToRecipients:[context toRecipients]];
5138 [mailViewController setSubject:[context subject]];
5139 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135140
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575141 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135142 if (!textFile.empty()) {
5143 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5144 NSData* data = [NSData dataWithContentsOfFile:filename];
5145 if (data) {
5146 NSString* displayName =
5147 base::SysUTF8ToNSString(textFile.BaseName().value());
5148 [mailViewController addAttachmentData:data
5149 mimeType:@"text/plain"
5150 fileName:displayName];
5151 }
5152 }
5153
5154 [mailViewController setMailComposeDelegate:self];
5155 [self presentViewController:mailViewController animated:YES completion:nil];
5156}
5157
5158#pragma mark - MFMailComposeViewControllerDelegate methods
5159
5160- (void)mailComposeController:(MFMailComposeViewController*)controller
5161 didFinishWithResult:(MFMailComposeResult)result
5162 error:(NSError*)error {
5163 [self dismissViewControllerAnimated:YES completion:nil];
5164}
5165
5166#pragma mark - StoreKitLauncher methods
5167
5168- (void)productViewControllerDidFinish:
5169 (SKStoreProductViewController*)viewController {
5170 [self dismissViewControllerAnimated:YES completion:nil];
5171}
5172
5173- (void)openAppStore:(NSString*)appId {
5174 if (![appId length])
5175 return;
5176 NSDictionary* product =
5177 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275178 SKStoreProductViewController* storeViewController =
5179 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135180 [storeViewController setDelegate:self];
5181 [storeViewController loadProductWithParameters:product completionBlock:nil];
5182 [self presentViewController:storeViewController animated:YES completion:nil];
5183}
5184
5185#pragma mark - TabDialogDelegate methods
5186
sdefresnee65fd872016-12-19 13:38:135187- (void)cancelDialogForTab:(Tab*)tab {
5188 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5189}
5190
5191#pragma mark - FKFeedbackPromptDelegate methods
5192
5193- (void)userTappedRateApp:(UIView*)view {
5194 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275195 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135196}
5197
5198- (void)userTappedSendFeedback:(UIView*)view {
5199 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275200 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205201 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135202}
5203
5204- (void)userTappedDismiss:(UIView*)view {
5205 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275206 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135207}
5208
5209#pragma mark - VoiceSearchBarDelegate
5210
5211- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275212 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135213 [self ensureVoiceSearchControllerCreated];
5214 return _voiceSearchController->IsTextToSpeechEnabled() &&
5215 _voiceSearchController->IsTextToSpeechSupported();
5216}
5217
5218- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275219 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135220 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5221}
5222
5223#pragma mark - VoiceSearchPresenter
5224
5225- (UIView*)voiceSearchButton {
5226 return _voiceSearchButton;
5227}
5228
5229- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5230 return [self currentLogoAnimationControllerOwner];
5231}
5232
Rohit Rao01e0e002017-08-14 20:49:435233#pragma mark - ActivityService Providers
5234
5235- (void)presentActivityServiceViewController:(UIViewController*)controller {
5236 [self presentViewController:controller animated:YES completion:nil];
5237}
5238
5239- (void)activityServiceDidEndPresenting {
5240 self.presenting = NO;
5241 [self.dialogPresenter tryToPresent];
5242}
5243
Rohit Raocda0a992017-08-16 15:37:115244#pragma mark - QRScanner Requirements
5245
5246- (void)presentQRScannerViewController:(UIViewController*)controller {
5247 [self presentViewController:controller animated:YES completion:nil];
5248}
5249
5250- (void)dismissQRScannerViewController:(UIViewController*)controller
5251 completion:(void (^)(void))completion {
5252 DCHECK_EQ(controller, self.presentedViewController);
5253 [self dismissViewControllerAnimated:YES completion:completion];
5254}
5255
sczsdd860eba2017-08-10 01:55:385256#pragma mark - TabHistoryPresenter
5257
sczs0a726d22017-08-21 22:40:135258- (UIView*)viewForTabHistoryPresentation {
5259 return self.view;
5260}
5261
sczsdd860eba2017-08-10 01:55:385262- (void)prepareForTabHistoryPresentation {
5263 DCHECK(self.visible || self.dismissingModal);
5264 [[self.tabModel currentTab].webController dismissKeyboard];
sczsf1620e52017-10-02 22:54:465265 [_toolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385266}
5267
Mike Doughertya1ec26402017-08-23 19:46:315268#pragma mark - CaptivePortalDetectorTabHelperDelegate
5269
Mike Dougherty4620cf8e2017-10-31 23:37:095270- (void)captivePortalDetectorTabHelper:
5271 (CaptivePortalDetectorTabHelper*)tabHelper
5272 connectWithLandingURL:(const GURL&)landingURL {
Mike Dougherty66e58812017-11-03 06:54:285273 [self addSelectedTabWithURL:landingURL transition:ui::PAGE_TRANSITION_TYPED];
Mike Doughertya1ec26402017-08-23 19:46:315274}
5275
Gregory Chatzinoffdf93d692017-09-09 01:32:275276#pragma mark - PageInfoPresentation
5277
Gregory Chatzinoffb6a01f72017-09-20 20:06:395278- (void)presentPageInfoView:(UIView*)pageInfoView {
5279 [pageInfoView setFrame:self.view.bounds];
5280 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275281}
5282
5283- (void)prepareForPageInfoPresentation {
5284 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:465285 [_toolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275286}
5287
Gregory Chatzinoffb6a01f72017-09-20 20:06:395288- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5289 return [self.view convertPoint:origin fromView:nil];
5290}
5291
Sylvain Defresnecacc3a52017-09-12 13:51:045292#pragma mark - WebStatePrinter
5293
5294- (void)printWebState:(web::WebState*)webState {
5295 if (webState == [_model currentTab].webState)
5296 [self printTab];
5297}
5298
Eugene But35ded552017-09-13 23:31:595299#pragma mark - RepostFormTabHelperDelegate
5300
5301- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
Sylvain Defresnee3c698122017-11-17 11:16:325302 presentRepostFormDialogForWebState:(web::WebState*)webState
5303 dialogAtPoint:(CGPoint)location
5304 completionHandler:(void (^)(BOOL))completion {
5305 _repostFormCoordinator =
5306 [[RepostFormCoordinator alloc] initWithBaseViewController:self
5307 dialogLocation:location
5308 webState:webState
5309 completionHandler:completion];
Eugene But35ded552017-09-13 23:31:595310 [_repostFormCoordinator start];
5311}
5312
5313- (void)repostFormTabHelperDismissRepostFormDialog:
5314 (RepostFormTabHelper*)helper {
5315 _repostFormCoordinator = nil;
5316}
5317
edchinf5150c682017-09-18 02:50:035318#pragma mark - TabStripPresentation
5319
5320- (BOOL)isTabStripFullyVisible {
5321 return ([self currentHeaderOffset] == 0.0f);
5322}
5323
5324- (void)showTabStripView:(UIView*)tabStripView {
5325 DCHECK([self isViewLoaded]);
5326 DCHECK(tabStripView);
5327 self.tabStripView = tabStripView;
5328 CGRect tabStripFrame = [self.tabStripView frame];
5329 tabStripFrame.origin = CGPointZero;
5330 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5331 // because the CGPointZero above will break reset the offset, but it's not
5332 // clear what removing that will do.
5333 tabStripFrame.origin.y = [self headerOffset];
5334 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5335 [self.tabStripView setFrame:tabStripFrame];
5336 [[self view] addSubview:tabStripView];
5337}
5338
edchincd32fdf2017-10-25 12:45:455339#pragma mark - ManageAccountsDelegate
5340
5341- (void)onManageAccounts {
5342 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5343 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5344 ->GetState());
edchin5b8aa052017-10-30 23:27:285345 [self.dispatcher showAccountsSettingsFromViewController:self];
edchincd32fdf2017-10-25 12:45:455346}
5347
5348- (void)onAddAccount {
5349 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5350 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5351 ->GetState());
edchinb082b2982017-11-03 19:41:215352 [self.dispatcher showAddAccountFromViewController:self];
edchincd32fdf2017-10-25 12:45:455353}
5354
5355- (void)onGoIncognito:(const GURL&)url {
5356 // The user taps on go incognito from the mobile U-turn webpage (the web page
5357 // that displays all users accounts available in the content area). As the
5358 // user chooses to go to incognito, the mobile U-turn page is no longer
5359 // neeeded. The current solution is to go back in history. This has the
5360 // advantage of keeping the current browsing session and give a good user
5361 // experience when the user comes back from incognito.
5362 [self.tabModel.currentTab goBack];
5363
5364 if (url.is_valid()) {
5365 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5366 initWithURL:url
5367 referrer:web::Referrer() // Strip referrer when switching modes.
5368 inIncognito:YES
5369 inBackground:NO
5370 appendTo:kLastTab];
5371 [self.dispatcher openURL:command];
5372 } else {
5373 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5374 }
5375}
5376
edchin95c927072017-11-04 00:35:075377#pragma mark - SyncPresenter
5378
5379- (void)showReauthenticateSignin {
5380 [self.dispatcher
edchin3b46e8d2017-11-07 22:48:125381 showSignin:
5382 [[ShowSigninCommand alloc]
5383 initWithOperation:AUTHENTICATION_OPERATION_REAUTHENTICATE
5384 accessPoint:signin_metrics::AccessPoint::
5385 ACCESS_POINT_UNKNOWN]
5386 baseViewController:self];
edchin95c927072017-11-04 00:35:075387}
5388
5389- (void)showSyncSettings {
edchina14d7182017-11-06 18:37:505390 [self.dispatcher showSyncSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075391}
5392
5393- (void)showSyncPassphraseSettings {
edchinec723062017-11-06 20:03:545394 [self.dispatcher showSyncPassphraseSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075395}
5396
edchin9e7a1112017-11-07 18:28:035397#pragma mark - SigninPresenter
5398
5399- (void)showSignin:(ShowSigninCommand*)command {
edchin3b46e8d2017-11-07 22:48:125400 [self.dispatcher showSignin:command baseViewController:self];
edchin9e7a1112017-11-07 18:28:035401}
5402
sdefresnee65fd872016-12-19 13:38:135403@end