blob: 3133b2a61d362f78407a6ae00c4390c62119ab60 [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"
mathp9b4c11d2017-07-06 20:24:1347#include "components/payments/core/features.h"
sdefresnee65fd872016-12-19 13:38:1348#include "components/prefs/pref_service.h"
olivierrobin52b6cd6ec2017-03-23 13:55:5449#include "components/reading_list/core/reading_list_model.h"
sdefresnee65fd872016-12-19 13:38:1350#include "components/search_engines/search_engines_pref_names.h"
51#include "components/search_engines/template_url_service.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0552#include "components/sessions/core/session_types.h"
sdefresnee65fd872016-12-19 13:38:1353#include "components/sessions/core/tab_restore_service_helper.h"
edchincd32fdf2017-10-25 12:45:4554#include "components/signin/core/browser/account_reconcilor.h"
55#include "components/signin/core/browser/signin_metrics.h"
56#import "components/signin/ios/browser/account_consistency_service.h"
Eugene Butc90499d52017-09-22 16:02:0957#include "components/signin/ios/browser/active_state_manager.h"
sdefresnee65fd872016-12-19 13:38:1358#include "components/strings/grit/components_strings.h"
59#include "components/toolbar/toolbar_model_impl.h"
60#include "ios/chrome/app/tests_hook.h"
61#include "ios/chrome/browser/bookmarks/bookmark_model_factory.h"
62#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
63#include "ios/chrome/browser/chrome_url_constants.h"
64#include "ios/chrome/browser/chrome_url_util.h"
65#include "ios/chrome/browser/experimental_flags.h"
66#import "ios/chrome/browser/favicon/favicon_loader.h"
67#include "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
Tommy Nyquistc1d6dea12017-07-26 20:37:2368#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
69#include "ios/chrome/browser/feature_engagement/tracker_util.h"
sdefresnee65fd872016-12-19 13:38:1370#import "ios/chrome/browser/find_in_page/find_in_page_controller.h"
71#import "ios/chrome/browser/find_in_page/find_in_page_model.h"
rohitraob2bf3cb2017-02-10 14:10:3672#import "ios/chrome/browser/find_in_page/find_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1373#include "ios/chrome/browser/first_run/first_run.h"
74#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
75#include "ios/chrome/browser/infobars/infobar_container_ios.h"
76#include "ios/chrome/browser/infobars/infobar_container_view.h"
Rohit Raoaf46af92017-08-10 12:52:3077#include "ios/chrome/browser/infobars/infobar_manager_impl.h"
sdefresnee65fd872016-12-19 13:38:1378#import "ios/chrome/browser/metrics/new_tab_page_uma.h"
79#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
sdefresnee65fd872016-12-19 13:38:1380#import "ios/chrome/browser/open_url_util.h"
81#import "ios/chrome/browser/passwords/password_controller.h"
Tomasz Garbusb844e992017-09-29 12:44:5582#include "ios/chrome/browser/passwords/password_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:1383#include "ios/chrome/browser/pref_names.h"
Rohit Rao44f204302017-08-10 14:49:5484#import "ios/chrome/browser/prerender/preload_controller_delegate.h"
85#import "ios/chrome/browser/prerender/prerender_service.h"
86#import "ios/chrome/browser/prerender/prerender_service_factory.h"
olivierrobin013ba672017-03-01 21:16:2487#include "ios/chrome/browser/reading_list/offline_url_utils.h"
sdefresnee65fd872016-12-19 13:38:1388#include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
89#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
90#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
Sylvain Defresnef2e00d9b2017-08-24 10:54:0591#include "ios/chrome/browser/sessions/session_util.h"
sdefresnee65fd872016-12-19 13:38:1392#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios.h"
93#include "ios/chrome/browser/sessions/tab_restore_service_delegate_impl_ios_factory.h"
edchincd32fdf2017-10-25 12:45:4594#import "ios/chrome/browser/signin/account_consistency_service_factory.h"
95#include "ios/chrome/browser/signin/account_reconcilor_factory.h"
sdefresnee65fd872016-12-19 13:38:1396#import "ios/chrome/browser/snapshots/snapshot_cache.h"
97#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
98#import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h"
Mike Dougherty4620cf8e2017-10-31 23:37:0999#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
100#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper_delegate.h"
pkld6e73e52017-03-08 15:56:51101#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
sdefresne0452a9d2017-02-09 15:33:28102#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13103#import "ios/chrome/browser/tabs/tab.h"
104#import "ios/chrome/browser/tabs/tab_dialog_delegate.h"
olivierrobin9ce77b82017-01-12 17:29:19105#import "ios/chrome/browser/tabs/tab_headers_delegate.h"
sdefresnee65fd872016-12-19 13:38:13106#import "ios/chrome/browser/tabs/tab_model.h"
107#import "ios/chrome/browser/tabs/tab_model_observer.h"
Sylvain Defresne72c530e42017-08-25 15:28:16108#import "ios/chrome/browser/tabs/tab_private.h"
sdefresnee65fd872016-12-19 13:38:13109#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
Rohit Rao01e0e002017-08-14 20:49:43110#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
111#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
sdefresnee65fd872016-12-19 13:38:13112#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
Eugene But35ded552017-09-13 23:31:59113#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13114#import "ios/chrome/browser/ui/authentication/re_signin_infobar_delegate.h"
115#import "ios/chrome/browser/ui/background_generator.h"
116#import "ios/chrome/browser/ui/bookmarks/bookmark_interaction_controller.h"
117#import "ios/chrome/browser/ui/browser_container_view.h"
sdefresnee65fd872016-12-19 13:38:13118#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h"
Cooper Knaak33f9f402017-08-09 18:04:38119#import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
sdefresnee65fd872016-12-19 13:38:13120#import "ios/chrome/browser/ui/chrome_web_view_factory.h"
Mark Cogan5e3da152017-07-11 15:57:30121#import "ios/chrome/browser/ui/commands/application_commands.h"
Mark Cogan6c58ea92017-07-06 13:08:24122#import "ios/chrome/browser/ui/commands/browser_commands.h"
edchin9badb062017-08-16 18:47:54123#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
Mark Cogandfcdea72017-07-18 13:47:38124#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
sdefresnee65fd872016-12-19 13:38:13125#import "ios/chrome/browser/ui/commands/open_url_command.h"
126#import "ios/chrome/browser/ui/commands/reading_list_add_command.h"
edchin95c927072017-11-04 00:35:07127#import "ios/chrome/browser/ui/commands/show_signin_command.h"
edchin7f210cd2017-09-28 08:03:53128#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
Jean-François Geyelin5d2e184c2017-07-28 19:48:00129#import "ios/chrome/browser/ui/commands/start_voice_search_command.h"
Gauthier Ambardf520c022017-08-29 07:42:23130#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
sdefresnee65fd872016-12-19 13:38:13131#import "ios/chrome/browser/ui/context_menu/context_menu_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13132#import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
133#import "ios/chrome/browser/ui/dialogs/java_script_dialog_presenter_impl.h"
134#import "ios/chrome/browser/ui/elements/activity_overlay_coordinator.h"
135#import "ios/chrome/browser/ui/external_file_controller.h"
Louis Romerod11747a2017-10-20 20:10:35136#import "ios/chrome/browser/ui/external_search/external_search_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13137#import "ios/chrome/browser/ui/find_bar/find_bar_controller_ios.h"
138#import "ios/chrome/browser/ui/first_run/welcome_to_chrome_view_controller.h"
Kurt Horimoto803840622017-10-28 01:20:37139#import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
Kurt Horimoto62e97c72017-11-03 19:51:47140#import "ios/chrome/browser/ui/fullscreen/legacy_fullscreen_controller.h"
sczsdd860eba2017-08-10 01:55:38141#import "ios/chrome/browser/ui/history_popup/requirements/tab_history_presentation.h"
sczs0a726d22017-08-21 22:40:13142#import "ios/chrome/browser/ui/history_popup/tab_history_legacy_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13143#import "ios/chrome/browser/ui/key_commands_provider.h"
Kurt Horimoto1945ef42017-10-26 03:57:26144#import "ios/chrome/browser/ui/location_bar_notification_names.h"
Rohit Rao9a8ad772017-10-30 22:35:59145#import "ios/chrome/browser/ui/main/main_feature_flags.h"
Gauthier Ambard5bb5f7a2017-09-06 12:58:10146#import "ios/chrome/browser/ui/ntp/modal_ntp.h"
sdefresnee65fd872016-12-19 13:38:13147#import "ios/chrome/browser/ui/ntp/new_tab_page_controller.h"
Gauthier Ambardd4287fc2017-08-29 09:14:42148#import "ios/chrome/browser/ui/ntp/recent_tabs/recent_tabs_handset_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13149#import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
Gregory Chatzinoffdf93d692017-09-09 01:32:27150#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
151#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
sdefresnee65fd872016-12-19 13:38:13152#import "ios/chrome/browser/ui/page_not_available_controller.h"
mahmadi1acec7042017-04-24 08:29:37153#import "ios/chrome/browser/ui/payments/payment_request_manager.h"
sdefresnee65fd872016-12-19 13:38:13154#import "ios/chrome/browser/ui/print/print_controller.h"
Rohit Raocda0a992017-08-16 15:37:11155#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
156#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
sdefresnee65fd872016-12-19 13:38:13157#import "ios/chrome/browser/ui/reading_list/offline_page_native_content.h"
gambard6299cc1d2017-02-21 13:06:03158#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13159#import "ios/chrome/browser/ui/reading_list/reading_list_menu_notifier.h"
sdefresnee65fd872016-12-19 13:38:13160#include "ios/chrome/browser/ui/rtl_geometry.h"
sczs6ae47ad2017-09-06 17:26:53161#import "ios/chrome/browser/ui/sad_tab/sad_tab_legacy_coordinator.h"
edchin95c927072017-11-04 00:35:07162#import "ios/chrome/browser/ui/settings/sync_utils/sync_presenter.h"
sczs40443972017-09-13 19:02:39163#import "ios/chrome/browser/ui/settings/sync_utils/sync_util.h"
sdefresnee65fd872016-12-19 13:38:13164#import "ios/chrome/browser/ui/side_swipe/side_swipe_controller.h"
edchin9e7a1112017-11-07 18:28:03165#import "ios/chrome/browser/ui/signin_interaction/public/signin_presenter.h"
edchin7f210cd2017-09-28 08:03:53166#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13167#import "ios/chrome/browser/ui/stack_view/card_view.h"
168#import "ios/chrome/browser/ui/stack_view/page_animation_util.h"
169#import "ios/chrome/browser/ui/static_content/static_html_native_content.h"
sdefresnee65fd872016-12-19 13:38:13170#import "ios/chrome/browser/ui/tab_switcher/tab_switcher_controller.h"
edchinf5150c682017-09-18 02:50:03171#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_constants.h"
172#import "ios/chrome/browser/ui/tabs/requirements/tab_strip_presentation.h"
173#import "ios/chrome/browser/ui/tabs/tab_strip_legacy_coordinator.h"
sczsf1620e52017-10-02 22:54:46174#include "ios/chrome/browser/ui/toolbar/toolbar_coordinator.h"
sdefresnee65fd872016-12-19 13:38:13175#include "ios/chrome/browser/ui/toolbar/toolbar_model_delegate_ios.h"
176#include "ios/chrome/browser/ui/toolbar/toolbar_model_ios.h"
Gauthier Ambard29939db12017-10-30 16:47:31177#import "ios/chrome/browser/ui/toolbar/toolbar_snapshot_providing.h"
sczsc2b4f152017-10-11 01:44:24178#import "ios/chrome/browser/ui/toolbar/web_toolbar_controller.h"
sczsbbad1632017-07-29 03:48:00179#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
sdefresnee65fd872016-12-19 13:38:13180#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
181#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
182#include "ios/chrome/browser/ui/ui_util.h"
183#import "ios/chrome/browser/ui/uikit_ui_util.h"
gambard6a138362017-02-06 17:19:28184#import "ios/chrome/browser/ui/util/pasteboard_util.h"
sdefresnee65fd872016-12-19 13:38:13185#import "ios/chrome/browser/ui/voice/text_to_speech_player.h"
186#include "ios/chrome/browser/upgrade/upgrade_center.h"
eugenebut275f5892017-03-09 22:20:51187#import "ios/chrome/browser/web/blocked_popup_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13188#import "ios/chrome/browser/web/error_page_content.h"
Danyao Wang85389a82017-10-25 18:56:27189#import "ios/chrome/browser/web/load_timing_tab_helper.h"
sdefresnee65fd872016-12-19 13:38:13190#import "ios/chrome/browser/web/passkit_dialog_provider.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04191#include "ios/chrome/browser/web/print_tab_helper.h"
eugenebutcae3d9e62017-01-27 20:01:05192#import "ios/chrome/browser/web/repost_form_tab_helper.h"
Eugene But35ded552017-09-13 23:31:59193#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
sczs6ae47ad2017-09-06 17:26:53194#import "ios/chrome/browser/web/sad_tab_tab_helper.h"
Sylvain Defresnecacc3a52017-09-12 13:51:04195#include "ios/chrome/browser/web/web_state_printer.h"
sdefresne62a00bb2017-04-10 15:36:05196#import "ios/chrome/browser/web_state_list/web_state_list.h"
197#import "ios/chrome/browser/web_state_list/web_state_opener.h"
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57198#import "ios/chrome/browser/webui/net_export_tab_helper.h"
199#import "ios/chrome/browser/webui/net_export_tab_helper_delegate.h"
200#import "ios/chrome/browser/webui/show_mail_composer_context.h"
sdefresnee65fd872016-12-19 13:38:13201#include "ios/chrome/grit/ios_chromium_strings.h"
202#include "ios/chrome/grit/ios_strings.h"
203#import "ios/net/request_tracker.h"
204#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
205#include "ios/public/provider/chrome/browser/ui/app_rating_prompt.h"
206#include "ios/public/provider/chrome/browser/ui/default_ios_web_view_factory.h"
207#import "ios/public/provider/chrome/browser/voice/voice_search_bar.h"
208#import "ios/public/provider/chrome/browser/voice/voice_search_bar_owner.h"
209#include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
210#include "ios/public/provider/chrome/browser/voice/voice_search_controller_delegate.h"
211#include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
edchineeb4d422017-10-02 17:39:36212#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
sdefresnee65fd872016-12-19 13:38:13213#include "ios/web/public/navigation_item.h"
214#import "ios/web/public/navigation_manager.h"
215#include "ios/web/public/referrer_util.h"
216#include "ios/web/public/ssl_status.h"
217#include "ios/web/public/url_scheme_util.h"
liaoyukeea9f3ee62017-03-07 22:05:39218#include "ios/web/public/user_agent.h"
sdefresnee65fd872016-12-19 13:38:13219#include "ios/web/public/web_client.h"
220#import "ios/web/public/web_state/context_menu_params.h"
sdefresnee65fd872016-12-19 13:38:13221#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
eugenebut46487992017-03-16 17:21:29222#import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
Sylvain Defresnee7f2c8a2017-10-17 02:39:19223#import "ios/web/public/web_state/web_state.h"
sdefresnee65fd872016-12-19 13:38:13224#import "ios/web/public/web_state/web_state_delegate_bridge.h"
225#include "ios/web/public/web_thread.h"
226#import "ios/web/web_state/ui/crw_web_controller.h"
227#import "net/base/mac/url_conversions.h"
gambard9efce7a2017-02-09 18:53:17228#include "net/base/mime_util.h"
sdefresnee65fd872016-12-19 13:38:13229#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
230#include "net/ssl/ssl_info.h"
231#include "net/url_request/url_request_context_getter.h"
232#include "third_party/google_toolbox_for_mac/src/iPhone/GTMUIImage+Resize.h"
233#include "ui/base/l10n/l10n_util.h"
234#include "ui/base/l10n/l10n_util_mac.h"
235#include "ui/base/page_transition_types.h"
236#include "url/gurl.h"
237
stkhapuginf58b10d02017-04-10 13:36:17238#if !defined(__has_feature) || !__has_feature(objc_arc)
239#error "This file requires ARC support."
240#endif
241
sdefresnee65fd872016-12-19 13:38:13242using base::UserMetricsAction;
243using bookmarks::BookmarkNode;
244
245class BrowserBookmarkModelBridge;
246class InfoBarContainerDelegateIOS;
247
sdefresnee65fd872016-12-19 13:38:13248namespace {
249
250typedef NS_ENUM(NSInteger, ContextMenuHistogram) {
251 // Note: these values must match the ContextMenuOption enum in histograms.xml.
252 ACTION_OPEN_IN_NEW_TAB = 0,
253 ACTION_OPEN_IN_INCOGNITO_TAB = 1,
254 ACTION_COPY_LINK_ADDRESS = 2,
255 ACTION_SAVE_IMAGE = 6,
256 ACTION_OPEN_IMAGE = 7,
257 ACTION_OPEN_IMAGE_IN_NEW_TAB = 8,
258 ACTION_SEARCH_BY_IMAGE = 11,
259 ACTION_OPEN_JAVASCRIPT = 21,
260 ACTION_READ_LATER = 22,
261 NUM_ACTIONS = 23,
262};
263
Wei-Yin Chen (陳威尹)223326c2017-07-21 02:08:28264void Record(ContextMenuHistogram action, bool is_image, bool is_link) {
sdefresnee65fd872016-12-19 13:38:13265 if (is_image) {
266 if (is_link) {
267 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.ImageLink", action,
268 NUM_ACTIONS);
269 } else {
270 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Image", action,
271 NUM_ACTIONS);
272 }
273 } else {
274 UMA_HISTOGRAM_ENUMERATION("ContextMenu.SelectedOption.Link", action,
275 NUM_ACTIONS);
276 }
277}
278
edchinf5150c682017-09-18 02:50:03279// Returns the status bar background color.
280UIColor* StatusBarBackgroundColor() {
281 return [UIColor colorWithRed:0.149 green:0.149 blue:0.164 alpha:1];
282}
283
284// Duration of the toolbar animation.
Kurt Horimoto62e97c72017-11-03 19:51:47285const NSTimeInterval kLegacyFullscreenControllerToolbarAnimationDuration = 0.3;
edchinf5150c682017-09-18 02:50:03286
sdefresnee65fd872016-12-19 13:38:13287const CGFloat kVoiceSearchBarHeight = 59.0;
288
289// Dimensions to use when downsizing an image for search-by-image.
290const CGFloat kSearchByImageMaxImageArea = 90000.0;
291const CGFloat kSearchByImageMaxImageWidth = 600.0;
292const CGFloat kSearchByImageMaxImageHeight = 400.0;
293
sdefresnee65fd872016-12-19 13:38:13294enum HeaderBehaviour {
295 // The header moves completely out of the screen.
296 Hideable = 0,
297 // This header stays on screen and doesn't overlap with the content.
298 Visible,
299 // This header stay on screen and covers part of the content.
300 Overlap
301};
302
sdefresnee65fd872016-12-19 13:38:13303const CGFloat kIPadFindBarOverlap = 11;
304
305bool IsURLAllowedInIncognito(const GURL& url) {
dbeam25b548f2017-05-05 18:05:24306 // Most URLs are allowed in incognito; the following is an exception.
307 return !(url.SchemeIs(kChromeUIScheme) && url.host() == kChromeUIHistoryHost);
sdefresnee65fd872016-12-19 13:38:13308}
309
edchineeb4d422017-10-02 17:39:36310// Snackbar category for browser view controller.
311NSString* const kBrowserViewControllerSnackbarCategory =
312 @"BrowserViewControllerSnackbarCategory";
313
rohitrao005a6432017-03-16 20:52:42314} // namespace
sdefresnee65fd872016-12-19 13:38:13315
stkhapugin952ecef2017-04-11 12:11:45316#pragma mark - HeaderDefinition helper
317
318@interface HeaderDefinition : NSObject
319
320// The header view.
321@property(nonatomic, strong) UIView* view;
322// How to place the view, and its behaviour when the headers move.
323@property(nonatomic, assign) HeaderBehaviour behaviour;
324// Reduces the height of a header to adjust for shadows.
325@property(nonatomic, assign) CGFloat heightAdjustement;
326// Nudges that particular header up by this number of points.
327@property(nonatomic, assign) CGFloat inset;
328
329- (instancetype)initWithView:(UIView*)view
330 headerBehaviour:(HeaderBehaviour)behaviour
331 heightAdjustment:(CGFloat)heightAdjustment
332 inset:(CGFloat)inset;
333
334+ (instancetype)definitionWithView:(UIView*)view
335 headerBehaviour:(HeaderBehaviour)behaviour
336 heightAdjustment:(CGFloat)heightAdjustment
337 inset:(CGFloat)inset;
338
339@end
340
341@implementation HeaderDefinition
342@synthesize view = _view;
343@synthesize behaviour = _behaviour;
344@synthesize heightAdjustement = _heightAdjustement;
345@synthesize inset = _inset;
346
347+ (instancetype)definitionWithView:(UIView*)view
348 headerBehaviour:(HeaderBehaviour)behaviour
349 heightAdjustment:(CGFloat)heightAdjustment
350 inset:(CGFloat)inset {
351 return [[self alloc] initWithView:view
352 headerBehaviour:behaviour
353 heightAdjustment:heightAdjustment
354 inset:inset];
355}
356
357- (instancetype)initWithView:(UIView*)view
358 headerBehaviour:(HeaderBehaviour)behaviour
359 heightAdjustment:(CGFloat)heightAdjustment
360 inset:(CGFloat)inset {
361 self = [super init];
362 if (self) {
363 _view = view;
364 _behaviour = behaviour;
365 _heightAdjustement = heightAdjustment;
366 _inset = inset;
367 }
368 return self;
369}
370
371@end
372
373#pragma mark - BVC
374
Rohit Rao01e0e002017-08-14 20:49:43375@interface BrowserViewController ()<ActivityServicePresentation,
Rohit Rao01e0e002017-08-14 20:49:43376 AppRatingPromptDelegate,
Mike Dougherty4620cf8e2017-10-31 23:37:09377 CaptivePortalDetectorTabHelperDelegate,
sdefresnee65fd872016-12-19 13:38:13378 CRWNativeContentProvider,
379 CRWWebStateDelegate,
380 DialogPresenterDelegate,
Kurt Horimoto62e97c72017-11-03 19:51:47381 LegacyFullscreenControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13382 KeyCommandsPlumbing,
Gregory Chatzinoff5f9f7f02017-09-19 02:04:57383 NetExportTabHelperDelegate,
edchincd32fdf2017-10-25 12:45:45384 ManageAccountsDelegate,
sdefresnee65fd872016-12-19 13:38:13385 MFMailComposeViewControllerDelegate,
386 NewTabPageControllerObserver,
387 OverscrollActionsControllerDelegate,
Gregory Chatzinoffdf93d692017-09-09 01:32:27388 PageInfoPresentation,
sdefresnee65fd872016-12-19 13:38:13389 PassKitDialogProvider,
Tomasz Garbusb844e992017-09-29 12:44:55390 PasswordControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13391 PreloadControllerDelegate,
Rohit Raocda0a992017-08-16 15:37:11392 QRScannerPresenting,
Eugene But35ded552017-09-13 23:31:59393 RepostFormTabHelperDelegate,
Gauthier Ambard29939db12017-10-30 16:47:31394 SideSwipeControllerDelegate,
sdefresnee65fd872016-12-19 13:38:13395 SKStoreProductViewControllerDelegate,
396 SnapshotOverlayProvider,
397 StoreKitLauncher,
edchin9e7a1112017-11-07 18:28:03398 SigninPresenter,
edchin95c927072017-11-04 00:35:07399 SyncPresenter,
sdefresnee65fd872016-12-19 13:38:13400 TabDialogDelegate,
olivierrobin9ce77b82017-01-12 17:29:19401 TabHeadersDelegate,
sczsdd860eba2017-08-10 01:55:38402 TabHistoryPresentation,
sdefresnee65fd872016-12-19 13:38:13403 TabModelObserver,
404 TabSnapshottingDelegate,
edchinf5150c682017-09-18 02:50:03405 TabStripPresentation,
sdefresnee65fd872016-12-19 13:38:13406 UIGestureRecognizerDelegate,
407 UpgradeCenterClientProtocol,
408 VoiceSearchBarDelegate,
Sylvain Defresnecacc3a52017-09-12 13:51:04409 VoiceSearchBarOwner,
410 WebStatePrinter> {
sdefresnee65fd872016-12-19 13:38:13411 // The dependency factory passed on initialization. Used to vend objects used
412 // by the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27413 BrowserViewControllerDependencyFactory* _dependencyFactory;
sdefresnee65fd872016-12-19 13:38:13414
415 // The browser's tab model.
stkhapuginc9eee7b2017-04-10 15:49:27416 TabModel* _model;
sdefresnee65fd872016-12-19 13:38:13417
sczsf1620e52017-10-02 22:54:46418 // Facade objects used by |_toolbarCoordinator|.
419 // Must outlive |_toolbarCoordinator|.
sdefresnee65fd872016-12-19 13:38:13420 std::unique_ptr<ToolbarModelDelegateIOS> _toolbarModelDelegate;
421 std::unique_ptr<ToolbarModelIOS> _toolbarModelIOS;
422
sdefresnee65fd872016-12-19 13:38:13423 // Controller for edge swipe gestures for page and tab navigation.
stkhapuginc9eee7b2017-04-10 15:49:27424 SideSwipeController* _sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13425
426 // Handles displaying the context menu for all form factors.
stkhapuginc9eee7b2017-04-10 15:49:27427 ContextMenuCoordinator* _contextMenuCoordinator;
sdefresnee65fd872016-12-19 13:38:13428
429 // Backing object for property of the same name.
stkhapuginc9eee7b2017-04-10 15:49:27430 DialogPresenter* _dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13431
432 // Handles presentation of JavaScript dialogs.
433 std::unique_ptr<JavaScriptDialogPresenterImpl> _javaScriptDialogPresenter;
434
justincohen75011c32017-04-28 16:31:39435 // Handles command dispatching.
436 CommandDispatcher* _dispatcher;
437
sdefresnee65fd872016-12-19 13:38:13438 // Keyboard commands provider. It offloads most of the keyboard commands
439 // management off of the BVC.
stkhapuginc9eee7b2017-04-10 15:49:27440 KeyCommandsProvider* _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13441
442 // Calls to |-relinquishedToolbarController| will set this to yes, and calls
443 // to |-reparentToolbarController| will reset it to NO.
444 BOOL _isToolbarControllerRelinquished;
445
446 // The controller that owns the currently relinquished toolbar controller.
447 // The reference is weak because it's possible for the toolbar owner to be
448 // deallocated mid-animation due to memory pressure or a tab being closed
449 // before the animation is finished.
stkhapuginc9eee7b2017-04-10 15:49:27450 __weak id _relinquishedToolbarOwner;
sdefresnee65fd872016-12-19 13:38:13451
sdefresnee65fd872016-12-19 13:38:13452 // Used to inject Javascript implementing the PaymentRequest API and to
453 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27454 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13455
sdefresnee65fd872016-12-19 13:38:13456 // Used to display the Voice Search UI. Nil if not visible.
457 scoped_refptr<VoiceSearchController> _voiceSearchController;
458
gambard6299cc1d2017-02-21 13:06:03459 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27460 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03461
sdefresnee65fd872016-12-19 13:38:13462 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27463 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13464
sdefresnee65fd872016-12-19 13:38:13465 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27466 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13467
468 // Records the set of domains for which full screen alert has already been
469 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27470 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13471
472 // Adapter to let BVC be the delegate for WebState.
473 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
474
475 // YES if new tab is animating in.
476 BOOL _inNewTabAnimation;
477
478 // YES if Voice Search should be started when the new tab animation is
479 // finished.
480 BOOL _startVoiceSearchAfterNewTabAnimation;
481
482 // YES if the user interacts with the location bar.
483 BOOL _locationBarHasFocus;
484 // YES if a load was cancelled due to typing in the location bar.
485 BOOL _locationBarEditCancelledLoad;
486 // YES if waiting for a foreground tab due to expectNewForegroundTab.
487 BOOL _expectingForegroundTab;
488
Sylvain Defresne41170aa2017-06-15 10:25:20489 // Whether or not -shutdown has been called.
490 BOOL _isShutdown;
491
sdefresnee65fd872016-12-19 13:38:13492 // The ChromeBrowserState associated with this BVC.
493 ios::ChromeBrowserState* _browserState; // weak
494
495 // Whether or not Incognito* is enabled.
496 BOOL _isOffTheRecord;
497
498 // The last point within |_contentArea| that's received a touch.
499 CGPoint _lastTapPoint;
500
501 // The time at which |_lastTapPoint| was most recently set.
502 CFTimeInterval _lastTapTime;
503
504 // A single infobar container handles all infobars in all tabs. It keeps
505 // track of infobars for current tab (accessed via infobar helper of
506 // the current tab).
507 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
508
509 // Bridge class to deliver container change notifications to BVC.
510 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
511
512 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37513 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27514 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13515
516 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11517 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13518
sdefresnee65fd872016-12-19 13:38:13519 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27520 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13521
522 // Bridge to register for bookmark changes.
523 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
524
525 // Cached pointer to the bookmarks model.
526 bookmarks::BookmarkModel* _bookmarkModel; // weak
527
528 // The controller that shows the bookmarking UI after the user taps the star
529 // button.
stkhapuginc9eee7b2017-04-10 15:49:27530 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13531
sdefresnee65fd872016-12-19 13:38:13532 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27533 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13534
Eugene But56efc322017-08-11 14:03:44535 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28536 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13537
538 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27539 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13540
Jean-François Geyelin3d47c212017-08-03 09:24:09541 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27542 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13543
Rohit Rao01e0e002017-08-14 20:49:43544 // Coordinator for the share menu (Activity Services).
545 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
546
sdefresnee65fd872016-12-19 13:38:13547 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27548 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38549
Rohit Raocda0a992017-08-16 15:37:11550 // Coordinator for the QR scanner.
551 QRScannerLegacyCoordinator* _qrScannerCoordinator;
552
sczsdd860eba2017-08-10 01:55:38553 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13554 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53555
556 // Coordinator for displaying Sad Tab.
557 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27558
559 // Coordinator for Page Info UI.
560 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59561
562 // Coordinator for displaying Repost Form dialog.
563 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22564
edchin7f210cd2017-09-28 08:03:53565 // Coordinator for displaying snackbars.
566 SnackbarCoordinator* _snackbarCoordinator;
567
sczsf1620e52017-10-02 22:54:46568 // Coordinator for the toolbar.
569 LegacyToolbarCoordinator* _toolbarCoordinator;
570
Louis Romerod11747a2017-10-20 20:10:35571 // Coordinator for the External Search UI.
572 ExternalSearchCoordinator* _externalSearchCoordinator;
573
Justin Cohenb3170c32017-09-19 01:55:22574 // Fake status bar view used to blend the toolbar into the status bar.
575 UIView* _fakeStatusBarView;
sdefresnee65fd872016-12-19 13:38:13576}
577
578// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17579@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13580// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17581@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13582// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17583@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13584// Whether the current tab can enable the request desktop menu item.
585@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
586// Whether the sharing menu should be enabled.
587@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
588// Helper method to check web controller canShowFindBar method.
589@property(nonatomic, assign, readonly) BOOL canShowFindBar;
590// Whether the controller's view is currently available.
591// YES from viewWillAppear to viewWillDisappear.
592@property(nonatomic, assign, getter=isVisible) BOOL visible;
593// Whether the controller's view is currently visible.
594// YES from viewDidAppear to viewWillDisappear.
595@property(nonatomic, assign) BOOL viewVisible;
596// Whether the controller is currently dismissing a presented view controller.
597@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
598// Returns YES if the toolbar has not been scrolled out by fullscreen.
599@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
600 BOOL toolbarOnScreen;
601// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12602@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13603// Whether BVC prefers to hide the status bar. This value is used to determine
604// the response from the |prefersStatusBarHidden| method.
605@property(nonatomic, assign) BOOL hideStatusBar;
606// Whether the VoiceSearchBar should be displayed.
607@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
608// Coordinator for displaying a modal overlay with activity indicator to prevent
609// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17610@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13611 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39612// A block to be run when the |tabWasAdded:| method completes the animation
613// for the presentation of a new tab. Can be used to record performance metrics.
614@property(nonatomic, strong, nullable)
615 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42616// Coordinator for Recent Tabs.
617@property(nonatomic, strong)
618 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03619// Coordinator for tablet tab strip.
620@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
621// A weak reference to the view of the tab strip on tablet.
622@property(nonatomic, weak) UIView* tabStripView;
sdefresnee65fd872016-12-19 13:38:13623
liaoyukeea9f3ee62017-03-07 22:05:39624// The user agent type used to load the currently visible page. User agent type
625// is NONE if there is no visible page or visible page is a native page.
626@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
627
stkhapugin952ecef2017-04-11 12:11:45628// Returns the header views, all the chrome on top of the page, including the
629// ones that cannot be scrolled off screen by full screen.
630@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
631
Cooper Knaakd0a974cd2017-08-10 18:05:47632// Used to display the new tab tip in-product help promotion bubble. |nil| if
633// the new tab tip bubble has not yet been presented. Once the bubble is
634// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38635@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47636 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38637
Helen Yang9175bd52017-08-12 00:28:40638// Used to display the new incognito tab tip in-product help promotion bubble.
639@property(nonatomic, strong)
640 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
641
sdefresnee65fd872016-12-19 13:38:13642// BVC initialization:
643// If the BVC is initialized with a valid browser state & tab model immediately,
644// the path is straightforward: functionality is enabled, and the UI is built
645// when -viewDidLoad is called.
646// If the BVC is initialized without a browser state or tab model, the tab model
647// and browser state may or may not be provided before -viewDidLoad is called.
648// In most cases, they will not, to improve startup performance.
649// In order to handle this, initialization of various aspects of BVC have been
650// broken out into the following functions, which have expectations (enforced
651// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
652
653// Registers for notifications.
654- (void)registerForNotifications;
655// Called when a tab is starting to load. If it's a link click or form
656// submission, the user is navigating away from any entries in the forward
657// history. Tell the toolbar so it can update the UI appropriately.
658// See the warning on [Tab webWillStartLoadingURL] about invocation of this
659// method sequence by malicious pages.
660- (void)pageLoadStarting:(NSNotification*)notify;
661// Called when a tab actually starts loading.
662- (void)pageLoadStarted:(NSNotification*)notify;
663// Called when a tab finishes loading. Update the Omnibox with the url and
664// stop any page load progess display.
665- (void)pageLoadComplete:(NSNotification*)notify;
666// Called when a tab is deselected in the model.
667// This notification also occurs when a tab is closed.
668- (void)tabDeselected:(NSNotification*)notify;
669// Animates sliding current tab and rotate-entering new tab while new tab loads
670// in background on the iPhone only.
671- (void)tabWasAdded:(NSNotification*)notify;
672
673// Updates non-view-related functionality with the given browser state and tab
674// model.
675// Does not matter whether or not the view has been loaded.
676- (void)updateWithTabModel:(TabModel*)model
677 browserState:(ios::ChromeBrowserState*)browserState;
678// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
679// the status bar to mimic this layout.
680- (void)installFakeStatusBar;
681// Builds the UI parts of tab strip and the toolbar. Does not matter whether
682// or not browser state and tab model are valid.
683- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50684// Sets up the constraints on the toolbar.
685- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13686// Updates view-related functionality with the given tab model and browser
687// state. The view must have been loaded. Uses |_browserState| and |_model|.
688- (void)addUIFunctionalityForModelAndBrowserState;
Julien Brianceaub7e590ac2017-08-01 17:30:22689// Sets the correct frame and hierarchy for subviews and helper views.
sdefresnee65fd872016-12-19 13:38:13690- (void)setUpViewLayout;
sdefresnee65fd872016-12-19 13:38:13691// Makes |tab| the currently visible tab, displaying its view. Calls
692// -selectedTabChanged on the toolbar only if |newSelection| is YES.
693- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
694// Initializes the bookmark interaction controller if not already initialized.
695- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13696// Add all delegates to the provided |tab|.
697- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14698// Remove delegates from the provided |tab|.
699- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13700// Closes the current tab, with animation if applicable.
701- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13702// Show the bookmarks page.
703- (void)showAllBookmarks;
704// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23705- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13706// Dismisses the "rate this app" dialog.
707- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32708// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13709- (BOOL)isTabNativePage:(Tab*)tab;
710// Returns the view to use when animating a page in or out, positioning it to
711// fill the content area but not actually adding it to the view hierarchy.
712- (UIImageView*)pageOpenCloseAnimationView;
713// Returns the view to use when animating full screen NTP paper in, filling the
714// entire screen but not actually adding it to the view hierarchy.
715- (UIImageView*)pageFullScreenOpenCloseAnimationView;
716// Updates the toolbar display based on the current tab.
717- (void)updateToolbar;
718// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12719// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13720- (void)updateDialogPresenterActiveState;
721// Dismisses popups and modal dialogs that are displayed above the BVC upon size
722// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
723// is performed.
724// TODO(crbug.com/522721): Support size changes for all popups and modal
725// dialogs.
726- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47727
728// Returns a bubble associated with an in-product help promotion if
729// it is valid to show the promotion and |nil| otherwise. |feature| is the
730// base::Feature object associated with the given promotion. |direction| is the
731// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21732// arrow on the button. |text| is the text displayed by the bubble. This method
733// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47734- (BubbleViewControllerPresenter*)
735bubblePresenterForFeature:(const base::Feature&)feature
736 direction:(BubbleArrowDirection)direction
737 alignment:(BubbleAlignment)alignment
738 text:(NSString*)text;
739
Cooper Knaak120cee5e2017-08-10 20:57:00740// Waits to present a bubble associated with the new tab tip in-product help
741// promotion until the feature engagement tracker database is fully initialized.
742// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
743// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11744// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21745// determines it is not valid to present it. This method requires that
746// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00747- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11748// Optionally presents a bubble associated with the new tab tip in-product help
749// promotion. If the feature engagement tracker determines it is valid to show
750// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
751// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21752// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
753// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00754- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40755// Waits to present a bubble associated with the new incognito tab tip
756// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21757// fully initialized. This method requires that |self.browserState| is
758// not NULL.
Helen Yang9175bd52017-08-12 00:28:40759- (void)presentNewIncognitoTabTipBubbleOnInitialized;
760// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21761// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40762- (void)presentNewIncognitoTabTipBubble;
Gregory Chatzinoff541b8642017-10-25 00:25:21763// Presents the New Tab Tip or New Incognito Tab Tip Bubble if one is
764// eligible. Only one can be eligible per session (as enforced by the
765// FeatureEngagementTracker). If neither is eligible, neither bubble is
766// presented. This method requires that |self.browserState| is not NULL.
767- (void)presentBubblesIfEligible;
Cooper Knaak120cee5e2017-08-10 20:57:00768
sdefresnee65fd872016-12-19 13:38:13769// Update find bar with model data. If |shouldFocus| is set to YES, the text
770// field will become first responder.
771- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13772// Hide find bar.
773- (void)hideFindBarWithAnimation:(BOOL)animate;
774// Shows find bar. If |selectText| is YES, all text inside the Find Bar
775// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
776// set to be first responder.
777- (void)showFindBarWithAnimation:(BOOL)animate
778 selectText:(BOOL)selectText
779 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36780
sdefresnee65fd872016-12-19 13:38:13781// The infobar state (typically height) has changed.
782- (void)infoBarContainerStateChanged:(bool)is_animating;
783// Adds a CardView on top of the contentArea either taking the size of the full
784// screen or just the size of the space under the header.
785// Returns the CardView that was added.
786- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
787// Called when either a tab finishes loading or when a tab with finished content
788// is added directly to the model via pre-rendering. The tab must be non-nil and
789// must be a member of the tab model controlled by this BrowserViewController.
790- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
791// Evaluates Javascript asynchronously using the current page context.
792- (void)openJavascript:(NSString*)javascript;
edchineeb4d422017-10-02 17:39:36793// Shows a self-dismissing snackbar displaying |message|.
794- (void)showSnackbar:(NSString*)message;
sdefresnee65fd872016-12-19 13:38:13795// Induces an intentional crash in the browser process.
796- (void)induceBrowserCrash;
797// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17798- (void)managePermissionAndSaveImage:(NSData*)data
799 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13800// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24801// saved as a temporary file on disk then saved in photos. Saving will happen
802// on a background sequence and the completion block will be invoked on that
803// sequence.
804- (void)saveImage:(NSData*)data
805 withFileExtension:(NSString*)fileExtension
806 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13807// Called when Chrome has been denied access to the photos or videos and the
808// user can change it.
809// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
810// settings. Dismiss previous alert if it has not been dismissed yet.
811- (void)displayImageErrorAlertWithSettingsOnMainQueue;
812// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
813// previous alert if it has not been dismissed yet.
814- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
815// Called when Chrome has been denied access to the photos or videos and the
816// user cannot change it.
817// Shows a privacy alert on the main queue, with errorContent as the message.
818// Dismisses previous alert if it has not been dismissed yet.
819- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
820// Called with the results of saving a picture in the photo album. If error is
821// nil the save succeeded.
822- (void)finishSavingImageWithError:(NSError*)error;
823// Provides a view that encompasses currently displayed infobar(s) or nil
824// if no infobar is presented.
825- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
826// Returns a vertical infobar offset relative to the tab content.
827- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
828// Provides a view that encompasses the voice search bar if it's displayed or
829// nil if the voice search bar isn't displayed.
830- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
831// Returns a vertical voice search bar offset relative to the tab content.
832- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
833// Lazily instantiates |_voiceSearchController|.
834- (void)ensureVoiceSearchControllerCreated;
835// Lazily instantiates |_voiceSearchBar| and adds it to the view.
836- (void)ensureVoiceSearchBarCreated;
837// Shows/hides the voice search bar.
838- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
839// The LogoAnimationControllerOwner to be used for the next logo transition
840// animation.
841- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13842// Returns the footer view if one exists (e.g. the voice search bar).
843- (UIView*)footerView;
844// Returns the height of the header view for the tab model's current tab.
845- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13846// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45847- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13848 atOffset:(CGFloat)headerOffset;
849// Returns the y coordinate for the footer's frame when animating the footer
850// in/out of fullscreen.
851- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
852// Called when the animation for setting the header view's offset is finished.
853// |completed| should indicate if the animation finished completely or was
854// interrupted. |offset| should indicate the header offset after the animation.
855// |dragged| should indicate if the header moved due to the user dragging.
Kurt Horimoto62e97c72017-11-03 19:51:47856- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:13857 headerAnimationCompleted:(BOOL)completed
858 offset:(CGFloat)offset;
859// Performs a search with the image at the given url. The referrer is used to
860// download the image.
861- (void)searchByImageAtURL:(const GURL&)url
862 referrer:(const web::Referrer)referrer;
863// Saves the image at the given URL on the system's album. The referrer is used
864// to download the image.
865- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
866
Mark Cogandfcdea72017-07-18 13:47:38867// Record the last tap point based on the |originPoint| (if any) passed in
868// |command|.
869- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13870// Get return the last stored |_lastTapPoint| if it's been set within the past
871// second.
872- (CGPoint)lastTapPoint;
873// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
874- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
875// Returns the native controller being used by |tab|'s web controller.
876- (id)nativeControllerForTab:(Tab*)tab;
877// Installs the BVC as overscroll actions controller of |nativeContent| if
878// needed. Sets the style of the overscroll actions toolbar.
879- (void)setOverScrollActionControllerToStaticNativeContent:
880 (StaticHtmlNativeContent*)nativeContent;
881// Whether the BVC should declare keyboard commands.
882- (BOOL)shouldRegisterKeyboardCommands;
883// Adds the given url to the reading list.
884- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
885@end
886
887class InfoBarContainerDelegateIOS
888 : public infobars::InfoBarContainer::Delegate {
889 public:
890 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
891 : controller_(controller) {}
892
893 ~InfoBarContainerDelegateIOS() override {}
894
895 private:
896 SkColor GetInfoBarSeparatorColor() const override {
897 NOTIMPLEMENTED();
898 return SK_ColorBLACK;
899 }
900
901 int ArrowTargetHeightForInfoBar(
902 size_t index,
903 const gfx::SlideAnimation& animation) const override {
904 return 0;
905 }
906
907 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
908 int arrow_target_height,
909 int bar_target_height,
910 int* arrow_height,
911 int* arrow_half_width,
912 int* bar_height) const override {
913 DCHECK_NE(-1, bar_target_height)
914 << "Infobars don't have a default height on iOS";
915 *arrow_height = 0;
916 *arrow_half_width = 0;
917 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
918 }
919
920 void InfoBarContainerStateChanged(bool is_animating) override {
921 [controller_ infoBarContainerStateChanged:is_animating];
922 }
923
924 bool DrawInfoBarArrows(int* x) const override { return false; }
925
stkhapuginf58b10d02017-04-10 13:36:17926 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13927};
928
929// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
930@interface BrowserViewController (BookmarkBridgeMethods)
931// If a bookmark matching the currentTab url is added or moved, update the
932// toolbar state so the star highlight is in sync.
933- (void)bookmarkNodeModified:(const BookmarkNode*)node;
934- (void)allBookmarksRemoved;
935@end
936
937// Handle notification that bookmarks has been removed changed so we can update
938// the bookmarked star icon.
939class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
940 public:
941 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
942 : owner_(owner) {}
943
944 ~BrowserBookmarkModelBridge() override {}
945
946 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
947 const BookmarkNode* parent,
948 int old_index,
949 const BookmarkNode* node,
950 const std::set<GURL>& removed_urls) override {
951 [owner_ bookmarkNodeModified:node];
952 }
953
954 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
955 bool ids_reassigned) override {}
956
957 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
958 const BookmarkNode* old_parent,
959 int old_index,
960 const BookmarkNode* new_parent,
961 int new_index) override {}
962
963 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
964 const BookmarkNode* parent,
965 int index) override {
966 [owner_ bookmarkNodeModified:parent->GetChild(index)];
967 }
968
969 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
970 const BookmarkNode* node) override {}
971
972 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
973 const BookmarkNode* node) override {}
974
975 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
976 const BookmarkNode* node) override {}
977
978 void BookmarkAllUserNodesRemoved(
979 bookmarks::BookmarkModel* model,
980 const std::set<GURL>& removed_urls) override {
981 [owner_ allBookmarksRemoved];
982 }
983
984 private:
stkhapuginf58b10d02017-04-10 13:36:17985 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13986};
987
988@implementation BrowserViewController
989
990@synthesize contentArea = _contentArea;
991@synthesize typingShield = _typingShield;
992@synthesize active = _active;
993@synthesize visible = _visible;
994@synthesize viewVisible = _viewVisible;
995@synthesize dismissingModal = _dismissingModal;
996@synthesize hideStatusBar = _hideStatusBar;
997@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
998@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39999@synthesize foregroundTabWasAddedCompletionBlock =
1000 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:401001@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
1002@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:421003@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:031004@synthesize tabStripCoordinator = _tabStripCoordinator;
1005@synthesize tabStripView = _tabStripView;
sdefresnee65fd872016-12-19 13:38:131006
1007#pragma mark - Object lifecycle
1008
Mark Cogan5e3da152017-07-11 15:57:301009- (instancetype)
1010 initWithTabModel:(TabModel*)model
1011 browserState:(ios::ChromeBrowserState*)browserState
1012 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
1013applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:131014 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
1015 if (self) {
1016 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:171017
stkhapuginc9eee7b2017-04-10 15:49:271018 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:271019 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
1020 presentingViewController:self];
justincohen75011c32017-04-28 16:31:391021 _dispatcher = [[CommandDispatcher alloc] init];
1022 [_dispatcher startDispatchingToTarget:self
1023 forProtocol:@protocol(UrlLoader)];
1024 [_dispatcher startDispatchingToTarget:self
1025 forProtocol:@protocol(WebToolbarDelegate)];
1026 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:241027 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:301028 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
1029 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:321030 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
1031 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
1032 // dispatched to the endpoint as well. Since this is potentially
1033 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
1034 DCHECK(!applicationCommandEndpoint ||
1035 [applicationCommandEndpoint
1036 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
1037 [_dispatcher
1038 startDispatchingToTarget:applicationCommandEndpoint
1039 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:391040
edchin7f210cd2017-09-28 08:03:531041 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
1042 _snackbarCoordinator.dispatcher = _dispatcher;
1043 [_snackbarCoordinator start];
1044
sdefresnee65fd872016-12-19 13:38:131045 _javaScriptDialogPresenter.reset(
1046 new JavaScriptDialogPresenterImpl(_dialogPresenter));
1047 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
1048 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:151049 [[UpgradeCenter sharedInstance] registerClient:self
1050 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131051 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131052 if (model && browserState)
1053 [self updateWithTabModel:model browserState:browserState];
1054 if ([[NSUserDefaults standardUserDefaults]
1055 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271056 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131057 }
1058 }
1059 return self;
1060}
1061
1062- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1063 bundle:(NSBundle*)nibBundleOrNil {
1064 NOTREACHED();
1065 return nil;
1066}
1067
1068- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1069 NOTREACHED();
1070 return nil;
1071}
1072
1073- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201074 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131075}
1076
1077#pragma mark - Accessibility
1078
1079- (BOOL)accessibilityPerformEscape {
1080 [self dismissPopups];
1081 return YES;
1082}
1083
1084#pragma mark - Properties
1085
edchin3365c7d2017-09-01 22:20:371086- (id<ApplicationCommands,
1087 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371088 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531089 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371090 UrlLoader,
1091 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561092 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531093 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1094 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241095}
1096
sdefresnee65fd872016-12-19 13:38:131097- (void)setActive:(BOOL)active {
1098 if (_active == active) {
1099 return;
1100 }
1101 _active = active;
1102
1103 // If not active, display an activity indicator overlay over the view to
1104 // prevent interaction with the web page.
1105 // TODO(crbug.com/637093): This coordinator should be managed by the
1106 // coordinator used to present BrowserViewController, when implemented.
1107 if (active) {
1108 [self.activityOverlayCoordinator stop];
1109 self.activityOverlayCoordinator = nil;
1110 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171111 self.activityOverlayCoordinator =
1112 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131113 [self.activityOverlayCoordinator start];
1114 }
1115
1116 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091117 ActiveStateManager* active_state_manager =
1118 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131119 active_state_manager->SetActive(active);
1120 }
1121
1122 [_model setWebUsageEnabled:active];
1123 [self updateDialogPresenterActiveState];
1124
1125 if (active) {
1126 // Make sure the tab (if any; it's possible to get here without a current
1127 // tab if the caller is about to create one) ends up on screen completely.
1128 Tab* currentTab = [_model currentTab];
1129 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441130 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131131 if (_expectingForegroundTab)
1132 [currentTab.webController setOverlayPreviewMode:YES];
1133 if (currentTab)
1134 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341135 } else {
1136 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131137 }
sdefresnee65fd872016-12-19 13:38:131138 [_paymentRequestManager enablePaymentRequest:active];
1139
1140 [self setNeedsStatusBarAppearanceUpdate];
1141}
1142
1143- (void)setPrimary:(BOOL)primary {
1144 [_model setPrimary:primary];
1145 if (primary) {
1146 [self updateDialogPresenterActiveState];
1147 } else {
1148 self.dialogPresenter.active = false;
1149 }
1150}
1151
1152- (BOOL)isPlayingTTS {
1153 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1154}
1155
sdefresne6165c8742017-01-16 15:42:021156- (ios::ChromeBrowserState*)browserState {
1157 return _browserState;
1158}
1159
1160- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271161 return _model;
sdefresne6165c8742017-01-16 15:42:021162}
1163
sdefresnee65fd872016-12-19 13:38:131164- (SideSwipeController*)sideSwipeController {
1165 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271166 _sideSwipeController =
1167 [[SideSwipeController alloc] initWithTabModel:_model
1168 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131169 [_sideSwipeController setSnapshotDelegate:self];
Gauthier Ambard29939db12017-10-30 16:47:311170 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sdefresnee65fd872016-12-19 13:38:131171 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031172 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131173 }
1174 return _sideSwipeController;
1175}
1176
sdefresnee65fd872016-12-19 13:38:131177- (DialogPresenter*)dialogPresenter {
1178 return _dialogPresenter;
1179}
1180
sdefresnee65fd872016-12-19 13:38:131181- (BOOL)canUseDesktopUserAgent {
1182 Tab* tab = [_model currentTab];
1183 if ([self isTabNativePage:tab])
1184 return NO;
1185
1186 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441187 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131188}
1189
1190// Whether the sharing menu should be shown.
1191- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191192 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251193 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131194}
1195
1196- (BOOL)canShowFindBar {
1197 // Make sure web controller can handle find in page.
1198 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421199 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131200 return NO;
rohitrao005a6432017-03-16 20:52:421201 }
sdefresnee65fd872016-12-19 13:38:131202
rohitrao005a6432017-03-16 20:52:421203 auto* helper = FindTabHelper::FromWebState(tab.webState);
1204 return (helper && helper->CurrentPageSupportsFindInPage() &&
1205 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131206}
1207
liaoyukeea9f3ee62017-03-07 22:05:391208- (web::UserAgentType)userAgentType {
1209 web::WebState* webState = [_model currentTab].webState;
1210 if (!webState)
1211 return web::UserAgentType::NONE;
1212 web::NavigationItem* visibleItem =
1213 webState->GetNavigationManager()->GetVisibleItem();
1214 if (!visibleItem)
1215 return web::UserAgentType::NONE;
1216
1217 return visibleItem->GetUserAgentType();
1218}
1219
sdefresnee65fd872016-12-19 13:38:131220- (void)setVisible:(BOOL)visible {
1221 if (_visible == visible)
1222 return;
1223 _visible = visible;
1224}
1225
1226- (void)setViewVisible:(BOOL)viewVisible {
1227 if (_viewVisible == viewVisible)
1228 return;
1229 _viewVisible = viewVisible;
1230 self.visible = viewVisible;
1231 [self updateDialogPresenterActiveState];
1232}
1233
1234- (BOOL)isToolbarOnScreen {
1235 return [self headerHeight] - [self currentHeaderOffset] > 0;
1236}
1237
kkhorimotoa44349c12017-04-12 23:02:121238- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1239 if (_inNewTabAnimation == inNewTabAnimation)
1240 return;
1241 _inNewTabAnimation = inNewTabAnimation;
1242 [self updateDialogPresenterActiveState];
1243}
1244
sdefresnee65fd872016-12-19 13:38:131245- (BOOL)isInNewTabAnimation {
1246 return _inNewTabAnimation;
1247}
1248
1249- (BOOL)shouldShowVoiceSearchBar {
1250 // On iPads, the voice search bar should only be shown for regular horizontal
1251 // size class configurations. It should always be shown for voice search
1252 // results Tabs on iPhones, including configurations with regular horizontal
1253 // size classes (i.e. landscape iPhone 6 Plus).
1254 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1255 UIUserInterfaceSizeClassCompact;
1256 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1257 (!IsIPadIdiom() || compactWidth);
1258}
1259
1260- (void)setHideStatusBar:(BOOL)hideStatusBar {
1261 if (_hideStatusBar == hideStatusBar)
1262 return;
1263 _hideStatusBar = hideStatusBar;
1264 [self setNeedsStatusBarAppearanceUpdate];
1265}
1266
1267#pragma mark - IBActions
1268
1269- (void)shieldWasTapped:(id)sender {
sczsf1620e52017-10-02 22:54:461270 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131271}
1272
Cooper Knaakd0a974cd2017-08-10 18:05:471273- (void)userEnteredTabSwitcher {
1274 if ([self.tabTipBubblePresenter isUserEngaged]) {
1275 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1276 }
1277}
1278
Cooper Knaake963d6702017-08-11 21:03:111279- (void)presentBubblesIfEligible {
1280 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401281 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111282}
1283
sdefresnee65fd872016-12-19 13:38:131284#pragma mark - UIViewController methods
1285
1286// Perform additional set up after loading the view, typically from a nib.
1287- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091288 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281289 initialViewsRect.origin.y += StatusBarHeight();
1290 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131291 UIViewAutoresizing initialViewAutoresizing =
1292 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1293
stkhapuginf58b10d02017-04-10 13:36:171294 self.contentArea =
1295 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131296 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171297 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131298 self.typingShield.autoresizingMask = initialViewAutoresizing;
1299 [self.typingShield addTarget:self
1300 action:@selector(shieldWasTapped:)
1301 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131302 self.view.autoresizingMask = initialViewAutoresizing;
1303 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1304 [self.view addSubview:self.contentArea];
1305 [self.view addSubview:self.typingShield];
1306 [super viewDidLoad];
1307
1308 // Install fake status bar for iPad iOS7
1309 [self installFakeStatusBar];
1310 [self buildToolbarAndTabStrip];
1311 [self setUpViewLayout];
Justin Cohenba27610e2017-11-08 19:34:451312 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:501313 [self addConstraintsToToolbar];
1314 }
sdefresnee65fd872016-12-19 13:38:131315 // If the tab model and browser state are valid, finish initialization.
1316 if (_model && _browserState)
1317 [self addUIFunctionalityForModelAndBrowserState];
1318
1319 // Add a tap gesture recognizer to save the last tap location for the source
1320 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271321 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1322 initWithTarget:self
1323 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131324 [tapRecognizer setDelegate:self];
1325 [tapRecognizer setCancelsTouchesInView:NO];
1326 [_contentArea addGestureRecognizer:tapRecognizer];
1327}
1328
Justin Cohenb3170c32017-09-19 01:55:221329- (void)viewSafeAreaInsetsDidChange {
1330 [super viewSafeAreaInsetsDidChange];
1331 // Gate this behind iPhone X, since it's currently the only device that
1332 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501333 if (IsIPhoneX()) {
Justin Cohenb3170c32017-09-19 01:55:221334 [self setUpViewLayout];
Jean-François Geyelined4cde72017-10-11 11:34:501335 }
Justin Cohenba27610e2017-11-08 19:34:451336 if (IsSafeAreaCompatibleToolbarEnabled()) {
Gauthier Ambard100670f72017-10-27 09:54:271337 // TODO(crbug.com/778236): Check if this call can be removed once the
1338 // Toolbar is a contained ViewController.
sczs42f7f7482017-11-08 01:13:271339 [_toolbarCoordinator.toolbarViewController viewSafeAreaInsetsDidChange];
Gauthier Ambard100670f72017-10-27 09:54:271340 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501341 }
Justin Cohenb3170c32017-09-19 01:55:221342}
1343
sdefresnee65fd872016-12-19 13:38:131344- (void)viewDidAppear:(BOOL)animated {
1345 [super viewDidAppear:animated];
1346 self.viewVisible = YES;
1347 [self updateDialogPresenterActiveState];
Gregory Chatzinoff541b8642017-10-25 00:25:211348
1349 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1350 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1351 // check for |self.browserState| before calling the presenting the bubbles.
1352 if (self.browserState) {
1353 [self presentBubblesIfEligible];
1354 }
sdefresnee65fd872016-12-19 13:38:131355}
1356
1357- (void)viewWillAppear:(BOOL)animated {
1358 [super viewWillAppear:animated];
1359
Rohit Rao9a8ad772017-10-30 22:35:591360 // Reparent the toolbar if it's been relinquished. If the tab switcher
1361 // presentation experiment is enabled, only do this if the parent VC is not
1362 // currently being presented. Otherwise, reparenting here would remove the
1363 // toolbar from the tab switcher while the switcher is in the process of
1364 // animating.
1365 if (_isToolbarControllerRelinquished) {
1366 if (!TabSwitcherPresentsBVCEnabled() ||
1367 (!self.beingPresented && !self.parentViewController.beingPresented)) {
1368 [self reparentToolbarController];
1369 }
1370 }
sdefresnee65fd872016-12-19 13:38:131371
1372 self.visible = YES;
1373
1374 // Restore hidden infobars.
Rohit Rao755c37b2017-11-10 14:05:521375 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131376 _infoBarContainer->RestoreInfobars();
1377 }
1378
1379 // If the controller is suspended, or has been paged out due to low memory,
1380 // updating the view will be handled when it's displayed again.
1381 if (![_model webUsageEnabled] || !self.contentArea)
1382 return;
1383 // Update the displayed tab (if any; the switcher may not have created one
1384 // yet) in case it changed while showing the switcher.
1385 Tab* currentTab = [_model currentTab];
1386 if (currentTab)
1387 [self displayTab:currentTab isNewSelection:YES];
1388}
1389
1390- (void)viewWillDisappear:(BOOL)animated {
1391 self.viewVisible = NO;
1392 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131393 [[_model currentTab] wasHidden];
1394 [_bookmarkInteractionController dismissSnackbar];
Rohit Rao755c37b2017-11-10 14:05:521395 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131396 _infoBarContainer->SuspendInfobars();
1397 }
1398 [super viewWillDisappear:animated];
1399}
1400
1401- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1402 duration:(NSTimeInterval)duration {
1403 [super willRotateToInterfaceOrientation:orient duration:duration];
1404 [self dismissPopups];
1405 [self reshowFindBarIfNeededWithCoordinator:nil];
1406}
1407
1408- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1409 [super didRotateFromInterfaceOrientation:orient];
1410
1411 // This reinitializes the toolbar, including updating the Overlay View,
1412 // if there is one.
1413 [self updateToolbar];
1414 [self infoBarContainerStateChanged:false];
1415}
1416
1417- (BOOL)prefersStatusBarHidden {
1418 return self.hideStatusBar;
1419}
1420
1421// Called when in the foreground and the OS needs more memory. Release as much
1422// as possible.
1423- (void)didReceiveMemoryWarning {
1424 // Releases the view if it doesn't have a superview.
1425 [super didReceiveMemoryWarning];
1426
1427 // Release any cached data, images, etc that aren't in use.
1428 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1429 // but since the FaviconCache uses obj-c in the header, it can't be included
1430 // there.
1431 if (_browserState) {
1432 FaviconLoader* loader =
1433 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1434 _browserState);
1435 if (loader)
1436 loader->PurgeCache();
1437 }
1438
1439 if (![self isViewLoaded]) {
1440 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1441 // as the BrowserViewController.
1442 self.contentArea = nil;
1443 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271444 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131445 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271446 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421447 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461448 _toolbarCoordinator = nil;
stkhapuginc9eee7b2017-04-10 15:49:271449 _toolbarModelDelegate = nil;
1450 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031451 [self.tabStripCoordinator stop];
1452 self.tabStripCoordinator = nil;
1453 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271454 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131455 }
1456}
1457
1458- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1459 [super traitCollectionDidChange:previousTraitCollection];
1460 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1461 // because in some cases the presented view controller isn't a child of the
1462 // BVC in the view controller hierarchy (some intervening object isn't a
1463 // view controller).
1464 [self.presentedViewController
1465 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131466 // Update voice search bar visibility.
1467 [self updateVoiceSearchBarVisibilityAnimated:NO];
1468}
1469
1470- (void)viewWillTransitionToSize:(CGSize)size
1471 withTransitionCoordinator:
1472 (id<UIViewControllerTransitionCoordinator>)coordinator {
1473 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1474 [self dismissPopups];
1475 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1476}
1477
1478- (void)reshowFindBarIfNeededWithCoordinator:
1479 (id<UIViewControllerTransitionCoordinator>)coordinator {
1480 if (![_findBarController isFindInPageShown])
1481 return;
1482
1483 // Record focused state.
1484 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1485
1486 [self hideFindBarWithAnimation:NO];
1487
stkhapuginc9eee7b2017-04-10 15:49:271488 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131489 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1490 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271491 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131492 if (strongSelf)
1493 [strongSelf showFindBarWithAnimation:NO
1494 selectText:NO
1495 shouldFocus:isFocusedBeforeReshow];
1496 };
1497
1498 BOOL enqueued =
1499 [coordinator animateAlongsideTransition:nil completion:completion];
1500 if (!enqueued) {
1501 completion(nil);
1502 }
1503}
1504
1505- (void)dismissViewControllerAnimated:(BOOL)flag
1506 completion:(void (^)())completion {
Rohit Rao685807a52017-11-10 20:50:111507 // It is an error to call this method when no VC is being presented.
1508 DCHECK(!TabSwitcherPresentsBVCEnabled() || self.presentedViewController);
1509
Rohit Raoa668c022017-11-08 00:04:441510 // Some calling code invokes |dismissViewControllerAnimated:completion:|
1511 // multiple times. When the BVC is displayed using VC containment, multiple
1512 // calls are effectively idempotent because only the first call has any effect
1513 // and subsequent calls do nothing. However, when the BVC is presented,
1514 // subsequent calls end up dismissing the BVC itself. This is never what we
Rohit Rao685807a52017-11-10 20:50:111515 // want, so check for this case and return early. It is not enough to check
1516 // |self.dismissingModal| because some dismissals do not go through
1517 // -[BrowserViewController dismissViewControllerAnimated:completion:|.
Rohit Raoa668c022017-11-08 00:04:441518 // TODO(crbug.com/782338): Fix callers and remove this early return.
Rohit Rao685807a52017-11-10 20:50:111519 if (TabSwitcherPresentsBVCEnabled() &&
1520 (self.dismissingModal || self.presentedViewController.isBeingDismissed)) {
Rohit Raoa668c022017-11-08 00:04:441521 return;
1522 }
1523
sdefresnee65fd872016-12-19 13:38:131524 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271525 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131526 [super dismissViewControllerAnimated:flag
1527 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271528 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131529 [strongSelf setDismissingModal:NO];
1530 [strongSelf setPresenting:NO];
1531 if (completion)
1532 completion();
1533 [[strongSelf dialogPresenter] tryToPresent];
1534 }];
1535}
1536
1537- (void)presentViewController:(UIViewController*)viewControllerToPresent
1538 animated:(BOOL)flag
1539 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271540 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131541 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1542 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1543 // presented, to show a temporary view of the launch screen and then remove it
1544 // when the controller for the FRE has been presented. This fix should be
1545 // removed when the FRE startup code is rewritten.
1546 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1547 experimental_flags::AlwaysDisplayFirstRun()) &&
1548 !tests_hook::DisableFirstRun();
1549 // These if statements check that |presentViewController| is being called for
1550 // the FRE case.
1551 if (firstRunLaunch &&
1552 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1553 UINavigationController* navController =
1554 base::mac::ObjCCastStrict<UINavigationController>(
1555 viewControllerToPresent);
1556 if ([navController.topViewController
1557 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1558 self.hideStatusBar = YES;
1559
1560 // Load view from Launch Screen and add it to window.
1561 NSBundle* mainBundle = base::mac::FrameworkBundle();
1562 NSArray* topObjects =
1563 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1564 UIViewController* launchScreenController =
1565 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1566 // |launchScreenView| is loaded as an autoreleased object, and is retained
1567 // by the |completion| block below.
1568 UIView* launchScreenView = launchScreenController.view;
1569 launchScreenView.userInteractionEnabled = NO;
1570 launchScreenView.frame = self.view.window.bounds;
1571 [self.view.window addSubview:launchScreenView];
1572
1573 // Replace the completion handler sent to the superclass with one which
1574 // removes |launchScreenView| and resets the status bar. If |completion|
1575 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271576 __weak BrowserViewController* weakSelf = self;
1577 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131578 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271579 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131580 if (completion)
1581 completion();
stkhapuginc9eee7b2017-04-10 15:49:271582 };
sdefresnee65fd872016-12-19 13:38:131583 }
1584 }
1585
1586 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171587 if ([_sideSwipeController inSwipe]) {
1588 [_sideSwipeController resetContentView];
1589 }
sdefresnee65fd872016-12-19 13:38:131590
1591 [super presentViewController:viewControllerToPresent
1592 animated:flag
1593 completion:finalCompletionHandler];
1594}
1595
1596#pragma mark - Notification handling
1597
1598- (void)registerForNotifications {
1599 DCHECK(_model);
1600 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1601 [defaultCenter addObserver:self
1602 selector:@selector(pageLoadStarting:)
1603 name:kTabModelTabWillStartLoadingNotification
1604 object:_model];
1605 [defaultCenter addObserver:self
1606 selector:@selector(pageLoadStarted:)
1607 name:kTabModelTabDidStartLoadingNotification
1608 object:_model];
1609 [defaultCenter addObserver:self
1610 selector:@selector(pageLoadComplete:)
1611 name:kTabModelTabDidFinishLoadingNotification
1612 object:_model];
1613 [defaultCenter addObserver:self
1614 selector:@selector(tabDeselected:)
1615 name:kTabModelTabDeselectedNotification
1616 object:_model];
1617 [defaultCenter addObserver:self
1618 selector:@selector(tabWasAdded:)
1619 name:kTabModelNewTabWillOpenNotification
1620 object:_model];
1621}
1622
1623- (void)pageLoadStarting:(NSNotification*)notify {
1624 Tab* tab = notify.userInfo[kTabModelTabKey];
1625 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511626
1627 // Stop any Find in Page searches and close the find bar when navigating to a
1628 // new page.
1629 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361630
sdefresnee65fd872016-12-19 13:38:131631 if (tab == [_model currentTab]) {
1632 // TODO(pinkerton): Fill in here about hiding the forward button on
1633 // navigation.
1634 }
1635}
1636
1637- (void)pageLoadStarted:(NSNotification*)notify {
1638 Tab* tab = notify.userInfo[kTabModelTabKey];
1639 DCHECK(tab);
1640 if (tab == [_model currentTab]) {
1641 if (![self isTabNativePage:tab]) {
sczsf1620e52017-10-02 22:54:461642 [_toolbarCoordinator currentPageLoadStarted];
sdefresnee65fd872016-12-19 13:38:131643 }
1644 [self updateVoiceSearchBarVisibilityAnimated:NO];
1645 }
1646}
1647
1648- (void)pageLoadComplete:(NSNotification*)notify {
1649 // Update the UI, but only if the current tab.
1650 Tab* tab = notify.userInfo[kTabModelTabKey];
1651 if (tab == [_model currentTab]) {
1652 // There isn't any need to update the toolbar here. When the page finishes,
1653 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1654 }
1655
1656 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1657
1658 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1659}
1660
1661- (void)tabDeselected:(NSNotification*)notify {
1662 DCHECK(notify);
1663 Tab* tab = notify.userInfo[kTabModelTabKey];
1664 DCHECK(tab);
1665 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221666 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131667}
1668
1669- (void)tabWasAdded:(NSNotification*)notify {
1670 Tab* tab = notify.userInfo[kTabModelTabKey];
1671 DCHECK(tab);
1672
Eugene But56efc322017-08-11 14:03:441673 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131674
1675 // When adding new tabs, check what kind of reminder infobar should
1676 // be added to the new tab. Try to add only one of them.
1677 // This check is done when a new tab is added either through the Tools Menu
1678 // "New Tab" or through "New Tab" in Stack View Controller. This method
1679 // is called after a new tab has added and finished initial navigation.
1680 // If this is added earlier, the initial navigation may end up clearing
1681 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301682 web::WebState* webState = tab.webState;
1683 DCHECK(webState);
1684
1685 infobars::InfoBarManager* infoBarManager =
1686 InfoBarManagerImpl::FromWebState(webState);
1687 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131688 forTabId:[tab tabId]];
edchin9e7a1112017-11-07 18:28:031689 if (!ReSignInInfoBarDelegate::Create(_browserState, tab,
1690 self /* id<SigninPresenter> */)) {
edchin95c927072017-11-04 00:35:071691 DisplaySyncErrors(_browserState, tab, self /* id<SyncPresenter> */);
sdefresnee65fd872016-12-19 13:38:131692 }
1693
1694 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501695 // phone-specific. Call the foreground tab added completion block; for
1696 // iPhones, this will get executed after the animation has finished.
1697 if (IsIPadIdiom()) {
1698 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431699 // This callback is called before webState is activated (on
1700 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1701 // callback asynchronously to be sure the activation is complete.
1702 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011703 // Test existence again as the block may have been deleted.
1704 if (self.foregroundTabWasAddedCompletionBlock) {
1705 self.foregroundTabWasAddedCompletionBlock();
1706 self.foregroundTabWasAddedCompletionBlock = nil;
1707 }
Olivier Robinc7e46242017-09-06 07:55:431708 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501709 }
sdefresnee65fd872016-12-19 13:38:131710 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501711 }
sdefresnee65fd872016-12-19 13:38:131712
1713 // Do nothing if browsing is currently suspended. The BVC will set everything
1714 // up correctly when browsing resumes.
1715 if (!self.visible || ![_model webUsageEnabled])
1716 return;
1717
1718 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1719
1720 // Block that starts voice search at the end of new Tab animation if
1721 // necessary.
1722 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1723 if (_startVoiceSearchAfterNewTabAnimation) {
1724 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001725 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131726 }
1727 };
1728
kkhorimotoa44349c12017-04-12 23:02:121729 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131730 if (!inBackground) {
1731 UIView* animationParentView = _contentArea;
1732 // Create the new page image, and load with the new tab page snapshot.
1733 CGFloat newPageOffset = 0;
1734 UIImageView* newPage;
Sylvain Defresnee7f2c8a2017-10-17 02:39:191735 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
1736 !_isOffTheRecord && !IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131737 animationParentView = self.view;
1738 newPage = [self pageFullScreenOpenCloseAnimationView];
1739 } else {
1740 newPage = [self pageOpenCloseAnimationView];
1741 }
1742 newPageOffset = newPage.frame.origin.y;
1743
1744 [tab view].frame = _contentArea.bounds;
1745 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1746 [animationParentView addSubview:newPage];
1747 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521748 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
sdefresnee65fd872016-12-19 13:38:131749 newPage, -newPageOffset,
1750 newPage.frame.size.height - newPage.image.size.height, origin,
1751 _isOffTheRecord, NULL, ^{
1752 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121753 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421754 // Use the model's currentTab here because it is possible that it can
1755 // be reset to a new value before the new Tab animation finished (e.g.
1756 // if another Tab shows a dialog via |dialogPresenter|). However, that
1757 // tab's view hasn't been displayed yet because it was in a new tab
1758 // animation.
1759 Tab* currentTab = [_model currentTab];
1760 if (currentTab) {
1761 [self tabSelected:currentTab];
1762 }
sdefresnee65fd872016-12-19 13:38:131763 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391764
1765 if (self.foregroundTabWasAddedCompletionBlock) {
1766 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451767 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391768 }
sdefresnee65fd872016-12-19 13:38:131769 });
1770 } else {
1771 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1772 // snapshot before adding the views needed for the background animation.
1773 Tab* topTab = [_model currentTab];
1774 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1775 visibleFrameOnly:self.isToolbarOnScreen];
1776 // Add three layers in order on top of the contentArea for the animation:
1777 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271778 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131779 InstallBackgroundInView(background);
1780 [_contentArea addSubview:background];
1781
1782 // 2. A CardView displaying the data from the current tab.
1783 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1784 NSString* title = [topTab title];
1785 if (![title length])
1786 title = [topTab urlDisplayString];
1787 [topCard setTitle:title];
sdefresnee65fd872016-12-19 13:38:131788 [topCard setImage:image];
Sylvain Defresne7178d4c2017-09-14 13:22:371789 [topCard setFavicon:nil];
1790
1791 favicon::FaviconDriver* faviconDriver =
1792 favicon::WebFaviconDriver::FromWebState(topTab.webState);
1793 if (faviconDriver && faviconDriver->FaviconIsValid()) {
1794 gfx::Image favicon = faviconDriver->GetFavicon();
1795 if (!favicon.IsEmpty())
1796 [topCard setFavicon:favicon.ToUIImage()];
1797 }
sdefresnee65fd872016-12-19 13:38:131798
1799 // 3. A new, blank CardView to represent the new tab being added.
1800 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521801 page_animation_util::AnimateNewBackgroundPageWithCompletion(
sdefresnee65fd872016-12-19 13:38:131802 topCard, [_contentArea frame], IsPortrait(), ^{
1803 [background removeFromSuperview];
1804 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121805 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131806 // Resnapshot the top card if it has its own toolbar, as the toolbar
1807 // will be captured in the new tab animation, but isn't desired for
1808 // the stack view snapshots.
1809 id nativeController = [self nativeControllerForTab:topTab];
1810 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1811 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1812 startVoiceSearchIfNecessaryBlock();
1813 });
peterlaurens9f1b6e02017-06-22 17:46:451814 // Reset the foreground tab completion block so that it can never be
1815 // called more than once regardless of foreground/background tab
1816 // appearances.
1817 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131818 }
1819}
1820
1821#pragma mark - UI Configuration and Layout
1822
1823- (void)updateWithTabModel:(TabModel*)model
1824 browserState:(ios::ChromeBrowserState*)browserState {
1825 DCHECK(model);
1826 DCHECK(browserState);
1827 DCHECK(!_model);
1828 DCHECK(!_browserState);
1829 _browserState = browserState;
1830 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271831 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381832
sdefresnee65fd872016-12-19 13:38:131833 [_model addObserver:self];
1834
1835 if (!_isOffTheRecord) {
1836 [DefaultIOSWebViewFactory
1837 registerWebViewFactory:[ChromeWebViewFactory class]];
1838 }
1839 NSUInteger count = [_model count];
1840 for (NSUInteger index = 0; index < count; ++index)
1841 [self installDelegatesForTab:[_model tabAtIndex:index]];
1842
1843 [self registerForNotifications];
1844
gambardbdc07cc2017-02-03 16:43:111845 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121846 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271847 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131848
sdefresnedc432f42017-01-17 14:36:591849 // Register for bookmark changed notification (BookmarkModel may be null
1850 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131851 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591852 if (_bookmarkModel) {
1853 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1854 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1855 }
sdefresnee65fd872016-12-19 13:38:131856}
1857
sdefresnee65fd872016-12-19 13:38:131858- (void)browserStateDestroyed {
1859 [self setActive:NO];
sdefresnee65fd872016-12-19 13:38:131860 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271861 _paymentRequestManager = nil;
sczsf1620e52017-10-02 22:54:461862 [_toolbarCoordinator browserStateDestroyed];
sdefresnee65fd872016-12-19 13:38:131863 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381864
1865 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431866 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111867 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381868 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271869 [_pageInfoCoordinator disconnect];
Louis Romerod11747a2017-10-20 20:10:351870 [_externalSearchCoordinator disconnect];
edchinf5150c682017-09-18 02:50:031871 [self.tabStripCoordinator stop];
1872 self.tabStripCoordinator = nil;
1873 self.tabStripView = nil;
sczsdd860eba2017-08-10 01:55:381874
sdefresnee65fd872016-12-19 13:38:131875 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391876 [_dispatcher stopDispatchingToTarget:self];
1877 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131878}
1879
1880- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221881 CGFloat statusBarHeight = StatusBarHeight();
1882 CGRect statusBarFrame =
1883 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1884 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1885 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131886 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221887 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1888 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1889 [_fakeStatusBarView layer].zPosition = 99;
1890 [[self view] addSubview:_fakeStatusBarView];
1891 } else {
1892 // Add a white bar on phone so that the status bar on the NTP is white.
1893 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1894 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131895 }
1896}
1897
1898// Create the UI elements. May or may not have valid browser state & tab model.
1899- (void)buildToolbarAndTabStrip {
1900 DCHECK([self isViewLoaded]);
1901 DCHECK(!_toolbarModelDelegate);
1902
Rohit Rao44f204302017-08-10 14:49:541903 // Initialize the prerender service before creating the toolbar controller.
1904 PrerenderService* prerenderService =
1905 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1906 if (prerenderService) {
1907 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131908 }
1909
1910 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201911 _toolbarModelDelegate.reset(
1912 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131913 _toolbarModelIOS.reset([_dependencyFactory
1914 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
sczsf1620e52017-10-02 22:54:461915 _toolbarCoordinator =
1916 [[LegacyToolbarCoordinator alloc] initWithBaseViewController:self];
Gauthier Ambard29939db12017-10-30 16:47:311917 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sczsf1620e52017-10-02 22:54:461918 _toolbarCoordinator.tabModel = _model;
Gauthier Ambard82c8cc52017-10-26 15:59:051919 [_toolbarCoordinator
1920 setWebToolbar:[_dependencyFactory
1921 newWebToolbarControllerWithDelegate:self
1922 urlLoader:self
1923 dispatcher:self.dispatcher]];
sczsf1620e52017-10-02 22:54:461924 [_dispatcher startDispatchingToTarget:_toolbarCoordinator
justincohen75011c32017-04-28 16:31:391925 forProtocol:@protocol(OmniboxFocuser)];
sczsf1620e52017-10-02 22:54:461926 [_toolbarCoordinator setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271927 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461928 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:051929 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131930
sdefresnee65fd872016-12-19 13:38:131931 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031932 self.tabStripCoordinator =
1933 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1934 self.tabStripCoordinator.browserState = _browserState;
1935 self.tabStripCoordinator.dispatcher = _dispatcher;
1936 self.tabStripCoordinator.tabModel = _model;
1937 self.tabStripCoordinator.presentationProvider = self;
1938 self.tabStripCoordinator.animationWaitDuration =
Kurt Horimoto62e97c72017-11-03 19:51:471939 kLegacyFullscreenControllerToolbarAnimationDuration;
edchinf5150c682017-09-18 02:50:031940 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131941 }
1942
1943 // Create infobar container.
1944 if (!_infoBarContainerDelegate) {
1945 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1946 _infoBarContainer.reset(
1947 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1948 }
1949}
1950
Jean-François Geyelined4cde72017-10-11 11:34:501951- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:111952 NSLayoutYAxisAnchor* topAnchor;
1953 // On iPad, the toolbar is underneath the tab strip.
1954 // On iPhone, it is underneath the top of the screen.
1955 if (IsIPadIdiom()) {
1956 topAnchor = self.tabStripView.bottomAnchor;
1957 } else {
1958 topAnchor = [self view].topAnchor;
1959 }
1960
Gauthier Ambard100670f72017-10-27 09:54:271961 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501962
1963 [NSLayoutConstraint activateConstraints:@[
sczs42f7f7482017-11-08 01:13:271964 [_toolbarCoordinator.toolbarViewController.view.leadingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501965 constraintEqualToAnchor:[self view].leadingAnchor],
sczs42f7f7482017-11-08 01:13:271966 [_toolbarCoordinator.toolbarViewController.view.topAnchor
1967 constraintEqualToAnchor:topAnchor],
1968 [_toolbarCoordinator.toolbarViewController.view.trailingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501969 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501970 ]];
1971 [[self view] layoutIfNeeded];
1972}
1973
sdefresnee65fd872016-12-19 13:38:131974// Enable functionality that only makes sense if the views are loaded and
1975// both browser state and tab model are valid.
1976- (void)addUIFunctionalityForModelAndBrowserState {
1977 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051978 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131979 DCHECK(_model);
1980 DCHECK([self isViewLoaded]);
1981
1982 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1983
Rohit Raoaf46af92017-08-10 12:52:301984 infobars::InfoBarManager* infoBarManager = nullptr;
1985 if (_model.currentTab) {
1986 DCHECK(_model.currentTab.webState);
1987 infoBarManager =
1988 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1989 }
sdefresnee65fd872016-12-19 13:38:131990 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1991
sczsdd860eba2017-08-10 01:55:381992 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431993 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1994 initWithBaseViewController:self];
1995 _activityServiceCoordinator.dispatcher = _dispatcher;
1996 _activityServiceCoordinator.tabModel = _model;
1997 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:461998 _activityServiceCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051999 [_toolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:432000 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:432001
Rohit Raocda0a992017-08-16 15:37:112002 _qrScannerCoordinator =
2003 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
2004 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:052005 _qrScannerCoordinator.loadProvider =
2006 [_toolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:112007 _qrScannerCoordinator.presentationProvider = self;
2008
sczsdd860eba2017-08-10 01:55:382009 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:132010 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:382011 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:462012 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:052013 [_toolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:382014 _tabHistoryCoordinator.tabModel = _model;
2015 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:462016 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard82c8cc52017-10-26 15:59:052017 [_toolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:382018
sczs6ae47ad2017-09-06 17:26:532019 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:202020 _sadTabCoordinator.baseViewController = self;
2021 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:532022
Gregory Chatzinoffdf93d692017-09-09 01:32:272023 _pageInfoCoordinator =
2024 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
2025 _pageInfoCoordinator.browserState = _browserState;
2026 _pageInfoCoordinator.dispatcher = _dispatcher;
2027 _pageInfoCoordinator.loader = self;
2028 _pageInfoCoordinator.presentationProvider = self;
2029 _pageInfoCoordinator.tabModel = _model;
2030
Louis Romerod11747a2017-10-20 20:10:352031 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
2032 _externalSearchCoordinator.dispatcher = _dispatcher;
2033
mathp9b4c11d2017-07-06 20:24:132034 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272035 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132036 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202037 browserState:_browserState
2038 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052039 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192040 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132041 }
2042}
2043
2044// Set the frame for the various views. View must be loaded.
2045- (void)setUpViewLayout {
2046 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132047 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:132048 CGFloat minY = [self headerOffset];
2049
Justin Cohenb3170c32017-09-19 01:55:222050 // Update the fake toolbar background height.
2051 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2052 fakeStatusBarFrame.size.height = StatusBarHeight();
2053 _fakeStatusBarView.frame = fakeStatusBarFrame;
2054
edchinf5150c682017-09-18 02:50:032055 if (self.tabStripView) {
2056 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:132057 }
2058
2059 // Position the toolbar next, either at the top of the browser view or
2060 // directly under the tabstrip.
sczs42f7f7482017-11-08 01:13:272061 [self addChildViewController:_toolbarCoordinator.toolbarViewController];
2062 CGRect toolbarFrame = _toolbarCoordinator.toolbarViewController.view.frame;
sdefresnee65fd872016-12-19 13:38:132063 toolbarFrame.origin = CGPointMake(0, minY);
2064 toolbarFrame.size.width = widthOfView;
Justin Cohenba27610e2017-11-08 19:34:452065 if (!IsSafeAreaCompatibleToolbarEnabled()) {
sczs42f7f7482017-11-08 01:13:272066 [_toolbarCoordinator.toolbarViewController.view setFrame:toolbarFrame];
Jean-François Geyelined4cde72017-10-11 11:34:502067 }
sdefresnee65fd872016-12-19 13:38:132068
2069 // Place the infobar container above the content area.
2070 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
2071 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
2072
2073 // Place the toolbar controller above the infobar container.
sczs42f7f7482017-11-08 01:13:272074 [[self view] insertSubview:_toolbarCoordinator.toolbarViewController.view
sdefresnee65fd872016-12-19 13:38:132075 aboveSubview:infoBarContainerView];
2076 minY += CGRectGetHeight(toolbarFrame);
sczs42f7f7482017-11-08 01:13:272077 [_toolbarCoordinator.toolbarViewController
2078 didMoveToParentViewController:self];
sdefresnee65fd872016-12-19 13:38:132079
2080 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
2081 // content slightly.
sczs8c837782017-10-03 02:57:242082 minY -= 0.0;
sdefresnee65fd872016-12-19 13:38:132083
2084 // Adjust the content area to be under the toolbar, for fullscreen or below
2085 // the toolbar is not fullscreen.
2086 CGRect contentFrame = [_contentArea frame];
2087 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222088 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2089 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132090 [_contentArea setFrame:contentFrame];
2091
2092 // Adjust the infobar container to be either at the bottom of the screen
2093 // (iPhone) or on the lower toolbar edge (iPad).
2094 CGRect infoBarFrame = contentFrame;
2095 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2096 infoBarFrame.size.height = 0;
2097 [infoBarContainerView setFrame:infoBarFrame];
2098
2099 // Attach the typing shield to the content area but have it hidden.
2100 [_typingShield setFrame:[_contentArea frame]];
2101 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
2102 [_typingShield setHidden:YES];
2103 _typingShield.accessibilityIdentifier = @"Typing Shield";
2104 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
2105}
2106
sdefresnee65fd872016-12-19 13:38:132107- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2108 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442109 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132110
kkhorimotoa44349c12017-04-12 23:02:122111 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132112 // Hide findbar. |updateToolbar| will restore the findbar later.
2113 [self hideFindBarWithAnimation:NO];
2114
2115 // Make new content visible, resizing it first as the orientation may
2116 // have changed from the last time it was displayed.
2117 [[tab view] setFrame:_contentArea.bounds];
2118 [_contentArea displayContentView:[tab view]];
2119 }
2120 [self updateToolbar];
2121
2122 if (newSelection)
sczsf1620e52017-10-02 22:54:462123 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132124
2125 // Notify the Tab that it was displayed.
2126 [tab wasShown];
2127}
2128
2129- (void)initializeBookmarkInteractionController {
2130 if (_bookmarkInteractionController)
2131 return;
edchinbb8ba892017-09-12 15:44:032132 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2133 initWithBrowserState:_browserState
2134 loader:self
2135 parentController:self
2136 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132137}
2138
2139// Update the state of back and forward buttons, hiding the forward button if
2140// there is nowhere to go. Assumes the model's current tab is up to date.
2141- (void)updateToolbar {
2142 // If the BVC has been partially torn down for low memory, wait for the
2143 // view rebuild to handle toolbar updates.
2144 if (!(_toolbarModelIOS && _browserState))
2145 return;
2146
2147 Tab* tab = [_model currentTab];
2148 if (![tab navigationManager])
2149 return;
sczsf1620e52017-10-02 22:54:462150 [_toolbarCoordinator updateToolbarState];
2151 [_toolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132152
Rohit Rao44f204302017-08-10 14:49:542153 PrerenderService* prerenderService =
2154 PrerenderServiceFactory::GetForBrowserState(self.browserState);
2155 BOOL isPrerenderTab =
2156 prerenderService && prerenderService->IsWebStatePrerendered(tab.webState);
2157 if (isPrerenderTab && !_toolbarModelIOS->IsLoading())
sczsf1620e52017-10-02 22:54:462158 [_toolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132159
2160 // Also update the loading state for the tools menu (that is really an
2161 // extension of the toolbar on the iPhone).
2162 if (!IsIPadIdiom())
sczsf1620e52017-10-02 22:54:462163 [[_toolbarCoordinator toolsPopupController]
sdefresnee65fd872016-12-19 13:38:132164 setIsTabLoading:_toolbarModelIOS->IsLoading()];
2165
rohitrao005a6432017-03-16 20:52:422166 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2167 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132168 [self showFindBarWithAnimation:NO
2169 selectText:YES
2170 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362171 }
sdefresnee65fd872016-12-19 13:38:132172
2173 // Hide the toolbar if displaying phone NTP.
2174 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552175 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132176 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552177 if (item) {
2178 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132179 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2180 hideToolbar = isNTP && !_isOffTheRecord &&
sczsf1620e52017-10-02 22:54:462181 ![_toolbarCoordinator isOmniboxFirstResponder] &&
2182 ![_toolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132183 }
sczs42f7f7482017-11-08 01:13:272184 [_toolbarCoordinator.toolbarViewController.view setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132185 }
2186}
2187
2188- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122189 self.dialogPresenter.active =
2190 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132191}
2192
2193- (void)dismissPopups {
sczsf1620e52017-10-02 22:54:462194 [_toolbarCoordinator dismissToolsMenuPopup];
Gregory Chatzinoffdf93d692017-09-09 01:32:272195 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282196 [_tabHistoryCoordinator dismissHistoryPopup];
Cooper Knaakd0a974cd2017-08-10 18:05:472197 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382198}
2199
Cooper Knaakd0a974cd2017-08-10 18:05:472200- (BubbleViewControllerPresenter*)
2201bubblePresenterForFeature:(const base::Feature&)feature
2202 direction:(BubbleArrowDirection)direction
2203 alignment:(BubbleAlignment)alignment
2204 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212205 DCHECK(self.browserState);
2206 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382207 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472208 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382209 }
2210 // Capture |weakSelf| instead of the feature engagement tracker object
2211 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2212 // the feature engagement tracker will remain pointing to invalid memory if
2213 // its owner (the ChromeBrowserState) is deallocated.
2214 __weak BrowserViewController* weakSelf = self;
2215 void (^dismissalCallback)(void) = ^() {
2216 BrowserViewController* strongSelf = weakSelf;
2217 if (strongSelf) {
2218 feature_engagement::TrackerFactory::GetForBrowserState(
2219 strongSelf.browserState)
2220 ->Dismissed(feature);
2221 }
2222 };
2223
Cooper Knaakd0a974cd2017-08-10 18:05:472224 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382225 [[BubbleViewControllerPresenter alloc] initWithText:text
2226 arrowDirection:direction
2227 alignment:alignment
2228 dismissalCallback:dismissalCallback];
2229
Cooper Knaakd0a974cd2017-08-10 18:05:472230 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132231}
2232
Cooper Knaak120cee5e2017-08-10 20:57:002233- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212234 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002235 // If the tab tip bubble has already been presented and the user is still
2236 // considered engaged, it can't be overwritten or set to |nil| or else it will
2237 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2238 // can be safely overwritten or set to |nil|.
2239 if (!self.tabTipBubblePresenter.isUserEngaged) {
2240 __weak BrowserViewController* weakSelf = self;
2241 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2242 [weakSelf presentNewTabTipBubble];
2243 };
2244
2245 // Because the new tab tip occurs on startup, the feature engagement
2246 // tracker's database is not guaranteed to be loaded by this time. For the
2247 // bubble to appear properly, a callback is used to guarantee the event data
2248 // is loaded before the check to see if the promotion should be displayed.
2249 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2250 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2251 }
2252}
2253
2254- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212255 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002256 NSString* text =
2257 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2258 CGPoint tabSwitcherAnchor;
2259 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032260 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002261 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032262 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002263 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2264 } else {
sczsf1620e52017-10-02 22:54:462265 DCHECK([_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002266 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
sczsf1620e52017-10-02 22:54:462267 tabSwitcherAnchor = [_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002268 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2269 }
Cooper Knaake963d6702017-08-11 21:03:112270 // If the feature engagement tracker does not consider it valid to display
2271 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2272 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002273 self.tabTipBubblePresenter =
2274 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2275 direction:BubbleArrowDirectionUp
2276 alignment:BubbleAlignmentTrailing
2277 text:text];
2278 [self.tabTipBubblePresenter presentInViewController:self
2279 view:self.view
2280 anchorPoint:tabSwitcherAnchor];
2281}
2282
Helen Yang9175bd52017-08-12 00:28:402283- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212284 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402285 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2286 // user is still considered engaged.
2287 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2288 __weak BrowserViewController* weakSelf = self;
2289 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2290 [weakSelf presentNewIncognitoTabTipBubble];
2291 };
2292
2293 // Use a callback in case the new incognito tab tip should be shown on
2294 // startup. This ensures that the tracker's database will be fully loaded
2295 // before checking if the promotion should be displayed.
2296 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2297 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2298 }
2299}
2300
2301- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212302 DCHECK(self.browserState);
sczsf1620e52017-10-02 22:54:462303 DCHECK([_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402304 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2305 NSString* text = l10n_util::GetNSStringWithFixup(
2306 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
sczsf1620e52017-10-02 22:54:462307 CGPoint toolsButtonAnchor = [_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402308 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2309 self.incognitoTabTipBubblePresenter =
2310 [self bubblePresenterForFeature:feature_engagement::
2311 kIPHNewIncognitoTabTipFeature
2312 direction:BubbleArrowDirectionUp
2313 alignment:BubbleAlignmentTrailing
2314 text:text];
2315 [self.incognitoTabTipBubblePresenter
2316 presentInViewController:self
2317 view:self.view
2318 anchorPoint:toolsButtonAnchor];
2319 // Only trigger the tools menu button animation if the bubble is shown.
2320 if (self.incognitoTabTipBubblePresenter) {
sczsf1620e52017-10-02 22:54:462321 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402322 }
2323}
2324
sdefresnee65fd872016-12-19 13:38:132325#pragma mark - Tap handling
2326
Mark Cogandfcdea72017-07-18 13:47:382327- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032328 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2329 _lastTapPoint = CGPointZero;
2330 } else {
2331 _lastTapPoint =
2332 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132333 }
Mark Cogane01ebce2017-07-12 19:31:032334 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132335}
2336
2337- (CGPoint)lastTapPoint {
2338 if (CACurrentMediaTime() - _lastTapTime < 1) {
2339 return _lastTapPoint;
2340 }
2341 return CGPointZero;
2342}
2343
2344- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2345 UIView* view = gestureRecognizer.view;
2346 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2347 _lastTapPoint =
2348 [[view superview] convertPoint:viewCoordinate toView:self.view];
2349 _lastTapTime = CACurrentMediaTime();
2350}
2351
2352- (BOOL)addTabIfNoTabWithNormalBrowserState {
2353 if (![_model count]) {
2354 if (!_isOffTheRecord) {
2355 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2356 transition:ui::PAGE_TRANSITION_TYPED];
2357 return YES;
2358 }
2359 }
2360 return NO;
2361}
2362
2363#pragma mark - Tab creation and selection
2364
2365// Called when either a tab finishes loading or when a tab with finished content
2366// is added directly to the model via pre-rendering.
2367- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2368 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2369
2370 // Persist the session on a delay.
2371 [_model saveSessionImmediately:NO];
2372}
2373
2374- (Tab*)addSelectedTabWithURL:(const GURL&)url
2375 postData:(TemplateURLRef::PostContent*)postData
2376 transition:(ui::PageTransition)transition {
2377 return [self addSelectedTabWithURL:url
2378 postData:postData
2379 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492380 transition:transition
2381 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132382}
2383
2384- (Tab*)addSelectedTabWithURL:(const GURL&)url
2385 transition:(ui::PageTransition)transition {
2386 return [self addSelectedTabWithURL:url
2387 atIndex:[_model count]
2388 transition:transition];
2389}
2390
2391- (Tab*)addSelectedTabWithURL:(const GURL&)url
2392 atIndex:(NSUInteger)position
2393 transition:(ui::PageTransition)transition {
2394 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492395 atIndex:position
2396 transition:transition
2397 tabAddedCompletion:nil];
2398}
2399
2400- (Tab*)addSelectedTabWithURL:(const GURL&)url
2401 atIndex:(NSUInteger)position
2402 transition:(ui::PageTransition)transition
2403 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2404 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132405 postData:NULL
2406 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492407 transition:transition
2408 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132409}
2410
2411- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2412 postData:(TemplateURLRef::PostContent*)postData
2413 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492414 transition:(ui::PageTransition)transition
2415 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132416 if (position == NSNotFound)
2417 position = [_model count];
2418 DCHECK(position <= [_model count]);
2419
2420 web::NavigationManager::WebLoadParams params(URL);
2421 params.transition_type = transition;
2422 if (postData) {
2423 // Extract the content type and post params from |postData| and add them
2424 // to the load params.
2425 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2426 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2427 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172428 params.post_data.reset(data);
2429 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132430 }
Olivier Robind508a5632017-07-19 16:29:492431
2432 if (tabAddedCompletion) {
2433 if (self.foregroundTabWasAddedCompletionBlock) {
2434 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2435 self.foregroundTabWasAddedCompletionBlock;
2436 self.foregroundTabWasAddedCompletionBlock = ^{
2437 oldForegroundTabWasAddedCompletionBlock();
2438 tabAddedCompletion();
2439 };
2440 } else {
2441 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2442 }
2443 }
2444
sdefresnea6395912017-03-01 01:14:352445 Tab* tab = [_model insertTabWithLoadParams:params
2446 opener:nil
2447 openedByDOM:NO
2448 atIndex:position
2449 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132450 return tab;
2451}
2452
olivierrobin889af53f2017-03-01 14:56:322453// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132454- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322455 web::WebState* webState = tab.webState;
2456 if (!webState)
2457 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392458 web::NavigationItem* visibleItem =
2459 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322460 if (!visibleItem)
2461 return NO;
2462 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132463}
2464
2465- (void)expectNewForegroundTab {
2466 _expectingForegroundTab = YES;
2467}
2468
2469- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2470 CGRect viewBounds, remainder;
2471 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2472 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172473 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132474}
2475
2476- (UIImageView*)pageOpenCloseAnimationView {
2477 CGRect frame = [_contentArea bounds];
2478
2479 frame.size.height = frame.size.height - [self headerHeight];
2480 frame.origin.y = [self headerHeight];
2481
stkhapuginf58b10d02017-04-10 13:36:172482 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132483 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2484 pageView.center = center;
2485
2486 pageView.backgroundColor = [UIColor whiteColor];
2487 return pageView;
2488}
2489
2490- (void)installDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112491 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
sdefresne49cf2862017-03-15 13:46:142492 // Unregistration happens when the Tab is removed from the TabModel.
edchincd32fdf2017-10-25 12:45:452493
2494 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2495 if (PasswordTabHelper* passwordTabHelper =
2496 PasswordTabHelper::FromWebState(tab.webState)) {
2497 passwordTabHelper->SetDispatcher(self.dispatcher);
2498 passwordTabHelper->SetPasswordControllerDelegate(self);
2499 }
2500
sdefresnee65fd872016-12-19 13:38:132501 tab.dialogDelegate = self;
2502 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132503 tab.passKitDialogProvider = self;
Kurt Horimotoa5a922a2017-11-07 00:21:092504 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472505 tab.legacyFullscreenControllerDelegate = self;
Kurt Horimoto803840622017-10-28 01:20:372506 }
sdefresnee65fd872016-12-19 13:38:132507 if (!IsIPadIdiom()) {
2508 tab.overscrollActionsControllerDelegate = self;
2509 }
olivierrobin9ce77b82017-01-12 17:29:192510 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132511 tab.tabSnapshottingDelegate = self;
2512 // Install the proper CRWWebController delegates.
2513 tab.webController.nativeProvider = self;
2514 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512515 // BrowserViewController presents SKStoreKitViewController on behalf of a
2516 // tab.
2517 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2518 if (tabHelper)
2519 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132520 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532521 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292522 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532523 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292524 }
Sylvain Defresnecacc3a52017-09-12 13:51:042525 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592526 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572527 NetExportTabHelper::CreateForWebState(tab.webState, self);
Mike Dougherty4620cf8e2017-10-31 23:37:092528 CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
edchincd32fdf2017-10-25 12:45:452529
2530 if (AccountConsistencyService* accountConsistencyService =
2531 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2532 self.browserState)) {
2533 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552534 }
sdefresnee65fd872016-12-19 13:38:132535}
2536
sdefresne49cf2862017-03-15 13:46:142537- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112538 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
edchincd32fdf2017-10-25 12:45:452539
2540 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2541 if (PasswordTabHelper* passwordTabHelper =
2542 PasswordTabHelper::FromWebState(tab.webState))
2543 passwordTabHelper->SetDispatcher(nil);
2544
sdefresne49cf2862017-03-15 13:46:142545 tab.dialogDelegate = nil;
2546 tab.snapshotOverlayProvider = nil;
2547 tab.passKitDialogProvider = nil;
Kurt Horimotoa5a922a2017-11-07 00:21:092548 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472549 tab.legacyFullscreenControllerDelegate = nil;
Kurt Horimoto803840622017-10-28 01:20:372550 }
sdefresne49cf2862017-03-15 13:46:142551 if (!IsIPadIdiom()) {
2552 tab.overscrollActionsControllerDelegate = nil;
2553 }
2554 tab.tabHeadersDelegate = nil;
2555 tab.tabSnapshottingDelegate = nil;
2556 tab.webController.nativeProvider = nil;
2557 tab.webController.swipeRecognizerProvider = nil;
2558 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2559 if (tabHelper)
2560 tabHelper->SetLauncher(nil);
2561 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452562 if (AccountConsistencyService* accountConsistencyService =
2563 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2564 self.browserState)) {
2565 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2566 }
sdefresne49cf2862017-03-15 13:46:142567}
2568
sdefresnee65fd872016-12-19 13:38:132569// Called when a tab is selected in the model. Make any required view changes.
2570// The notification will not be sent when the tab is already the selected tab.
2571- (void)tabSelected:(Tab*)tab {
2572 DCHECK(tab);
2573
2574 // Ignore changes while the tab stack view is visible (or while suspended).
2575 // The display will be refreshed when this view becomes active again.
2576 if (!self.visible || ![_model webUsageEnabled])
2577 return;
2578
2579 [self displayTab:tab isNewSelection:YES];
2580
kkhorimotoa44349c12017-04-12 23:02:122581 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132582 // Now that the new tab has been displayed, return to normal. Rather than
2583 // keep a reference to the previous tab, just turn off preview mode for all
2584 // tabs (since doing so is a no-op for the tabs that don't have it set).
2585 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272586 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132587 [tab.webController setOverlayPreviewMode:NO];
2588 }
2589 }
2590}
2591
edchinf5150c682017-09-18 02:50:032592- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
2593 return [self.tabStripCoordinator placeholderView];
2594}
2595
Sylvain Defresne41170aa2017-06-15 10:25:202596- (void)shutdown {
2597 DCHECK(!_isShutdown);
2598 _isShutdown = YES;
edchinf5150c682017-09-18 02:50:032599 [self.tabStripCoordinator stop];
2600 self.tabStripCoordinator = nil;
sczs42f7f7482017-11-08 01:13:272601 [_toolbarCoordinator stop];
2602 _toolbarCoordinator = nil;
edchinf5150c682017-09-18 02:50:032603 self.tabStripView = nil;
Sylvain Defresne41170aa2017-06-15 10:25:202604 _infoBarContainer = nil;
2605 _readingListMenuNotifier = nil;
2606 if (_bookmarkModel)
2607 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2608 [_model removeObserver:self];
2609 [[UpgradeCenter sharedInstance] unregisterClient:self];
2610 [[NSNotificationCenter defaultCenter] removeObserver:self];
Gauthier Ambard82c8cc52017-10-26 15:59:052611 [_toolbarCoordinator setToolbarDelegate:nil];
Sylvain Defresne41170aa2017-06-15 10:25:202612 if (_voiceSearchController)
2613 _voiceSearchController->SetDelegate(nil);
2614 [_rateThisAppDialog setDelegate:nil];
2615 [_model closeAllTabs];
Mohamad Ahmadibec07eb2017-09-12 19:38:462616 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202617}
2618
sdefresnee65fd872016-12-19 13:38:132619#pragma mark - SnapshotOverlayProvider methods
2620
2621- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2622 NSMutableArray* overlays = [NSMutableArray array];
2623 if (![_model webUsageEnabled]) {
2624 return overlays;
2625 }
2626 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2627 if (voiceSearchView) {
2628 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272629 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132630 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272631 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132632 [overlays addObject:voiceSearchOverlay];
2633 }
2634 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2635 if (infoBarView) {
2636 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272637 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132638 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272639 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132640 [overlays addObject:infoBarOverlay];
2641 }
2642 return overlays;
2643}
2644
2645#pragma mark -
2646
2647- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2648 if (IsIPadIdiom()) {
2649 // Not using overlays on iPad because the content is pushed down by
2650 // infobar and the transition between snapshot and fresh page can
2651 // cause both snapshot and real infobars to appear at the same time.
2652 return nil;
2653 }
2654 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302655 if (currentTab && tab == currentTab) {
2656 DCHECK(currentTab.webState);
2657 infobars::InfoBarManager* infoBarManager =
2658 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132659 if (infoBarManager->infobar_count() > 0) {
2660 DCHECK(_infoBarContainer);
2661 return _infoBarContainer->view();
2662 }
2663 }
2664 return nil;
2665}
2666
2667- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272668 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132669 // There is no UI representation for non-current tabs or there is
2670 // no _infoBarContainer instantiated yet.
2671 // Return offset outside of tab.
2672 return CGRectGetMaxY(self.view.frame);
2673 } else if (IsIPadIdiom()) {
2674 // The infobars on iPad are display at the top of a tab.
2675 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2676 } else {
2677 // The infobars on iPhone are displayed at the bottom of a tab.
2678 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2679 return CGRectGetMaxY(visibleFrame) -
2680 CGRectGetHeight(_infoBarContainer->view().frame);
2681 }
2682}
2683
2684- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2685 Tab* currentTab = [_model currentTab];
2686 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2687 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2688 return _voiceSearchBar;
2689 }
2690 return nil;
2691}
2692
2693- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2694 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2695 // There is no UI representation for non-current tabs or there is
2696 // no visible voice search. Return offset outside of tab.
2697 return CGRectGetMaxY(self.view.frame);
2698 } else {
2699 // The voice search bar on iPhone is displayed at the bottom of a tab.
2700 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2701 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2702 }
2703}
2704
2705- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272706 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132707 VoiceSearchProvider* provider =
2708 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2709 if (provider) {
2710 _voiceSearchController =
2711 provider->CreateVoiceSearchController(_browserState);
sczsf1620e52017-10-02 22:54:462712 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:052713 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:132714 }
2715 }
2716}
2717
2718- (void)ensureVoiceSearchBarCreated {
2719 if (_voiceSearchBar)
2720 return;
2721
2722 CGFloat width = CGRectGetWidth([[self view] bounds]);
2723 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2724 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272725 _voiceSearchBar = ios::GetChromeBrowserProvider()
2726 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002727 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132728 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2729 [_voiceSearchBar setHidden:YES];
2730 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2731 UIViewAutoresizingFlexibleWidth];
2732 [self.view insertSubview:_voiceSearchBar
2733 belowSubview:_infoBarContainer->view()];
2734}
2735
2736- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2737 // Voice search bar exists and is shown/hidden.
2738 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272739 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132740 return;
2741
2742 // Voice search bar doesn't exist and thus is not visible.
2743 if (!_voiceSearchBar && !show)
2744 return;
2745
2746 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272747 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132748 else
stkhapuginc9eee7b2017-04-10 15:49:272749 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132750}
2751
2752- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2753 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2754 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2755 self.shouldShowVoiceSearchBar) {
2756 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2757 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272758 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132759 }
2760 id currentNativeController =
2761 [self nativeControllerForTab:self.tabModel.currentTab];
2762 Protocol* possibleOwnerProtocol =
2763 @protocol(LogoAnimationControllerOwnerOwner);
2764 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2765 [currentNativeController logoAnimationControllerOwner]) {
2766 // If the current native controller is showing a GLIF view (e.g. the NTP
2767 // when there is no doodle), use that GLIFControllerOwner.
2768 return [currentNativeController logoAnimationControllerOwner];
2769 }
2770 return nil;
2771}
2772
2773#pragma mark - PassKitDialogProvider methods
2774
2775- (void)presentPassKitDialog:(NSData*)data {
2776 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272777 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132778 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272779 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132780 if (error || !data) {
2781 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302782 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132783 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302784 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132785 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2786 // NULL, replace if with DCHECK).
2787 if (infoBarManager)
2788 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2789 }
2790 } else {
2791 PKAddPassesViewController* passKitViewController =
2792 [_dependencyFactory newPassKitViewControllerForPass:pass];
2793 if (passKitViewController) {
2794 [self presentViewController:passKitViewController
2795 animated:YES
2796 completion:^{
2797 }];
2798 }
2799 }
2800}
2801
2802- (UIStatusBarStyle)preferredStatusBarStyle {
2803 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2804 : UIStatusBarStyleDefault;
2805}
2806
Tomasz Garbusb844e992017-09-29 12:44:552807#pragma mark - PasswordControllerDelegate methods
2808
2809- (BOOL)displaySignInNotification:(UIViewController*)viewController
2810 fromTabId:(NSString*)tabId {
2811 // Check if the call comes from currently visible tab.
2812 if ([tabId isEqual:[_model currentTab].tabId]) {
2813 [self addChildViewController:viewController];
2814 [self.view addSubview:viewController.view];
2815 [viewController didMoveToParentViewController:self];
2816 return YES;
2817 } else {
2818 return NO;
2819 }
2820}
2821
sdefresnee65fd872016-12-19 13:38:132822#pragma mark - CRWWebStateDelegate methods.
2823
eugenebut75a06fa72017-01-09 17:09:552824- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512825 createNewWebStateForURL:(const GURL&)URL
2826 openerURL:(const GURL&)openerURL
2827 initiatedByUser:(BOOL)initiatedByUser {
2828 // Check if requested web state is a popup and block it if necessary.
2829 if (!initiatedByUser) {
2830 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2831 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102832 // It's possible for a page to inject a popup into a window created via
2833 // window.open before its initial load is committed. Rather than relying
2834 // on the last committed or pending NavigationItem's referrer policy, just
2835 // use ReferrerPolicyDefault.
2836 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2837 // policy once referrer policies are correctly recorded in
2838 // NavigationItems.
2839 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512840 helper->HandlePopup(URL, referrer);
2841 return nil;
2842 }
2843 }
2844
2845 // Requested web state should not be blocked from opening.
2846 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2847 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2848
2849 // Tabs open by DOM are always renderer initiated.
2850 web::NavigationManager::WebLoadParams params(GURL{});
2851 params.transition_type = ui::PAGE_TRANSITION_LINK;
2852 params.is_renderer_initiated = true;
2853 Tab* childTab = [[self tabModel]
2854 insertTabWithLoadParams:params
2855 opener:currentTab
2856 openedByDOM:YES
2857 atIndex:TabModelConstants::kTabPositionAutomatically
2858 inBackground:NO];
2859 return childTab.webState;
2860}
2861
eugenebutb46b2122017-03-14 02:43:262862- (void)closeWebState:(web::WebState*)webState {
2863 // Only allow a web page to close itself if it was opened by DOM, or if there
2864 // are no navigation items.
2865 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582866 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262867
2868 if (![self tabModel])
2869 return;
2870
2871 NSUInteger index = [[self tabModel] indexOfTab:tab];
2872 if (index != NSNotFound)
2873 [[self tabModel] closeTabAtIndex:index];
2874}
2875
eugenebut275f5892017-03-09 22:20:512876- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552877 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2878 switch (params.disposition) {
2879 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2880 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
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:NO
2887 atIndex:TabModelConstants::kTabPositionAutomatically
2888 inBackground:(params.disposition ==
2889 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552890 return tab.webState;
2891 }
2892 case WindowOpenDisposition::CURRENT_TAB: {
2893 web::NavigationManager::WebLoadParams loadParams(params.url);
2894 loadParams.referrer = params.referrer;
2895 loadParams.transition_type = params.transition;
2896 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2897 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2898 return webState;
2899 }
eugenebutd0984e82017-02-22 23:47:512900 case WindowOpenDisposition::NEW_POPUP: {
2901 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352902 insertTabWithURL:params.url
2903 referrer:params.referrer
2904 transition:params.transition
2905 opener:LegacyTabHelper::GetTabForWebState(webState)
2906 openedByDOM:YES
2907 atIndex:TabModelConstants::kTabPositionAutomatically
2908 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512909 return tab.webState;
2910 }
eugenebut75a06fa72017-01-09 17:09:552911 default:
2912 NOTIMPLEMENTED();
2913 return nullptr;
2914 };
2915}
2916
Mike Dougherty4e6b3a32017-08-23 18:49:212917- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132918 handleContextMenu:(const web::ContextMenuParams&)params {
2919 // Prevent context menu from displaying for a tab which is no longer the
2920 // current one.
2921 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212922 return;
sdefresnee65fd872016-12-19 13:38:132923 }
2924
2925 // No custom context menu if no valid url is available in |params|.
2926 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212927 return;
sdefresnee65fd872016-12-19 13:38:132928 }
2929
2930 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:132931
stkhapuginc9eee7b2017-04-10 15:49:272932 _contextMenuCoordinator =
2933 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2934 params:params];
sdefresnee65fd872016-12-19 13:38:132935
2936 NSString* title = nil;
2937 ProceduralBlock action = nil;
2938
stkhapuginc9eee7b2017-04-10 15:49:272939 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132940 GURL link = params.link_url;
2941 bool isLink = link.is_valid();
2942 GURL imageUrl = params.src_url;
2943 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:192944 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:132945
2946 if (isLink) {
2947 if (link.SchemeIs(url::kJavaScriptScheme)) {
2948 // Open
2949 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2950 action = ^{
2951 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2952 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2953 };
2954 [_contextMenuCoordinator addItemWithTitle:title action:action];
2955 }
2956
2957 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192958 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132959
sdefresnee65fd872016-12-19 13:38:132960 // Open in New Tab.
2961 title = l10n_util::GetNSStringWithFixup(
2962 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2963 action = ^{
2964 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002965 // The "New Tab" item in the context menu opens a new tab in the current
2966 // browser state. |isOffTheRecord| indicates whether or not the current
2967 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132968 [weakSelf webPageOrderedOpen:link
2969 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002970 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132971 inBackground:YES
2972 appendTo:kCurrentTab];
2973 };
2974 [_contextMenuCoordinator addItemWithTitle:title action:action];
2975 if (!_isOffTheRecord) {
2976 // Open in Incognito Tab.
2977 title = l10n_util::GetNSStringWithFixup(
2978 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2979 action = ^{
2980 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2981 [weakSelf webPageOrderedOpen:link
2982 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132983 inIncognito:YES
2984 inBackground:NO
2985 appendTo:kCurrentTab];
2986 };
2987 [_contextMenuCoordinator addItemWithTitle:title action:action];
2988 }
olivierrobin51d4cf42017-01-17 13:32:352989 }
gambard65d69152017-03-23 17:44:222990 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352991 NSString* innerText = params.link_text;
2992 if ([innerText length] > 0) {
2993 // Add to reading list.
2994 title = l10n_util::GetNSStringWithFixup(
2995 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2996 action = ^{
2997 Record(ACTION_READ_LATER, isImage, isLink);
2998 [weakSelf addToReadingListURL:link title:innerText];
2999 };
3000 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:533001 }
sdefresnee65fd872016-12-19 13:38:133002 }
3003 // Copy Link.
3004 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
3005 action = ^{
3006 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:283007 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:133008 };
3009 [_contextMenuCoordinator addItemWithTitle:title action:action];
3010 }
3011 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193012 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133013 // Save Image.
gambard98b4ddf2017-04-18 07:14:053014 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:133015 action = ^{
3016 Record(ACTION_SAVE_IMAGE, isImage, isLink);
3017 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
3018 };
3019 [_contextMenuCoordinator addItemWithTitle:title action:action];
3020 // Open Image.
3021 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
3022 action = ^{
3023 Record(ACTION_OPEN_IMAGE, isImage, isLink);
3024 [weakSelf loadURL:imageUrl
3025 referrer:referrer
3026 transition:ui::PAGE_TRANSITION_LINK
3027 rendererInitiated:YES];
3028 };
3029 [_contextMenuCoordinator addItemWithTitle:title action:action];
3030 // Open Image In New Tab.
3031 title = l10n_util::GetNSStringWithFixup(
3032 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
3033 action = ^{
3034 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
3035 [weakSelf webPageOrderedOpen:imageUrl
3036 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133037 inBackground:true
3038 appendTo:kCurrentTab];
3039 };
3040 [_contextMenuCoordinator addItemWithTitle:title action:action];
3041
3042 TemplateURLService* service =
3043 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103044 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133045 if (defaultURL && !defaultURL->image_url().empty() &&
3046 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3047 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3048 defaultURL->short_name());
3049 action = ^{
3050 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3051 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3052 };
3053 [_contextMenuCoordinator addItemWithTitle:title action:action];
3054 }
3055 }
3056
3057 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133058}
3059
eugenebutb739bdc2017-01-25 06:32:483060- (void)webState:(web::WebState*)webState
3061 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3062 // Display the action sheet with the arrow pointing at the top center of the
3063 // web contents.
sdefresne0452a9d2017-02-09 15:33:283064 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483065 UIView* view = webState->GetView();
3066 CGPoint dialogLocation =
3067 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283068 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033069 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173070 helper->PresentDialog(dialogLocation,
3071 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053072 handler(shouldContinue);
3073 }));
eugenebutb739bdc2017-01-25 06:32:483074}
3075
sdefresnee65fd872016-12-19 13:38:133076- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3077 (web::WebState*)webState {
3078 return _javaScriptDialogPresenter.get();
3079}
3080
eugenebut63232102017-01-19 16:19:403081- (void)webState:(web::WebState*)webState
3082 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3083 proposedCredential:(NSURLCredential*)proposedCredential
3084 completionHandler:(void (^)(NSString* username,
3085 NSString* password))handler {
eugenebut862085f2017-03-28 16:47:423086 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
3087 if ([tab isPrerenderTab]) {
3088 [tab discardPrerender];
3089 if (handler) {
3090 handler(nil, nil);
3091 }
3092 return;
3093 }
3094
eugenebut63232102017-01-19 16:19:403095 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3096 proposedCredential:proposedCredential
3097 webState:webState
3098 completionHandler:handler];
3099}
3100
Kurt Horimoto62e97c72017-11-03 19:51:473101#pragma mark - LegacyFullscreenControllerDelegate methods
sdefresnee65fd872016-12-19 13:38:133102
3103- (CGFloat)headerOffset {
3104 if (IsIPadIdiom())
3105 return StatusBarHeight();
3106 return 0.0;
3107}
3108
stkhapugin952ecef2017-04-11 12:11:453109- (NSArray<HeaderDefinition*>*)headerViews {
3110 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:133111 if (![self isViewLoaded])
3112 return results;
3113
3114 if (!IsIPadIdiom()) {
sczs42f7f7482017-11-08 01:13:273115 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453116 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273117 definitionWithView:_toolbarCoordinator
3118 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453119 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243120 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453121 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133122 }
3123 } else {
edchinf5150c682017-09-18 02:50:033124 if (self.tabStripView) {
3125 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
3126 headerBehaviour:Hideable
3127 heightAdjustment:0.0
3128 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133129 }
sczs42f7f7482017-11-08 01:13:273130 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453131 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273132 definitionWithView:_toolbarCoordinator
3133 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453134 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243135 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453136 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133137 }
3138 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:453139 [results addObject:[HeaderDefinition
3140 definitionWithView:[_findBarController view]
3141 headerBehaviour:Overlap
3142 heightAdjustment:0.0
3143 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:133144 }
3145 }
stkhapugin952ecef2017-04-11 12:11:453146 return [results copy];
sdefresnee65fd872016-12-19 13:38:133147}
3148
3149- (UIView*)footerView {
3150 return _voiceSearchBar;
3151}
3152
3153- (CGFloat)headerHeight {
3154 return [self headerHeightForTab:[_model currentTab]];
3155}
3156
3157- (CGFloat)headerHeightForTab:(Tab*)tab {
3158 id nativeController = [self nativeControllerForTab:tab];
3159 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3160 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3161 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3162 // On iPhone, don't add any header height for ToolbarOwner native
3163 // controllers when they're displaying their own toolbar.
3164 return 0;
3165 }
3166
stkhapugin952ecef2017-04-11 12:11:453167 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133168
3169 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453170 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133171 if (header.view && header.behaviour == Hideable) {
3172 height += CGRectGetHeight([header.view frame]) -
3173 header.heightAdjustement - header.inset;
3174 }
3175 }
3176
3177 return height - StatusBarHeight();
3178}
3179
3180- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193181 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133182}
3183
3184- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453185 NSArray<HeaderDefinition*>* headers = [self headerViews];
3186 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133187 return 0.0;
3188
3189 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3190 // API documentation.
3191 if ([[[self tabModel] currentTab] isPrerenderTab])
3192 return [self headerHeight];
3193
3194 UIView* topHeader = headers[0].view;
3195 return -(topHeader.frame.origin.y - [self headerOffset]);
3196}
3197
3198- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3199 UIView* footer = [self footerView];
3200 CGFloat headerHeight = [self headerHeight];
3201 if (!footer || headerHeight == 0)
3202 return 0.0;
3203
3204 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3205 CGFloat offset = headerOffset * footerHeight / headerHeight;
3206 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3207}
3208
Kurt Horimoto62e97c72017-11-03 19:51:473209- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:133210 headerAnimationCompleted:(BOOL)completed
3211 offset:(CGFloat)offset {
3212 if (completed)
justincohen04c27772016-12-21 20:16:593213 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133214}
3215
stkhapugin952ecef2017-04-11 12:11:453216- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133217 atOffset:(CGFloat)headerOffset {
3218 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453219 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:133220 CGRect frame = [header.view frame];
3221 frame.origin.y = height - headerOffset - header.inset;
3222 [header.view setFrame:frame];
3223 if (header.behaviour != Overlap)
3224 height += CGRectGetHeight(frame);
3225 }
3226}
3227
Kurt Horimoto62e97c72017-11-03 19:51:473228- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133229 drawHeaderViewFromOffset:(CGFloat)headerOffset
3230 animate:(BOOL)animate {
3231 if ([_sideSwipeController inSwipe])
3232 return;
3233
3234 CGRect footerFrame = CGRectZero;
3235 UIView* footer = nil;
3236 // Only animate the voice search bar if the tab is a voice search results tab.
3237 if ([_model currentTab].isVoiceSearchResultsTab) {
3238 footer = [self footerView];
3239 footerFrame = footer.frame;
3240 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3241 }
3242
stkhapugin952ecef2017-04-11 12:11:453243 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133244 void (^block)(void) = ^{
3245 [self setFramesForHeaders:headers atOffset:headerOffset];
3246 footer.frame = footerFrame;
3247 };
3248 void (^completion)(BOOL) = ^(BOOL finished) {
3249 [self fullScreenController:fullScreenController
3250 headerAnimationCompleted:finished
3251 offset:headerOffset];
3252 };
3253 if (animate) {
Kurt Horimoto62e97c72017-11-03 19:51:473254 [UIView
3255 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3256 delay:0.0
3257 options:UIViewAnimationOptionBeginFromCurrentState
3258 animations:block
3259 completion:completion];
sdefresnee65fd872016-12-19 13:38:133260 } else {
3261 block();
3262 completion(YES);
3263 }
3264}
3265
Kurt Horimoto62e97c72017-11-03 19:51:473266- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133267 drawHeaderViewFromOffset:(CGFloat)headerOffset
3268 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3269 changeTopContentPadding:(BOOL)changeTopContentPadding
3270 scrollingToOffset:(CGFloat)contentOffset {
3271 DCHECK(webViewProxy);
3272 if ([_sideSwipeController inSwipe])
3273 return;
3274
3275 CGRect footerFrame;
3276 UIView* footer = nil;
3277 // Only animate the voice search bar if the tab is a voice search results tab.
3278 if ([_model currentTab].isVoiceSearchResultsTab) {
3279 footer = [self footerView];
3280 footerFrame = footer.frame;
3281 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3282 }
3283
stkhapugin952ecef2017-04-11 12:11:453284 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133285 void (^block)(void) = ^{
3286 [self setFramesForHeaders:headers atOffset:headerOffset];
3287 footer.frame = footerFrame;
3288 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3289 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3290 if (changeTopContentPadding)
3291 webViewProxy.topContentPadding = contentOffset;
3292 };
3293 void (^completion)(BOOL) = ^(BOOL finished) {
3294 [self fullScreenController:fullScreenController
3295 headerAnimationCompleted:finished
3296 offset:headerOffset];
3297 };
3298
Kurt Horimoto62e97c72017-11-03 19:51:473299 [UIView
3300 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3301 delay:0.0
3302 options:UIViewAnimationOptionBeginFromCurrentState
3303 animations:block
3304 completion:completion];
sdefresnee65fd872016-12-19 13:38:133305}
3306
3307#pragma mark - VoiceSearchBarOwner
3308
3309- (id<VoiceSearchBar>)voiceSearchBar {
3310 return _voiceSearchBar;
3311}
3312
3313#pragma mark - Install OverScrollActionController method.
3314- (void)setOverScrollActionControllerToStaticNativeContent:
3315 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343316 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133317 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173318 [[OverscrollActionsController alloc]
3319 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133320 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053321 OverscrollStyle style = _isOffTheRecord
3322 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3323 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133324 controller.style = style;
3325 nativeContent.overscrollActionsController = controller;
3326 }
3327}
3328
3329#pragma mark - OverscrollActionsControllerDelegate methods.
3330
3331- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053332 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133333 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053334 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383335 [self.dispatcher
3336 openNewTab:[OpenNewTabCommand
3337 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133338 break;
rohitrao922b7111c2017-01-03 14:31:053339 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243340 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133341 break;
liaoyuke563dc4a2017-03-17 18:36:293342 case OverscrollAction::REFRESH: {
liaoyuke563dc4a2017-03-17 18:36:293343 web::WebState* webState = [_model currentTab].webState;
Eugene But083b6c7a2017-10-02 15:49:383344 if (webState) {
3345 if (webState->IsLoading()) {
3346 webState->Stop();
3347 }
liaoyuke563dc4a2017-03-17 18:36:293348 // |check_for_repost| is true because the reload is explicitly initiated
3349 // by the user.
3350 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3351 true /* check_for_repost */);
Eugene But083b6c7a2017-10-02 15:49:383352 }
sdefresnee65fd872016-12-19 13:38:133353 break;
liaoyuke563dc4a2017-03-17 18:36:293354 }
rohitrao922b7111c2017-01-03 14:31:053355 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133356 NOTREACHED();
3357 break;
3358 }
3359}
3360
3361- (BOOL)shouldAllowOverscrollActions {
3362 return YES;
3363}
3364
3365- (UIView*)headerView {
sczs42f7f7482017-11-08 01:13:273366 return _toolbarCoordinator.toolbarViewController.view;
sdefresnee65fd872016-12-19 13:38:133367}
3368
3369- (UIView*)toolbarSnapshotView {
sczs42f7f7482017-11-08 01:13:273370 return [_toolbarCoordinator.toolbarViewController.view
3371 snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133372}
3373
3374- (CGFloat)overscrollActionsControllerHeaderInset:
3375 (OverscrollActionsController*)controller {
3376 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3377 return [self headerHeight];
3378 else
3379 return 0;
3380}
3381
3382- (CGFloat)overscrollHeaderHeight {
3383 return [self headerHeight] + StatusBarHeight();
3384}
3385
3386#pragma mark - TabSnapshottingDelegate methods.
3387
3388- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3389 CGRect pageContentArea = _contentArea.bounds;
3390 if ([_model webUsageEnabled])
3391 pageContentArea = tab.view.bounds;
3392 CGFloat headerHeight = [self headerHeightForTab:tab];
3393 id nativeController = [self nativeControllerForTab:tab];
3394 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3395 headerHeight += [nativeController toolbarHeight];
3396 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3397 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3398}
3399
3400#pragma mark - NewTabPageObserver methods.
3401
3402- (void)selectedPanelDidChange {
3403 [self updateToolbar];
3404}
3405
3406#pragma mark - CRWNativeContentProvider methods
3407
3408- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3409 withError:(NSError*)error
3410 isPost:(BOOL)isPost {
3411 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173412 [[ErrorPageContent alloc] initWithLoader:self
3413 browserState:self.browserState
3414 url:url
3415 error:error
3416 isPost:isPost
3417 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133418 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3419 return errorPageContent;
3420}
3421
3422- (BOOL)hasControllerForURL:(const GURL&)url {
Marti Wong64481ec2017-10-31 03:38:003423 base::StringPiece host = url.host_piece();
olivierrobin5c861c22017-04-07 15:56:453424 if (host == kChromeUIOfflineHost) {
3425 // Only allow offline URL that are fully specified.
3426 return reading_list::IsOfflineURLValid(
3427 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3428 }
sdefresnee65fd872016-12-19 13:38:133429
Justin Cohen8679e852017-08-14 16:35:253430 if (host == kChromeUIBookmarksHost) {
Marti Wong64481ec2017-10-31 03:38:003431 return IsBookmarksHostEnabled();
Justin Cohen8679e852017-08-14 16:35:253432 }
3433
3434 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133435}
3436
olivierrobind43eecb2017-01-27 20:35:263437- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3438 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133439 DCHECK(url.SchemeIs(kChromeUIScheme));
3440
3441 id<CRWNativeContent> nativeController = nil;
Marti Wong64481ec2017-10-31 03:38:003442 base::StringPiece url_host = url.host_piece();
Justin Cohen49715952017-08-22 14:12:193443 if (url_host == kChromeUINewTabHost ||
Marti Wong64481ec2017-10-31 03:38:003444 (url_host == kChromeUIBookmarksHost && IsBookmarksHostEnabled())) {
Gauthier Ambardd8890452017-09-29 12:07:463445 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3446 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3447 if (@available(iOS 11.0, *)) {
3448 safeAreaInset = self.view.safeAreaInsets;
3449 }
3450 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3451
sdefresnee65fd872016-12-19 13:38:133452 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173453 [[NewTabPageController alloc] initWithUrl:url
3454 loader:self
sczsf1620e52017-10-02 22:54:463455 focuser:_toolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173456 ntpObserver:self
3457 browserState:_browserState
3458 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463459 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453460 tabModel:_model
justincohen75011c32017-04-28 16:31:393461 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463462 dispatcher:self.dispatcher
3463 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133464 pageController.swipeRecognizerProvider = self.sideSwipeController;
3465
3466 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233467 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133468
Marti Wong64481ec2017-10-31 03:38:003469 if (IsBookmarksHostEnabled()) {
sdefresnee65fd872016-12-19 13:38:133470 // New Tab Page can have multiple panels. Each panel is addressable
3471 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3472 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3473 // the Bookmark Manager, etc.
3474 // The utility functions NewTabPage::IdentifierFromFragment() and
3475 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3476 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3477 // without changing the URL since the URL may be chrome://bookmarks/#123.
3478 // If the URL is chrome://newtab/, pre-select the panel based on the
3479 // #fragment.
3480 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233481 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133482 : NewTabPage::IdentifierFromFragment(url.ref());
3483 }
3484 [pageController selectPanel:panelType];
3485 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453486 } else if (url_host == kChromeUIOfflineHost &&
3487 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133488 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173489 [[OfflinePageNativeContent alloc] initWithLoader:self
3490 browserState:_browserState
3491 webState:webState
3492 URL:url];
sdefresnee65fd872016-12-19 13:38:133493 [self setOverScrollActionControllerToStaticNativeContent:
3494 staticNativeController];
3495 nativeController = staticNativeController;
3496 } else if (url_host == kChromeUIExternalFileHost) {
3497 // Return an instance of the |ExternalFileController| only if the file is
3498 // still in the sandbox.
3499 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3500 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173501 nativeController =
3502 [[ExternalFileController alloc] initWithURL:url
3503 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133504 }
peterlaurens44615d02017-05-23 20:23:093505 } else if (url_host == kChromeUICrashHost) {
3506 // There is no native controller for kChromeUICrashHost, it is instead
3507 // handled as any other renderer crash by the SadTabTabHelper.
3508 // nativeController must be set to nil to prevent defaulting to a
3509 // PageNotAvailableController.
3510 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133511 } else {
3512 DCHECK(![self hasControllerForURL:url]);
3513 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173514 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133515 }
3516 // If a native controller is vended before its tab is added to the tab model,
3517 // use the temporary key and add it under the new tab's tabId in the
3518 // TabModelObserver callback. This happens:
3519 // - when there is no current tab (occurs when vending the NTP controller for
3520 // the first tab that is opened),
3521 // - when the current tab's url doesn't match |url| (occurs when a native
3522 // controller is opened in a new tab)
3523 // - when the current tab's url matches |url| and there is already a native
3524 // controller of the appropriate type vended to it (occurs when a native
3525 // controller is opened in a new tab from a tab with a matching URL, e.g.
3526 // opening an NTP when an NTP is already displayed in the current tab).
3527 // For normal page loads, history navigations, tab restorations, and crash
3528 // recoveries, the tab will already exist in the tab model and the tabId can
3529 // be used as the native controller key.
3530 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3531 // that native controllers vended here always correspond to the current tab.
3532 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193533 if (!currentTab.webState ||
3534 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443535 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133536 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443537 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133538 }
sdefresnee65fd872016-12-19 13:38:133539 return nativeController;
3540}
3541
3542- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443543 id nativeController = tab.webController.nativeController;
3544 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133545}
3546
3547#pragma mark - DialogPresenterDelegate methods
3548
3549- (void)dialogPresenter:(DialogPresenter*)presenter
3550 willShowDialogForWebState:(web::WebState*)webState {
3551 for (Tab* iteratedTab in self.tabModel) {
3552 if ([iteratedTab webState] == webState) {
3553 self.tabModel.currentTab = iteratedTab;
3554 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3555 break;
3556 }
3557 }
3558}
3559
3560#pragma mark - Context menu methods
3561
3562- (void)searchByImageAtURL:(const GURL&)url
3563 referrer:(const web::Referrer)referrer {
3564 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273565 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113566 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173567 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3568 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113569 DCHECK(data);
3570 dispatch_async(dispatch_get_main_queue(), ^{
3571 [weakSelf searchByImageData:data atURL:image_source_url];
3572 });
3573 };
3574 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133575 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3576 web::PolicyForNavigation(url, referrer));
3577}
3578
3579- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3580 NSData* imageData = data;
3581 UIImage* image = [UIImage imageWithData:imageData];
3582 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3583 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3584 // kSearchByImageMaxImageHeight).
3585 if (image &&
3586 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3587 (image.size.width > kSearchByImageMaxImageWidth ||
3588 image.size.height > kSearchByImageMaxImageHeight)) {
3589 CGSize newImageSize =
3590 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3591 image = [image gtm_imageByResizingToSize:newImageSize
3592 preserveAspectRatio:YES
3593 trimToFit:NO];
3594 imageData = UIImageJPEGRepresentation(image, 1.0);
3595 }
3596
3597 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3598 std::string byteString(bytes, [imageData length]);
3599
3600 TemplateURLService* templateUrlService =
3601 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103602 const TemplateURL* defaultURL =
3603 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133604 DCHECK(!defaultURL->image_url().empty());
3605 DCHECK(defaultURL->image_url_ref().IsValid(
3606 templateUrlService->search_terms_data()));
3607 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3608 search_args.image_url = imageURL;
3609 search_args.image_thumbnail_content = byteString;
3610
3611 // Generate the URL and populate |post_content| with the content type and
3612 // HTTP body for the request.
3613 TemplateURLRef::PostContent post_content;
3614 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3615 search_args, templateUrlService->search_terms_data(), &post_content));
3616 [self addSelectedTabWithURL:result
3617 postData:&post_content
3618 transition:ui::PAGE_TRANSITION_TYPED];
3619}
3620
3621- (void)saveImageAtURL:(const GURL&)url
3622 referrer:(const web::Referrer&)referrer {
3623 DCHECK(url.is_valid());
3624
gambard9efce7a2017-02-09 18:53:173625 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3626 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113627 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133628
gambardbbf85c42017-06-29 11:15:343629 if ([data length] == 0) {
3630 [self displayPrivacyErrorAlertOnMainQueue:
3631 l10n_util::GetNSString(
3632 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3633 return;
3634 }
3635
gambard9efce7a2017-02-09 18:53:173636 base::FilePath::StringType extension;
3637
3638 bool extensionSuccess =
3639 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3640 if (!extensionSuccess || extension.length() == 0) {
3641 extension = "png";
3642 }
3643
3644 NSString* fileExtension =
3645 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3646 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113647 };
3648 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133649 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3650 web::PolicyForNavigation(url, referrer));
3651}
3652
gambard9efce7a2017-02-09 18:53:173653- (void)managePermissionAndSaveImage:(NSData*)data
3654 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133655 switch ([PHPhotoLibrary authorizationStatus]) {
3656 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173657 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133658 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3659 // Call -saveImage again to check if chrome needs to display an error or
3660 // saves the image.
3661 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173662 [self managePermissionAndSaveImage:data
3663 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133664 }];
3665 break;
stkhapuginf58b10d02017-04-10 13:36:173666 }
sdefresnee65fd872016-12-19 13:38:133667
3668 // The application doesn't have permission to access photo and the user
3669 // cannot grant it.
3670 case PHAuthorizationStatusRestricted:
3671 [self displayPrivacyErrorAlertOnMainQueue:
3672 l10n_util::GetNSString(
3673 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3674 break;
3675
3676 // The application doesn't have permission to access photo and the user
3677 // can grant it.
3678 case PHAuthorizationStatusDenied:
3679 [self displayImageErrorAlertWithSettingsOnMainQueue];
3680 break;
3681
3682 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243683 default:
3684 __weak BrowserViewController* weakSelf = self;
3685 [self saveImage:data
3686 withFileExtension:fileExtension
3687 completion:^(BOOL success, NSError* error) {
3688 [weakSelf finishSavingImageWithError:error];
3689 }];
sdefresnee65fd872016-12-19 13:38:133690 break;
sdefresnee65fd872016-12-19 13:38:133691 }
3692}
3693
Sylvain Defresnefd3ecf22017-07-12 18:47:243694- (void)saveImage:(NSData*)data
3695 withFileExtension:(NSString*)fileExtension
3696 completion:(void (^)(BOOL, NSError*))completion {
3697 base::PostTaskWithTraits(
3698 FROM_HERE,
3699 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3700 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3701 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373702 base::AssertBlockingAllowed();
sdefresnee65fd872016-12-19 13:38:133703
Sylvain Defresnefd3ecf22017-07-12 18:47:243704 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3705 stringByAppendingString:fileExtension];
3706 NSURL* fileURL = [NSURL
3707 fileURLWithPath:[NSTemporaryDirectory()
3708 stringByAppendingPathComponent:fileName]];
3709 NSError* error = nil;
3710 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3711 if (error) {
3712 if (completion)
3713 completion(NO, error);
3714 return;
3715 }
sdefresnee65fd872016-12-19 13:38:133716
Sylvain Defresnefd3ecf22017-07-12 18:47:243717 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3718 [PHAssetChangeRequest
3719 creationRequestForAssetFromImageAtFileURL:fileURL];
3720 }
3721 completionHandler:^(BOOL success, NSError* error) {
3722 base::PostTaskWithTraits(
3723 FROM_HERE,
3724 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3725 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3726 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373727 base::AssertBlockingAllowed();
Sylvain Defresnefd3ecf22017-07-12 18:47:243728 if (completion)
3729 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133730
Sylvain Defresnefd3ecf22017-07-12 18:47:243731 // Cleanup the temporary file.
3732 NSError* deleteFileError = nil;
3733 [[NSFileManager defaultManager]
3734 removeItemAtURL:fileURL
3735 error:&deleteFileError];
3736 }));
3737 }];
3738 }));
sdefresnee65fd872016-12-19 13:38:133739}
3740
3741- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3742 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3743 BOOL canGoToSetting =
3744 [[UIApplication sharedApplication] canOpenURL:settingURL];
3745 if (canGoToSetting) {
3746 dispatch_async(dispatch_get_main_queue(), ^{
3747 [self displayImageErrorAlertWithSettings:settingURL];
3748 });
3749 } else {
3750 [self displayPrivacyErrorAlertOnMainQueue:
3751 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3752 }
3753}
3754
3755- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3756 // Dismiss current alert.
3757 [_alertCoordinator stop];
3758
3759 NSString* title =
3760 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3761 NSString* message = l10n_util::GetNSString(
3762 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3763
stkhapuginc9eee7b2017-04-10 15:49:273764 _alertCoordinator =
3765 [[AlertCoordinator alloc] initWithBaseViewController:self
3766 title:title
3767 message:message];
sdefresnee65fd872016-12-19 13:38:133768
3769 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3770 action:nil
3771 style:UIAlertActionStyleCancel];
3772
3773 [_alertCoordinator
3774 addItemWithTitle:l10n_util::GetNSString(
3775 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3776 action:^{
3777 OpenUrlWithCompletionHandler(settingURL, nil);
3778 }
3779 style:UIAlertActionStyleDefault];
3780
3781 [_alertCoordinator start];
3782}
3783
3784- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3785 dispatch_async(dispatch_get_main_queue(), ^{
3786 NSString* title =
3787 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3788 [self showErrorAlertWithStringTitle:title message:errorContent];
3789 });
3790}
3791
3792// This callback is triggered when the image is effectively saved onto the photo
3793// album, or if the save failed for some reason.
3794- (void)finishSavingImageWithError:(NSError*)error {
3795 // Was there an error?
3796 if (error) {
3797 // Saving photo failed even though user has granted access to Photos.
3798 // Display the error information from the NSError object for user.
3799 NSString* errorMessage = [NSString
3800 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3801 [error domain], [error code]];
3802 // This code may be execute outside of the main thread. Make sure to display
3803 // the error on the main thread.
3804 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3805 } else {
3806 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3807 // the photo application. The current behaviour is to create the photo there
3808 // but not providing any link to it is suboptimal. That's what Safari is
3809 // doing, and what the PM want, but it doesn't make it right.
3810 }
3811}
3812
sdefresnee65fd872016-12-19 13:38:133813#pragma mark - Showing popups
3814
sdefresnee65fd872016-12-19 13:38:133815- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133816 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3817
3818 ReadingListModel* readingModel =
3819 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013820 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3821 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133822
pinkerton07e27842017-03-02 15:29:023823 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
edchineeb4d422017-10-02 17:39:363824 [self showSnackbar:l10n_util::GetNSString(
3825 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133826}
3827
3828#pragma mark - Keyboard commands management
3829
3830- (BOOL)shouldRegisterKeyboardCommands {
3831 if ([self presentedViewController])
3832 return NO;
3833
3834 if (_voiceSearchController && _voiceSearchController->IsVisible())
3835 return NO;
3836
3837 // If there is no first responder, try to make the webview the first
3838 // responder.
3839 if (!GetFirstResponder()) {
Eugene But08be7d02017-10-02 15:49:303840 web::WebState* webState = _model.currentTab.webState;
3841 if (webState)
3842 [webState->GetWebViewProxy() becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133843 }
3844
3845 return YES;
3846}
3847
3848- (KeyCommandsProvider*)keyCommandsProvider {
3849 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273850 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133851 }
stkhapuginc9eee7b2017-04-10 15:49:273852 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133853}
3854
3855#pragma mark - KeyCommandsPlumbing
3856
3857- (BOOL)isOffTheRecord {
3858 return _isOffTheRecord;
3859}
3860
3861- (NSUInteger)tabsCount {
3862 return [_model count];
3863}
3864
lpromero47ea8862017-01-13 17:51:063865- (BOOL)canGoBack {
3866 return [_model currentTab].canGoBack;
3867}
3868
3869- (BOOL)canGoForward {
3870 return [_model currentTab].canGoForward;
3871}
3872
sdefresnee65fd872016-12-19 13:38:133873- (void)focusTabAtIndex:(NSUInteger)index {
3874 if ([_model count] > index) {
3875 [_model setCurrentTab:[_model tabAtIndex:index]];
3876 }
3877}
3878
3879- (void)focusNextTab {
3880 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3881 NSInteger modelCount = [_model count];
3882 if (currentTabIndex < modelCount - 1) {
3883 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3884 [_model setCurrentTab:nextTab];
3885 } else {
3886 [_model setCurrentTab:[_model tabAtIndex:0]];
3887 }
3888}
3889
3890- (void)focusPreviousTab {
3891 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3892 if (currentTabIndex > 0) {
3893 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3894 [_model setCurrentTab:previousTab];
3895 } else {
3896 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3897 [_model setCurrentTab:lastTab];
3898 }
3899}
3900
3901- (void)reopenClosedTab {
3902 sessions::TabRestoreService* const tabRestoreService =
3903 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3904 if (!tabRestoreService || tabRestoreService->entries().empty())
3905 return;
3906
3907 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3908 tabRestoreService->entries().front();
3909 // Only handle the TAB type.
3910 if (entry->type != sessions::TabRestoreService::TAB)
3911 return;
3912
Mark Cogandfcdea72017-07-18 13:47:383913 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133914 TabRestoreServiceDelegateImplIOS* const delegate =
3915 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3916 _browserState);
3917 tabRestoreService->RestoreEntryById(delegate, entry->id,
3918 WindowOpenDisposition::CURRENT_TAB);
3919}
3920
3921- (void)focusOmnibox {
sczsf1620e52017-10-02 22:54:463922 [_toolbarCoordinator focusOmnibox];
sdefresnee65fd872016-12-19 13:38:133923}
3924
3925#pragma mark - UIResponder
3926
3927- (NSArray*)keyCommands {
3928 if (![self shouldRegisterKeyboardCommands]) {
3929 return nil;
3930 }
3931 return [self.keyCommandsProvider
3932 keyCommandsForConsumer:self
edchin8e4cfe032017-10-25 13:25:543933 baseViewController:self
Mark Cogan6c58ea92017-07-06 13:08:243934 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133935 editingText:![self isFirstResponder]];
3936}
3937
3938#pragma mark -
3939
3940// Induce an intentional crash in the browser process.
3941- (void)induceBrowserCrash {
3942 CHECK(false);
3943 // Call another function, so that the above CHECK can't be tail-call
3944 // optimized. This ensures that this method's name will show up in the stack
3945 // for easier identification.
3946 CHECK(true);
3947}
3948
3949- (void)loadURL:(const GURL&)url
3950 referrer:(const web::Referrer&)referrer
3951 transition:(ui::PageTransition)transition
3952 rendererInitiated:(BOOL)rendererInitiated {
3953 [[OmniboxGeolocationController sharedInstance]
3954 locationBarDidSubmitURL:url
3955 transition:transition
3956 browserState:_browserState];
3957
3958 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3959 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3960 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3961 }
3962
3963 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243964 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133965 // restart.
3966 if (url.host() == kChromeUIBrowserCrashHost) {
3967 [self induceBrowserCrash];
3968 // In debug the app can continue working even after the CHECK. Adding a
3969 // return avoids the crash url to be added to the history.
3970 return;
3971 }
3972
Danyao Wang85389a82017-10-25 18:56:273973 bool typed_or_generated_transition =
3974 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
3975 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
3976
Rohit Rao44f204302017-08-10 14:49:543977 PrerenderService* prerenderService =
3978 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3979 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593980 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543981 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593982 DCHECK(newWebState);
3983
sdefresnee65fd872016-12-19 13:38:133984 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593985 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133986 DCHECK(oldTab);
3987 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593988
kkhorimotod804c5732017-03-15 23:44:523989 bool canPruneItems =
3990 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593991
kkhorimotod804c5732017-03-15 23:44:523992 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523993 [newTab navigationManager]->CopyStateFromAndPrune(
3994 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593995
3996 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3997 std::move(newWebState));
sdefresnee65fd872016-12-19 13:38:133998
3999 // Set isPrerenderTab to NO after replacing the tab. This will allow the
4000 // BrowserViewController to detect that a pre-rendered tab is switched in,
4001 // and show the prerendering animation.
4002 newTab.isPrerenderTab = NO;
Danyao Wang85389a82017-10-25 18:56:274003 if (typed_or_generated_transition) {
4004 LoadTimingTabHelper::FromWebState(newTab.webState)
4005 ->DidPromotePrerenderTab();
4006 }
sdefresnee65fd872016-12-19 13:38:134007
sdefresne2f7781c2017-03-02 19:12:464008 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:134009 return;
4010 }
4011 }
4012
4013 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:544014 if (prerenderService) {
4015 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:134016 }
4017
sdefresnee65fd872016-12-19 13:38:134018 // Some URLs are not allowed while in incognito. If we are in incognito and
4019 // load a disallowed URL, instead create a new tab not in the incognito state.
4020 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
4021 [self webPageOrderedOpen:url
4022 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134023 inIncognito:NO
4024 inBackground:NO
4025 appendTo:kCurrentTab];
4026 return;
4027 }
4028
Danyao Wang85389a82017-10-25 18:56:274029 if (typed_or_generated_transition) {
4030 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
4031 ->DidInitiatePageLoad();
4032 }
4033
mrefaata84d5a02017-06-08 17:13:294034 // If this is a reload initiated from the omnibox.
4035 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
4036 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
4037 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
4038 [[_model currentTab] navigationManager]->Reload(
4039 web::ReloadType::NORMAL, true /* check_for_repost */);
4040 return;
4041 }
4042
sdefresnee65fd872016-12-19 13:38:134043 web::NavigationManager::WebLoadParams params(urlToLoad);
4044 params.referrer = referrer;
4045 params.transition_type = transition;
4046 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104047 Tab* currentTab = [_model currentTab];
4048 DCHECK(currentTab);
4049 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4050 currentTab.navigationManager->LoadURLWithParams(params);
4051 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4052 // to be animated on screen because the transition animator will handle the
4053 // animations. When a Tab stops being a voice search Tab, the voice search
4054 // bar should be animated away.
4055 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4056 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134057}
4058
4059- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544060 PrerenderService* prerenderService =
4061 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4062 if (prerenderService) {
4063 prerenderService->CancelPrerender();
4064 }
sdefresnee65fd872016-12-19 13:38:134065 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:184066 if ([self currentWebState])
4067 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134068}
4069
4070- (web::WebState*)currentWebState {
4071 return [[_model currentTab] webState];
4072}
4073
sdefresnee65fd872016-12-19 13:38:134074// Load a new URL on a new page/tab.
4075- (void)webPageOrderedOpen:(const GURL&)URL
4076 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134077 inBackground:(BOOL)inBackground
4078 appendTo:(OpenPosition)appendTo {
4079 Tab* adjacentTab = nil;
4080 if (appendTo == kCurrentTab)
4081 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354082 [_model insertTabWithURL:URL
4083 referrer:referrer
4084 transition:ui::PAGE_TRANSITION_LINK
4085 opener:adjacentTab
4086 openedByDOM:NO
4087 atIndex:TabModelConstants::kTabPositionAutomatically
4088 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134089}
4090
4091- (void)webPageOrderedOpen:(const GURL&)url
4092 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134093 inIncognito:(BOOL)inIncognito
4094 inBackground:(BOOL)inBackground
4095 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004096 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234097 // feature_engagement::Tracker based on |inIncognito|.
4098 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004099
sdefresnee65fd872016-12-19 13:38:134100 if (inIncognito == _isOffTheRecord) {
4101 [self webPageOrderedOpen:url
4102 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134103 inBackground:inBackground
4104 appendTo:appendTo];
4105 return;
4106 }
4107 // When sending an open command that switches modes, ensure the tab
4108 // ends up appended to the end of the model, not just next to what is
4109 // currently selected in the other mode. This is done with the |append|
4110 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274111 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134112 initWithURL:url
4113 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134114 inIncognito:inIncognito
4115 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274116 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154117 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134118}
4119
4120- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054121 WebStateList* webStateList = [_model webStateList];
4122 webStateList->ReplaceWebStateAt(
4123 webStateList->active_index(),
4124 session_util::CreateWebStateWithNavigationEntries(
4125 [_model browserState], sessionTab->current_navigation_index,
4126 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134127}
4128
4129- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:434130 DCHECK(javascript);
4131 javascript = [javascript stringByRemovingPercentEncoding];
4132 web::WebState* webState = [[_model currentTab] webState];
4133 if (webState) {
4134 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
4135 }
sdefresnee65fd872016-12-19 13:38:134136}
4137
4138#pragma mark - WebToolbarDelegate methods
4139
4140- (IBAction)locationBarDidBecomeFirstResponder:(id)sender {
4141 if (_locationBarHasFocus)
4142 return; // TODO(crbug.com/244366): This should not be necessary.
4143 _locationBarHasFocus = YES;
4144 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524145 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134146 object:nil];
4147 [_sideSwipeController setEnabled:NO];
4148 if ([[_model currentTab].webController wantsKeyboardShield]) {
4149 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
4150 [_typingShield setAlpha:0.0];
4151 [_typingShield setHidden:NO];
4152 [UIView animateWithDuration:0.3
4153 animations:^{
4154 [_typingShield setAlpha:1.0];
4155 }];
4156 }
4157 [[OmniboxGeolocationController sharedInstance]
4158 locationBarDidBecomeFirstResponder:_browserState];
4159}
4160
4161- (IBAction)locationBarDidResignFirstResponder:(id)sender {
4162 if (!_locationBarHasFocus)
4163 return; // TODO(crbug.com/244366): This should not be necessary.
4164 _locationBarHasFocus = NO;
4165 [_sideSwipeController setEnabled:YES];
4166 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524167 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134168 object:nil];
4169 [UIView animateWithDuration:0.3
4170 animations:^{
4171 [_typingShield setAlpha:0.0];
4172 }
4173 completion:^(BOOL finished) {
4174 // This can happen if one quickly resigns the omnibox and then taps
4175 // on the omnibox again during this animation. If the animation is
4176 // interrupted and the toolbar controller is first responder, it's safe
4177 // to assume the |_typingShield| shouldn't be hidden here.
sczsf1620e52017-10-02 22:54:464178 if (!finished && [_toolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134179 return;
4180 [_typingShield setHidden:YES];
4181 }];
4182 [[OmniboxGeolocationController sharedInstance]
4183 locationBarDidResignFirstResponder:_browserState];
4184
4185 // If a load was cancelled by an omnibox edit, but nothing is loading when
4186 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4187 if (_locationBarEditCancelledLoad) {
4188 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294189
4190 web::WebState* webState = [_model currentTab].webState;
4191 if (!_toolbarModelIOS->IsLoading() && webState)
4192 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4193 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134194 }
4195}
4196
4197- (IBAction)locationBarBeganEdit:(id)sender {
4198 // On handsets, if a page is currently loading it should be stopped.
4199 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354200 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134201 _locationBarEditCancelledLoad = YES;
4202 }
4203}
4204
sdefresnee65fd872016-12-19 13:38:134205- (ToolbarModelIOS*)toolbarModelIOS {
4206 return _toolbarModelIOS.get();
4207}
4208
sdefresnee65fd872016-12-19 13:38:134209- (void)willUpdateToolbarSnapshot {
4210 [[_model currentTab].overscrollActionsController clear];
4211}
4212
4213- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4214 CGRect frame = [_contentArea frame];
4215 if (!fullScreen) {
4216 // Changing the origin here is unnecessary, it's set in page_animation_util.
4217 frame.size.height -= [self headerHeight];
4218 }
4219
4220 CGFloat shortAxis = frame.size.width;
4221 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524222 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134223 CGFloat aspectRatio = frame.size.height / frame.size.width;
4224 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4225 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4226 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4227 CGRect cardFrame = {frame.origin, cardSize};
4228
4229 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174230 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134231 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4232 : CardCloseButtonSide::LEADING;
4233 [_contentArea addSubview:card];
4234 return card;
4235}
4236
Mark Cogan6ebbde02017-07-07 12:50:134237#pragma mark - BrowserCommands
4238
4239- (void)goBack {
4240 [[_model currentTab] goBack];
4241}
4242
4243- (void)goForward {
4244 [[_model currentTab] goForward];
4245}
4246
Mark Coganb9aac6432017-07-07 13:26:354247- (void)stopLoading {
4248 [_model currentTab].webState->Stop();
4249}
4250
4251- (void)reload {
4252 web::WebState* webState = [_model currentTab].webState;
4253 if (webState) {
4254 // |check_for_repost| is true because the reload is explicitly initiated
4255 // by the user.
4256 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4257 true /* check_for_repost */);
4258 }
4259}
4260
Mark Cogan8e791022017-07-10 09:55:354261- (void)bookmarkPage {
4262 [self initializeBookmarkInteractionController];
4263 [_bookmarkInteractionController
4264 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064265 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354266}
4267
Mark Cogan6acee7f2017-07-11 09:01:404268- (void)showToolsMenu {
4269 DCHECK(_browserState);
4270 DCHECK(self.visible || self.dismissingModal);
4271
4272 // Record the time this menu was requested; to be stored in the configuration
4273 // object.
4274 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
4275
4276 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:464277 [_toolbarCoordinator cancelOmniboxEdit];
Mark Cogan6acee7f2017-07-11 09:01:404278 // Dismiss the soft keyboard (if open).
4279 [[_model currentTab].webController dismissKeyboard];
4280 // Dismiss Find in Page focus.
4281 [self updateFindBar:NO shouldFocus:NO];
4282
4283 ToolsMenuConfiguration* configuration =
edchin8e4cfe032017-10-25 13:25:544284 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4285 baseViewController:self];
Mark Cogan6acee7f2017-07-11 09:01:404286 configuration.requestStartTime =
4287 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4288 if ([_model count] == 0)
4289 [configuration setNoOpenedTabs:YES];
4290
4291 if (_isOffTheRecord)
4292 [configuration setInIncognito:YES];
4293
4294 if (!_readingListMenuNotifier) {
4295 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4296 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4297 _browserState)];
4298 }
Cooper Knaake4f495cf2017-07-27 23:30:034299
4300 feature_engagement::Tracker* engagementTracker =
4301 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4302 if (engagementTracker->ShouldTriggerHelpUI(
4303 feature_engagement::kIPHBadgedReadingListFeature)) {
4304 [configuration setShowReadingListNewBadge:YES];
4305 [configuration setEngagementTracker:engagementTracker];
4306 }
Mark Cogan6acee7f2017-07-11 09:01:404307 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4308
4309 [configuration setUserAgentType:self.userAgentType];
4310
Helen Yang9175bd52017-08-12 00:28:404311 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4312 [configuration setHighlightNewIncognitoTabCell:YES];
4313 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4314 }
4315
4316 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4317 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4318 }
4319
sczsf1620e52017-10-02 22:54:464320 [_toolbarCoordinator showToolsMenuPopupWithConfiguration:configuration];
Mark Cogan6acee7f2017-07-11 09:01:404321
4322 ToolsPopupController* toolsPopupController =
sczsf1620e52017-10-02 22:54:464323 [_toolbarCoordinator toolsPopupController];
Mark Cogan6acee7f2017-07-11 09:01:404324 if ([_model currentTab]) {
4325 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4326 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4327 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
Mark Cogan6acee7f2017-07-11 09:01:404328 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4329
4330 if (!IsIPadIdiom())
4331 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4332 }
4333}
4334
Mark Cogandfcdea72017-07-18 13:47:384335- (void)openNewTab:(OpenNewTabCommand*)command {
4336 if (self.isOffTheRecord != command.incognito) {
4337 // Not for this browser state, send it on its way.
4338 [self.dispatcher switchModesAndOpenNewTab:command];
4339 return;
4340 }
4341
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004342 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234343 // events to the feature_engagement::Tracker based on |command.userInitiated|
4344 // and |command.incognito|.
4345 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004346
Mark Cogandfcdea72017-07-18 13:47:384347 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4348 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494349 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4350 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384351 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494352 if (oldForegroundTabWasAddedCompletionBlock) {
4353 oldForegroundTabWasAddedCompletionBlock();
4354 }
Mark Cogandfcdea72017-07-18 13:47:384355 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4356 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4357 if (offTheRecord) {
4358 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4359 timeDelta);
4360 } else {
4361 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4362 }
4363 };
4364
4365 [self setLastTapPoint:command];
Rohit Rao2e22b8d2017-11-07 19:54:544366 // When the tab switcher presentation experiment is enabled, the new tab can
4367 // be opened before BVC has been made visible onscreen. Test for this case by
4368 // checking if the parent container VC is currently in the process of being
4369 // presented.
4370 DCHECK(self.visible || self.dismissingModal ||
4371 (TabSwitcherPresentsBVCEnabled() &&
4372 self.parentViewController.isBeingPresented));
Mark Cogandfcdea72017-07-18 13:47:384373 Tab* currentTab = [_model currentTab];
4374 if (currentTab) {
4375 [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;
sczsf1620e52017-10-02 22:54:464649 [_toolbarCoordinator dismissToolsMenuPopup];
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
4798- (ToolbarController*)relinquishedToolbarController {
4799 if (_isToolbarControllerRelinquished)
4800 return nil;
4801
4802 ToolbarController* relinquishedToolbarController = nil;
sczs42f7f7482017-11-08 01:13:274803 if (_toolbarCoordinator.toolbarViewController.view.hidden) {
sdefresnee65fd872016-12-19 13:38:134804 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:194805 if (currentTab.webState &&
4806 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
sdefresnee65fd872016-12-19 13:38:134807 // Use the native content controller's toolbar when the BVC's is hidden.
4808 id nativeController = [self nativeControllerForTab:currentTab];
4809 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4810 relinquishedToolbarController =
4811 [nativeController relinquishedToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274812 _relinquishedToolbarOwner = nativeController;
sdefresnee65fd872016-12-19 13:38:134813 }
4814 }
4815 } else {
sczs42f7f7482017-11-08 01:13:274816 relinquishedToolbarController = _toolbarCoordinator.webToolbarController;
4817 [_toolbarCoordinator.toolbarViewController
4818 willMoveToParentViewController:nil];
4819 [_toolbarCoordinator.toolbarViewController.view removeFromSuperview];
4820 [_toolbarCoordinator.toolbarViewController removeFromParentViewController];
sdefresnee65fd872016-12-19 13:38:134821 }
4822 _isToolbarControllerRelinquished = (relinquishedToolbarController != nil);
4823 return relinquishedToolbarController;
4824}
4825
4826- (void)reparentToolbarController {
4827 if (_isToolbarControllerRelinquished) {
sczs42f7f7482017-11-08 01:13:274828 if ([_toolbarCoordinator.toolbarViewController.view
4829 isDescendantOfView:self.view]) {
sdefresnee65fd872016-12-19 13:38:134830 // A native content controller's toolbar has been relinquished.
4831 [_relinquishedToolbarOwner reparentToolbarController];
stkhapuginc9eee7b2017-04-10 15:49:274832 _relinquishedToolbarOwner = nil;
sdefresnee65fd872016-12-19 13:38:134833 } else if ([_findBarController isFindInPageShown]) {
sczs42f7f7482017-11-08 01:13:274834 [self.view insertSubview:_toolbarCoordinator.toolbarViewController.view
sdefresnee65fd872016-12-19 13:38:134835 belowSubview:[_findBarController view]];
Justin Cohenba27610e2017-11-08 19:34:454836 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:504837 [self addConstraintsToToolbar];
4838 }
sdefresnee65fd872016-12-19 13:38:134839 } else {
sczs42f7f7482017-11-08 01:13:274840 [self.view addSubview:_toolbarCoordinator.toolbarViewController.view];
Justin Cohenba27610e2017-11-08 19:34:454841 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:504842 [self addConstraintsToToolbar];
4843 }
sdefresnee65fd872016-12-19 13:38:134844 }
sdefresnee65fd872016-12-19 13:38:134845 _isToolbarControllerRelinquished = NO;
4846 }
4847}
4848
Gauthier Ambard04ddb512017-11-07 09:14:164849- (CGRect)toolbarFrame {
sczs42f7f7482017-11-08 01:13:274850 return _toolbarCoordinator.toolbarViewController.view.frame;
Gauthier Ambard04ddb512017-11-07 09:14:164851}
4852
Gauthier Ambard996d9b12017-11-06 09:39:214853- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
4854 id<ToolbarSnapshotProviding> toolbarSnapshotProvider = nil;
sczs42f7f7482017-11-08 01:13:274855 if (_toolbarCoordinator.toolbarViewController.view.hidden) {
Gauthier Ambard996d9b12017-11-06 09:39:214856 Tab* currentTab = [_model currentTab];
4857 if (currentTab.webState &&
4858 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
4859 // Use the native content controller's toolbar when the BVC's is hidden.
4860 id nativeController = [self nativeControllerForTab:currentTab];
4861 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4862 toolbarSnapshotProvider = [nativeController toolbarSnapshotProvider];
4863 }
4864 }
4865 } else {
4866 toolbarSnapshotProvider = _toolbarCoordinator;
4867 }
4868 return toolbarSnapshotProvider;
4869}
4870
sdefresnee65fd872016-12-19 13:38:134871#pragma mark - TabModelObserver methods
4872
4873// Observer method, tab inserted.
4874- (void)tabModel:(TabModel*)model
4875 didInsertTab:(Tab*)tab
4876 atIndex:(NSUInteger)modelIndex
4877 inForeground:(BOOL)fg {
4878 DCHECK(tab);
4879 [self installDelegatesForTab:tab];
4880
4881 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194882 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134883 }
4884}
4885
4886// Observer method, active tab changed.
4887- (void)tabModel:(TabModel*)model
4888 didChangeActiveTab:(Tab*)newTab
4889 previousTab:(Tab*)previousTab
4890 atIndex:(NSUInteger)index {
4891 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4892 // Currently this observer method is always called with a non-nil |newTab|,
4893 // but that may change in the future. Remove this DCHECK when it does.
4894 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274895 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304896 DCHECK(newTab.webState);
4897 infobars::InfoBarManager* infoBarManager =
4898 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134899 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4900 }
4901 [self updateVoiceSearchBarVisibilityAnimated:NO];
4902
Mohamad Ahmadi7d09ec32017-07-11 22:32:194903 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134904
4905 [self tabSelected:newTab];
sdefresnee65fd872016-12-19 13:38:134906}
4907
4908// Observer method, tab changed.
4909- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4910 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4911 if (tab == [_model currentTab]) {
4912 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134913 }
4914}
4915
sdefresne49cf2862017-03-15 13:46:144916// Observer method, tab replaced.
4917- (void)tabModel:(TabModel*)model
4918 didReplaceTab:(Tab*)oldTab
4919 withTab:(Tab*)newTab
4920 atIndex:(NSUInteger)index {
4921 [self uninstallDelegatesForTab:oldTab];
4922 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264923
michaeldo79909fb2017-05-09 23:42:504924 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304925 infobars::InfoBarManager* infoBarManager = nullptr;
4926 if (newTab) {
4927 DCHECK(newTab.webState);
4928 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4929 }
michaeldo79909fb2017-05-09 23:42:504930 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4931 }
4932
kkhorimotofa0844cc2017-03-20 17:01:264933 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4934 if (self.active && model.currentTab == newTab)
4935 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464936
4937 if (newTab)
4938 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144939}
4940
sdefresnee65fd872016-12-19 13:38:134941// A tab has been removed, remove its views from display if necessary.
4942- (void)tabModel:(TabModel*)model
4943 didRemoveTab:(Tab*)tab
4944 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144945 [self uninstallDelegatesForTab:tab];
4946
kkhorimoto496fdd72017-06-12 19:56:314947 // Cancel dialogs for |tab|'s WebState.
4948 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4949
sdefresnee65fd872016-12-19 13:38:134950 // Ignore changes while the tab stack view is visible (or while suspended).
4951 // The display will be refreshed when this view becomes active again.
4952 if (!self.visible || !model.webUsageEnabled)
4953 return;
4954
4955 // Remove the find bar for now.
4956 [self hideFindBarWithAnimation:NO];
4957}
4958
4959- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4960 if (tab == [model currentTab]) {
4961 [_contentArea displayContentView:nil];
sczsf1620e52017-10-02 22:54:464962 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:134963 }
4964
Mohamad Ahmadi7d09ec32017-07-11 22:32:194965 [_paymentRequestManager stopTrackingWebState:tab.webState];
4966
sdefresnee65fd872016-12-19 13:38:134967 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4968 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194969 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134970 }
4971}
4972
4973// Called when the number of tabs changes. Update the toolbar accordingly.
4974- (void)tabModelDidChangeTabCount:(TabModel*)model {
4975 DCHECK(model == _model);
sczsf1620e52017-10-02 22:54:464976 [_toolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134977}
4978
4979#pragma mark - Upgrade Detection
4980
4981- (void)showUpgrade:(UpgradeCenter*)center {
4982 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274983 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134984 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304985 DCHECK(tab.webState);
4986 infobars::InfoBarManager* infoBarManager =
4987 InfoBarManagerImpl::FromWebState(tab.webState);
4988 DCHECK(infoBarManager);
4989 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134990 }
4991}
4992
sdefresnee65fd872016-12-19 13:38:134993
4994#pragma mark - InfoBarControllerDelegate
4995
4996- (void)infoBarContainerStateChanged:(bool)isAnimating {
4997 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4998 DCHECK(infoBarContainerView);
4999 CGRect containerFrame = infoBarContainerView.frame;
5000 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
5001 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
5002 containerFrame.size.height = height;
5003 BOOL isViewVisible = self.visible;
5004 [UIView animateWithDuration:0.1
5005 animations:^{
5006 [infoBarContainerView setFrame:containerFrame];
5007 }
5008 completion:^(BOOL finished) {
5009 if (!isViewVisible)
5010 return;
5011 UIAccessibilityPostNotification(
5012 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
5013 }];
5014}
5015
5016- (BOOL)shouldAutorotate {
5017 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
5018 // Don't rotate if a voice search is being presented or dismissed. Once the
5019 // transition animations finish, only the Voice Search UIViewController's
5020 // |-shouldAutorotate| will be called.
5021 return NO;
5022 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
5023 // Don't auto rotate if side swipe controller view says not to.
5024 return NO;
5025 } else {
5026 return [super shouldAutorotate];
5027 }
5028}
5029
5030// Always return yes, as this tap should work with various recognizers,
5031// including UITextTapRecognizer, UILongPressGestureRecognizer,
5032// UIScrollViewPanGestureRecognizer and others.
5033- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
5034 shouldRecognizeSimultaneouslyWithGestureRecognizer:
5035 (UIGestureRecognizer*)otherGestureRecognizer {
5036 return YES;
5037}
5038
5039// Tap gestures should only be recognized within |_contentArea|.
5040- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
5041 CGPoint location = [gesture locationInView:self.view];
5042
5043 // Only allow touches on descendant views of |_contentArea|.
5044 UIView* hitView = [self.view hitTest:location withEvent:nil];
5045 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
5046}
5047
5048#pragma mark - SideSwipeController Delegate Methods
5049
5050- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
5051 DCHECK(!IsIPadIdiom());
5052 // Update frame incase orientation changed while |_contentArea| was out of
5053 // the view hierarchy.
5054 [_contentArea setFrame:[sideSwipeView frame]];
5055
Justin Cohen16ad60e2017-11-10 14:56:265056 [self.view insertSubview:_contentArea aboveSubview:_fakeStatusBarView];
sdefresnee65fd872016-12-19 13:38:135057 [self updateVoiceSearchBarVisibilityAnimated:NO];
5058 [self updateToolbar];
5059
5060 // Reset horizontal stack view.
5061 [sideSwipeView removeFromSuperview];
5062 [_sideSwipeController setInSwipe:NO];
5063 [_infoBarContainer->view() setHidden:NO];
5064}
5065
5066- (UIView*)contentView {
5067 return _contentArea;
5068}
5069
sdefresnee65fd872016-12-19 13:38:135070- (BOOL)preventSideSwipe {
sczsf1620e52017-10-02 22:54:465071 if ([_toolbarCoordinator toolsPopupController])
sdefresnee65fd872016-12-19 13:38:135072 return YES;
5073
5074 if (_voiceSearchController && _voiceSearchController->IsVisible())
5075 return YES;
5076
sdefresnee65fd872016-12-19 13:38:135077 if (!self.active)
5078 return YES;
5079
5080 return NO;
5081}
5082
5083- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5084 if (visible) {
5085 [self updateVoiceSearchBarVisibilityAnimated:NO];
5086 [self updateToolbar];
5087 [_infoBarContainer->view() setHidden:NO];
5088 } else {
5089 // Hide UI accessories such as find bar and first visit overlays
5090 // for welcome page.
5091 [self hideFindBarWithAnimation:NO];
5092 [_infoBarContainer->view() setHidden:YES];
5093 [_voiceSearchBar setHidden:YES];
5094 }
5095}
5096
5097- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5098 BOOL seenToolbar = NO;
5099 BOOL seenInfoBarContainer = NO;
5100 BOOL seenContentArea = NO;
5101 for (UIView* view in views.subviews) {
sczs42f7f7482017-11-08 01:13:275102 if (view == _toolbarCoordinator.toolbarViewController.view)
sdefresnee65fd872016-12-19 13:38:135103 seenToolbar = YES;
5104 else if (view == _infoBarContainer->view())
5105 seenInfoBarContainer = YES;
5106 else if (view == _contentArea)
5107 seenContentArea = YES;
5108 if ((seenToolbar && !seenInfoBarContainer) ||
5109 (seenInfoBarContainer && !seenContentArea))
5110 return NO;
5111 }
5112 return YES;
5113}
5114
5115#pragma mark - PreloadControllerDelegate methods
5116
rohitraoeeb5293b2017-06-15 14:40:025117- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445118 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135119}
5120
rohitraoeeb5293b2017-06-15 14:40:025121- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5122 return [self hasControllerForURL:url];
5123}
5124
sdefresnee65fd872016-12-19 13:38:135125#pragma mark - BookmarkBridgeMethods
5126
5127// If an added or removed bookmark is the same as the current url, update the
5128// toolbar so the star highlight is kept in sync.
5129- (void)bookmarkNodeModified:(const BookmarkNode*)node {
Sylvain Defresnee7f2c8a2017-10-17 02:39:195130 if ([_model currentTab].webState &&
5131 node->url() == [_model currentTab].webState->GetLastCommittedURL()) {
sdefresnee65fd872016-12-19 13:38:135132 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:255133 }
sdefresnee65fd872016-12-19 13:38:135134}
5135
5136// If all bookmarks are removed, update the toolbar so the star highlight is
5137// kept in sync.
5138- (void)allBookmarksRemoved {
5139 [self updateToolbar];
5140}
5141
sdefresnee65fd872016-12-19 13:38:135142- (void)showErrorAlertWithStringTitle:(NSString*)title
5143 message:(NSString*)message {
5144 // Dismiss current alert.
5145 [_alertCoordinator stop];
5146
stkhapuginc9eee7b2017-04-10 15:49:275147 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5148 message:message
5149 viewController:self];
sdefresnee65fd872016-12-19 13:38:135150 [_alertCoordinator start];
5151}
5152
edchineeb4d422017-10-02 17:39:365153- (void)showSnackbar:(NSString*)text {
5154 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
5155 message.accessibilityLabel = text;
5156 message.duration = 2.0;
5157 message.category = kBrowserViewControllerSnackbarCategory;
5158 [self.dispatcher showSnackbarMessage:message];
5159}
5160
sdefresnee65fd872016-12-19 13:38:135161#pragma mark - Show Mail Composer methods
5162
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575163- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5164 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135165 if (![MFMailComposeViewController canSendMail]) {
5166 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575167 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135168 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575169 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135170 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5171 return;
5172 }
stkhapuginc9eee7b2017-04-10 15:49:275173 MFMailComposeViewController* mailViewController =
5174 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135175 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575176 [mailViewController setToRecipients:[context toRecipients]];
5177 [mailViewController setSubject:[context subject]];
5178 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135179
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575180 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135181 if (!textFile.empty()) {
5182 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5183 NSData* data = [NSData dataWithContentsOfFile:filename];
5184 if (data) {
5185 NSString* displayName =
5186 base::SysUTF8ToNSString(textFile.BaseName().value());
5187 [mailViewController addAttachmentData:data
5188 mimeType:@"text/plain"
5189 fileName:displayName];
5190 }
5191 }
5192
5193 [mailViewController setMailComposeDelegate:self];
5194 [self presentViewController:mailViewController animated:YES completion:nil];
5195}
5196
5197#pragma mark - MFMailComposeViewControllerDelegate methods
5198
5199- (void)mailComposeController:(MFMailComposeViewController*)controller
5200 didFinishWithResult:(MFMailComposeResult)result
5201 error:(NSError*)error {
5202 [self dismissViewControllerAnimated:YES completion:nil];
5203}
5204
5205#pragma mark - StoreKitLauncher methods
5206
5207- (void)productViewControllerDidFinish:
5208 (SKStoreProductViewController*)viewController {
5209 [self dismissViewControllerAnimated:YES completion:nil];
5210}
5211
5212- (void)openAppStore:(NSString*)appId {
5213 if (![appId length])
5214 return;
5215 NSDictionary* product =
5216 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275217 SKStoreProductViewController* storeViewController =
5218 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135219 [storeViewController setDelegate:self];
5220 [storeViewController loadProductWithParameters:product completionBlock:nil];
5221 [self presentViewController:storeViewController animated:YES completion:nil];
5222}
5223
5224#pragma mark - TabDialogDelegate methods
5225
sdefresnee65fd872016-12-19 13:38:135226- (void)cancelDialogForTab:(Tab*)tab {
5227 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5228}
5229
5230#pragma mark - FKFeedbackPromptDelegate methods
5231
5232- (void)userTappedRateApp:(UIView*)view {
5233 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275234 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135235}
5236
5237- (void)userTappedSendFeedback:(UIView*)view {
5238 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275239 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205240 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135241}
5242
5243- (void)userTappedDismiss:(UIView*)view {
5244 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275245 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135246}
5247
5248#pragma mark - VoiceSearchBarDelegate
5249
5250- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275251 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135252 [self ensureVoiceSearchControllerCreated];
5253 return _voiceSearchController->IsTextToSpeechEnabled() &&
5254 _voiceSearchController->IsTextToSpeechSupported();
5255}
5256
5257- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275258 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135259 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5260}
5261
5262#pragma mark - VoiceSearchPresenter
5263
5264- (UIView*)voiceSearchButton {
5265 return _voiceSearchButton;
5266}
5267
5268- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5269 return [self currentLogoAnimationControllerOwner];
5270}
5271
Rohit Rao01e0e002017-08-14 20:49:435272#pragma mark - ActivityService Providers
5273
5274- (void)presentActivityServiceViewController:(UIViewController*)controller {
5275 [self presentViewController:controller animated:YES completion:nil];
5276}
5277
5278- (void)activityServiceDidEndPresenting {
5279 self.presenting = NO;
5280 [self.dialogPresenter tryToPresent];
5281}
5282
Rohit Raocda0a992017-08-16 15:37:115283#pragma mark - QRScanner Requirements
5284
5285- (void)presentQRScannerViewController:(UIViewController*)controller {
5286 [self presentViewController:controller animated:YES completion:nil];
5287}
5288
5289- (void)dismissQRScannerViewController:(UIViewController*)controller
5290 completion:(void (^)(void))completion {
5291 DCHECK_EQ(controller, self.presentedViewController);
5292 [self dismissViewControllerAnimated:YES completion:completion];
5293}
5294
sczsdd860eba2017-08-10 01:55:385295#pragma mark - TabHistoryPresenter
5296
sczs0a726d22017-08-21 22:40:135297- (UIView*)viewForTabHistoryPresentation {
5298 return self.view;
5299}
5300
sczsdd860eba2017-08-10 01:55:385301- (void)prepareForTabHistoryPresentation {
5302 DCHECK(self.visible || self.dismissingModal);
5303 [[self.tabModel currentTab].webController dismissKeyboard];
sczsf1620e52017-10-02 22:54:465304 [_toolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385305}
5306
Mike Doughertya1ec26402017-08-23 19:46:315307#pragma mark - CaptivePortalDetectorTabHelperDelegate
5308
Mike Dougherty4620cf8e2017-10-31 23:37:095309- (void)captivePortalDetectorTabHelper:
5310 (CaptivePortalDetectorTabHelper*)tabHelper
5311 connectWithLandingURL:(const GURL&)landingURL {
Mike Dougherty66e58812017-11-03 06:54:285312 [self addSelectedTabWithURL:landingURL transition:ui::PAGE_TRANSITION_TYPED];
Mike Doughertya1ec26402017-08-23 19:46:315313}
5314
Gregory Chatzinoffdf93d692017-09-09 01:32:275315#pragma mark - PageInfoPresentation
5316
Gregory Chatzinoffb6a01f72017-09-20 20:06:395317- (void)presentPageInfoView:(UIView*)pageInfoView {
5318 [pageInfoView setFrame:self.view.bounds];
5319 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275320}
5321
5322- (void)prepareForPageInfoPresentation {
5323 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:465324 [_toolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275325}
5326
Gregory Chatzinoffb6a01f72017-09-20 20:06:395327- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5328 return [self.view convertPoint:origin fromView:nil];
5329}
5330
Sylvain Defresnecacc3a52017-09-12 13:51:045331#pragma mark - WebStatePrinter
5332
5333- (void)printWebState:(web::WebState*)webState {
5334 if (webState == [_model currentTab].webState)
5335 [self printTab];
5336}
5337
Eugene But35ded552017-09-13 23:31:595338#pragma mark - RepostFormTabHelperDelegate
5339
5340- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
5341 presentRepostFromDialogAtPoint:(CGPoint)location
5342 completionHandler:(void (^)(BOOL))completion {
5343 _repostFormCoordinator = [[RepostFormCoordinator alloc]
5344 initWithBaseViewController:self
5345 dialogLocation:location
5346 webState:helper->web_state()
5347 completionHandler:completion];
5348 [_repostFormCoordinator start];
5349}
5350
5351- (void)repostFormTabHelperDismissRepostFormDialog:
5352 (RepostFormTabHelper*)helper {
5353 _repostFormCoordinator = nil;
5354}
5355
edchinf5150c682017-09-18 02:50:035356#pragma mark - TabStripPresentation
5357
5358- (BOOL)isTabStripFullyVisible {
5359 return ([self currentHeaderOffset] == 0.0f);
5360}
5361
5362- (void)showTabStripView:(UIView*)tabStripView {
5363 DCHECK([self isViewLoaded]);
5364 DCHECK(tabStripView);
5365 self.tabStripView = tabStripView;
5366 CGRect tabStripFrame = [self.tabStripView frame];
5367 tabStripFrame.origin = CGPointZero;
5368 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5369 // because the CGPointZero above will break reset the offset, but it's not
5370 // clear what removing that will do.
5371 tabStripFrame.origin.y = [self headerOffset];
5372 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5373 [self.tabStripView setFrame:tabStripFrame];
5374 [[self view] addSubview:tabStripView];
5375}
5376
edchincd32fdf2017-10-25 12:45:455377#pragma mark - ManageAccountsDelegate
5378
5379- (void)onManageAccounts {
5380 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5381 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5382 ->GetState());
edchin5b8aa052017-10-30 23:27:285383 [self.dispatcher showAccountsSettingsFromViewController:self];
edchincd32fdf2017-10-25 12:45:455384}
5385
5386- (void)onAddAccount {
5387 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5388 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5389 ->GetState());
edchinb082b2982017-11-03 19:41:215390 [self.dispatcher showAddAccountFromViewController:self];
edchincd32fdf2017-10-25 12:45:455391}
5392
5393- (void)onGoIncognito:(const GURL&)url {
5394 // The user taps on go incognito from the mobile U-turn webpage (the web page
5395 // that displays all users accounts available in the content area). As the
5396 // user chooses to go to incognito, the mobile U-turn page is no longer
5397 // neeeded. The current solution is to go back in history. This has the
5398 // advantage of keeping the current browsing session and give a good user
5399 // experience when the user comes back from incognito.
5400 [self.tabModel.currentTab goBack];
5401
5402 if (url.is_valid()) {
5403 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5404 initWithURL:url
5405 referrer:web::Referrer() // Strip referrer when switching modes.
5406 inIncognito:YES
5407 inBackground:NO
5408 appendTo:kLastTab];
5409 [self.dispatcher openURL:command];
5410 } else {
5411 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5412 }
5413}
5414
edchin95c927072017-11-04 00:35:075415#pragma mark - SyncPresenter
5416
5417- (void)showReauthenticateSignin {
5418 [self.dispatcher
edchin3b46e8d2017-11-07 22:48:125419 showSignin:
5420 [[ShowSigninCommand alloc]
5421 initWithOperation:AUTHENTICATION_OPERATION_REAUTHENTICATE
5422 accessPoint:signin_metrics::AccessPoint::
5423 ACCESS_POINT_UNKNOWN]
5424 baseViewController:self];
edchin95c927072017-11-04 00:35:075425}
5426
5427- (void)showSyncSettings {
edchina14d7182017-11-06 18:37:505428 [self.dispatcher showSyncSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075429}
5430
5431- (void)showSyncPassphraseSettings {
edchinec723062017-11-06 20:03:545432 [self.dispatcher showSyncPassphraseSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075433}
5434
edchin9e7a1112017-11-07 18:28:035435#pragma mark - SigninPresenter
5436
5437- (void)showSignin:(ShowSigninCommand*)command {
edchin3b46e8d2017-11-07 22:48:125438 [self.dispatcher showSignin:command baseViewController:self];
edchin9e7a1112017-11-07 18:28:035439}
5440
sdefresnee65fd872016-12-19 13:38:135441@end