blob: 6bdafcc2242d1544f2c39d57510042cbf5b343e7 [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"
Gauthier Ambard76d826ac2017-11-16 16:27:34174#include "ios/chrome/browser/ui/toolbar/legacy_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
sdefresnee65fd872016-12-19 13:38:13442 // Used to inject Javascript implementing the PaymentRequest API and to
443 // display the UI.
stkhapuginc9eee7b2017-04-10 15:49:27444 PaymentRequestManager* _paymentRequestManager;
sdefresnee65fd872016-12-19 13:38:13445
sdefresnee65fd872016-12-19 13:38:13446 // Used to display the Voice Search UI. Nil if not visible.
447 scoped_refptr<VoiceSearchController> _voiceSearchController;
448
gambard6299cc1d2017-02-21 13:06:03449 // Used to display the Reading List.
stkhapuginc9eee7b2017-04-10 15:49:27450 ReadingListCoordinator* _readingListCoordinator;
gambard6299cc1d2017-02-21 13:06:03451
sdefresnee65fd872016-12-19 13:38:13452 // Used to display the Find In Page UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27453 FindBarControllerIOS* _findBarController;
sdefresnee65fd872016-12-19 13:38:13454
sdefresnee65fd872016-12-19 13:38:13455 // Used to display the Print UI. Nil if not visible.
stkhapuginc9eee7b2017-04-10 15:49:27456 PrintController* _printController;
sdefresnee65fd872016-12-19 13:38:13457
458 // Records the set of domains for which full screen alert has already been
459 // shown.
stkhapuginc9eee7b2017-04-10 15:49:27460 NSMutableSet* _fullScreenAlertShown;
sdefresnee65fd872016-12-19 13:38:13461
462 // Adapter to let BVC be the delegate for WebState.
463 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
464
465 // YES if new tab is animating in.
466 BOOL _inNewTabAnimation;
467
468 // YES if Voice Search should be started when the new tab animation is
469 // finished.
470 BOOL _startVoiceSearchAfterNewTabAnimation;
471
472 // YES if the user interacts with the location bar.
473 BOOL _locationBarHasFocus;
474 // YES if a load was cancelled due to typing in the location bar.
475 BOOL _locationBarEditCancelledLoad;
476 // YES if waiting for a foreground tab due to expectNewForegroundTab.
477 BOOL _expectingForegroundTab;
478
Sylvain Defresne41170aa2017-06-15 10:25:20479 // Whether or not -shutdown has been called.
480 BOOL _isShutdown;
481
sdefresnee65fd872016-12-19 13:38:13482 // The ChromeBrowserState associated with this BVC.
483 ios::ChromeBrowserState* _browserState; // weak
484
485 // Whether or not Incognito* is enabled.
486 BOOL _isOffTheRecord;
487
488 // The last point within |_contentArea| that's received a touch.
489 CGPoint _lastTapPoint;
490
491 // The time at which |_lastTapPoint| was most recently set.
492 CFTimeInterval _lastTapTime;
493
494 // A single infobar container handles all infobars in all tabs. It keeps
495 // track of infobars for current tab (accessed via infobar helper of
496 // the current tab).
497 std::unique_ptr<InfoBarContainerIOS> _infoBarContainer;
498
499 // Bridge class to deliver container change notifications to BVC.
500 std::unique_ptr<InfoBarContainerDelegateIOS> _infoBarContainerDelegate;
501
502 // Voice search bar at the bottom of the view overlayed on |_contentArea|
kkhorimotoc2cdf6f42017-01-24 21:37:37503 // when displaying voice search results.
stkhapuginc9eee7b2017-04-10 15:49:27504 UIView<VoiceSearchBar>* _voiceSearchBar;
sdefresnee65fd872016-12-19 13:38:13505
506 // The image fetcher used to save images and perform image-based searches.
gambardbdc07cc2017-02-03 16:43:11507 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher;
sdefresnee65fd872016-12-19 13:38:13508
sdefresnee65fd872016-12-19 13:38:13509 // Dominant color cache. Key: (NSString*)url, val: (UIColor*)dominantColor.
stkhapuginc9eee7b2017-04-10 15:49:27510 NSMutableDictionary* _dominantColorCache;
sdefresnee65fd872016-12-19 13:38:13511
512 // Bridge to register for bookmark changes.
513 std::unique_ptr<BrowserBookmarkModelBridge> _bookmarkModelBridge;
514
515 // Cached pointer to the bookmarks model.
516 bookmarks::BookmarkModel* _bookmarkModel; // weak
517
518 // The controller that shows the bookmarking UI after the user taps the star
519 // button.
stkhapuginc9eee7b2017-04-10 15:49:27520 BookmarkInteractionController* _bookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13521
sdefresnee65fd872016-12-19 13:38:13522 // The currently displayed "Rate This App" dialog, if one exists.
stkhapuginc9eee7b2017-04-10 15:49:27523 id<AppRatingPrompt> _rateThisAppDialog;
sdefresnee65fd872016-12-19 13:38:13524
Eugene But56efc322017-08-11 14:03:44525 // Native controller vended to tab before Tab is added to the tab model.
Danyao Wangac242c72017-08-29 18:55:28526 __weak id _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:13527
528 // Notifies the toolbar menu of reading list changes.
stkhapuginc9eee7b2017-04-10 15:49:27529 ReadingListMenuNotifier* _readingListMenuNotifier;
sdefresnee65fd872016-12-19 13:38:13530
Jean-François Geyelin3d47c212017-08-03 09:24:09531 // The view used by the voice search presentation animation.
stkhapuginc9eee7b2017-04-10 15:49:27532 __weak UIView* _voiceSearchButton;
sdefresnee65fd872016-12-19 13:38:13533
Rohit Rao01e0e002017-08-14 20:49:43534 // Coordinator for the share menu (Activity Services).
535 ActivityServiceLegacyCoordinator* _activityServiceCoordinator;
536
sdefresnee65fd872016-12-19 13:38:13537 // Coordinator for displaying alerts.
stkhapuginc9eee7b2017-04-10 15:49:27538 AlertCoordinator* _alertCoordinator;
sczsdd860eba2017-08-10 01:55:38539
Rohit Raocda0a992017-08-16 15:37:11540 // Coordinator for the QR scanner.
541 QRScannerLegacyCoordinator* _qrScannerCoordinator;
542
sczsdd860eba2017-08-10 01:55:38543 // Coordinator for Tab History Popup.
sczs0a726d22017-08-21 22:40:13544 LegacyTabHistoryCoordinator* _tabHistoryCoordinator;
sczs6ae47ad2017-09-06 17:26:53545
546 // Coordinator for displaying Sad Tab.
547 SadTabLegacyCoordinator* _sadTabCoordinator;
Gregory Chatzinoffdf93d692017-09-09 01:32:27548
549 // Coordinator for Page Info UI.
550 PageInfoLegacyCoordinator* _pageInfoCoordinator;
Eugene But35ded552017-09-13 23:31:59551
552 // Coordinator for displaying Repost Form dialog.
553 RepostFormCoordinator* _repostFormCoordinator;
Justin Cohenb3170c32017-09-19 01:55:22554
edchin7f210cd2017-09-28 08:03:53555 // Coordinator for displaying snackbars.
556 SnackbarCoordinator* _snackbarCoordinator;
557
sczsf1620e52017-10-02 22:54:46558 // Coordinator for the toolbar.
559 LegacyToolbarCoordinator* _toolbarCoordinator;
560
Louis Romerod11747a2017-10-20 20:10:35561 // Coordinator for the External Search UI.
562 ExternalSearchCoordinator* _externalSearchCoordinator;
563
Justin Cohenb3170c32017-09-19 01:55:22564 // Fake status bar view used to blend the toolbar into the status bar.
565 UIView* _fakeStatusBarView;
Sylvain Defresnef5d2d952017-11-14 11:15:31566
567 // Stores whether the Tab currently inserted was a pre-rendered Tab. This
568 // is used to determine whether the pre-rendering animation should be played
569 // or not.
570 BOOL _insertedTabWasPrerenderedTab;
sdefresnee65fd872016-12-19 13:38:13571}
572
573// The browser's side swipe controller. Lazily instantiated on the first call.
stkhapuginf58b10d02017-04-10 13:36:17574@property(nonatomic, strong, readonly) SideSwipeController* sideSwipeController;
sdefresnee65fd872016-12-19 13:38:13575// The dialog presenter for this BVC's tab model.
stkhapuginf58b10d02017-04-10 13:36:17576@property(nonatomic, strong, readonly) DialogPresenter* dialogPresenter;
sdefresnee65fd872016-12-19 13:38:13577// The object that manages keyboard commands on behalf of the BVC.
stkhapuginf58b10d02017-04-10 13:36:17578@property(nonatomic, strong, readonly) KeyCommandsProvider* keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:13579// Whether the current tab can enable the request desktop menu item.
580@property(nonatomic, assign, readonly) BOOL canUseDesktopUserAgent;
581// Whether the sharing menu should be enabled.
582@property(nonatomic, assign, readonly) BOOL canShowShareMenu;
583// Helper method to check web controller canShowFindBar method.
584@property(nonatomic, assign, readonly) BOOL canShowFindBar;
585// Whether the controller's view is currently available.
586// YES from viewWillAppear to viewWillDisappear.
587@property(nonatomic, assign, getter=isVisible) BOOL visible;
588// Whether the controller's view is currently visible.
589// YES from viewDidAppear to viewWillDisappear.
590@property(nonatomic, assign) BOOL viewVisible;
591// Whether the controller is currently dismissing a presented view controller.
592@property(nonatomic, assign, getter=isDismissingModal) BOOL dismissingModal;
593// Returns YES if the toolbar has not been scrolled out by fullscreen.
594@property(nonatomic, assign, readonly, getter=isToolbarOnScreen)
595 BOOL toolbarOnScreen;
596// Whether a new tab animation is occurring.
kkhorimotoa44349c12017-04-12 23:02:12597@property(nonatomic, assign, getter=isInNewTabAnimation) BOOL inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:13598// Whether BVC prefers to hide the status bar. This value is used to determine
599// the response from the |prefersStatusBarHidden| method.
600@property(nonatomic, assign) BOOL hideStatusBar;
601// Whether the VoiceSearchBar should be displayed.
602@property(nonatomic, readonly) BOOL shouldShowVoiceSearchBar;
603// Coordinator for displaying a modal overlay with activity indicator to prevent
604// the user from interacting with the browser view.
stkhapuginf58b10d02017-04-10 13:36:17605@property(nonatomic, strong)
sdefresnee65fd872016-12-19 13:38:13606 ActivityOverlayCoordinator* activityOverlayCoordinator;
peterlaurens90ac0d32017-06-08 21:13:39607// A block to be run when the |tabWasAdded:| method completes the animation
608// for the presentation of a new tab. Can be used to record performance metrics.
609@property(nonatomic, strong, nullable)
610 ProceduralBlock foregroundTabWasAddedCompletionBlock;
Gauthier Ambardd4287fc2017-08-29 09:14:42611// Coordinator for Recent Tabs.
612@property(nonatomic, strong)
613 RecentTabsHandsetCoordinator* recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:03614// Coordinator for tablet tab strip.
615@property(nonatomic, strong) TabStripLegacyCoordinator* tabStripCoordinator;
616// A weak reference to the view of the tab strip on tablet.
617@property(nonatomic, weak) UIView* tabStripView;
sdefresnee65fd872016-12-19 13:38:13618
liaoyukeea9f3ee62017-03-07 22:05:39619// The user agent type used to load the currently visible page. User agent type
620// is NONE if there is no visible page or visible page is a native page.
621@property(nonatomic, assign, readonly) web::UserAgentType userAgentType;
622
stkhapugin952ecef2017-04-11 12:11:45623// Returns the header views, all the chrome on top of the page, including the
624// ones that cannot be scrolled off screen by full screen.
625@property(nonatomic, strong, readonly) NSArray<HeaderDefinition*>* headerViews;
626
Cooper Knaakd0a974cd2017-08-10 18:05:47627// Used to display the new tab tip in-product help promotion bubble. |nil| if
628// the new tab tip bubble has not yet been presented. Once the bubble is
629// dismissed, it remains allocated so that |userEngaged| remains accessible.
Cooper Knaak33f9f402017-08-09 18:04:38630@property(nonatomic, strong)
Cooper Knaakd0a974cd2017-08-10 18:05:47631 BubbleViewControllerPresenter* tabTipBubblePresenter;
Cooper Knaak33f9f402017-08-09 18:04:38632
Helen Yang9175bd52017-08-12 00:28:40633// Used to display the new incognito tab tip in-product help promotion bubble.
634@property(nonatomic, strong)
635 BubbleViewControllerPresenter* incognitoTabTipBubblePresenter;
636
sdefresnee65fd872016-12-19 13:38:13637// BVC initialization:
638// If the BVC is initialized with a valid browser state & tab model immediately,
639// the path is straightforward: functionality is enabled, and the UI is built
640// when -viewDidLoad is called.
641// If the BVC is initialized without a browser state or tab model, the tab model
642// and browser state may or may not be provided before -viewDidLoad is called.
643// In most cases, they will not, to improve startup performance.
644// In order to handle this, initialization of various aspects of BVC have been
645// broken out into the following functions, which have expectations (enforced
646// with DCHECKs) regarding |_browserState|, |_model|, and [self isViewLoaded].
647
648// Registers for notifications.
649- (void)registerForNotifications;
650// Called when a tab is starting to load. If it's a link click or form
651// submission, the user is navigating away from any entries in the forward
652// history. Tell the toolbar so it can update the UI appropriately.
653// See the warning on [Tab webWillStartLoadingURL] about invocation of this
654// method sequence by malicious pages.
655- (void)pageLoadStarting:(NSNotification*)notify;
656// Called when a tab actually starts loading.
657- (void)pageLoadStarted:(NSNotification*)notify;
658// Called when a tab finishes loading. Update the Omnibox with the url and
659// stop any page load progess display.
660- (void)pageLoadComplete:(NSNotification*)notify;
661// Called when a tab is deselected in the model.
662// This notification also occurs when a tab is closed.
663- (void)tabDeselected:(NSNotification*)notify;
664// Animates sliding current tab and rotate-entering new tab while new tab loads
665// in background on the iPhone only.
666- (void)tabWasAdded:(NSNotification*)notify;
667
668// Updates non-view-related functionality with the given browser state and tab
669// model.
670// Does not matter whether or not the view has been loaded.
671- (void)updateWithTabModel:(TabModel*)model
672 browserState:(ios::ChromeBrowserState*)browserState;
673// On iOS7, iPad should match iOS6 status bar. Install a simple black bar under
674// the status bar to mimic this layout.
675- (void)installFakeStatusBar;
676// Builds the UI parts of tab strip and the toolbar. Does not matter whether
677// or not browser state and tab model are valid.
678- (void)buildToolbarAndTabStrip;
Jean-François Geyelined4cde72017-10-11 11:34:50679// Sets up the constraints on the toolbar.
680- (void)addConstraintsToToolbar;
sdefresnee65fd872016-12-19 13:38:13681// Updates view-related functionality with the given tab model and browser
682// state. The view must have been loaded. Uses |_browserState| and |_model|.
683- (void)addUIFunctionalityForModelAndBrowserState;
Justin Cohen4eeada32017-11-13 18:21:28684// Sets the correct frame and hierarchy for subviews and helper views. Only
685// insert views on |initialLayout|.
686- (void)setUpViewLayout:(BOOL)initialLayout;
sdefresnee65fd872016-12-19 13:38:13687// Makes |tab| the currently visible tab, displaying its view. Calls
688// -selectedTabChanged on the toolbar only if |newSelection| is YES.
689- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection;
690// Initializes the bookmark interaction controller if not already initialized.
691- (void)initializeBookmarkInteractionController;
sdefresnee65fd872016-12-19 13:38:13692// Add all delegates to the provided |tab|.
693- (void)installDelegatesForTab:(Tab*)tab;
sdefresne49cf2862017-03-15 13:46:14694// Remove delegates from the provided |tab|.
695- (void)uninstallDelegatesForTab:(Tab*)tab;
sdefresnee65fd872016-12-19 13:38:13696// Closes the current tab, with animation if applicable.
697- (void)closeCurrentTab;
sdefresnee65fd872016-12-19 13:38:13698// Show the bookmarks page.
699- (void)showAllBookmarks;
700// Shows a panel within the New Tab Page.
Gauthier Ambardf520c022017-08-29 07:42:23701- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel;
sdefresnee65fd872016-12-19 13:38:13702// Dismisses the "rate this app" dialog.
703- (void)dismissRateThisAppDialog;
olivierrobin889af53f2017-03-01 14:56:32704// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:13705- (BOOL)isTabNativePage:(Tab*)tab;
706// Returns the view to use when animating a page in or out, positioning it to
707// fill the content area but not actually adding it to the view hierarchy.
708- (UIImageView*)pageOpenCloseAnimationView;
709// Returns the view to use when animating full screen NTP paper in, filling the
710// entire screen but not actually adding it to the view hierarchy.
711- (UIImageView*)pageFullScreenOpenCloseAnimationView;
712// Updates the toolbar display based on the current tab.
713- (void)updateToolbar;
714// Updates |dialogPresenter|'s |active| property to account for the BVC's
kkhorimotoa44349c12017-04-12 23:02:12715// |active|, |visible|, and |inNewTabAnimation| properties.
sdefresnee65fd872016-12-19 13:38:13716- (void)updateDialogPresenterActiveState;
717// Dismisses popups and modal dialogs that are displayed above the BVC upon size
718// changes (e.g. rotation, resizing,…) or when the accessibility escape gesture
719// is performed.
720// TODO(crbug.com/522721): Support size changes for all popups and modal
721// dialogs.
722- (void)dismissPopups;
Cooper Knaakd0a974cd2017-08-10 18:05:47723
724// Returns a bubble associated with an in-product help promotion if
725// it is valid to show the promotion and |nil| otherwise. |feature| is the
726// base::Feature object associated with the given promotion. |direction| is the
727// direction the bubble's arrow is pointing. |alignment| is the alignment of the
Gregory Chatzinoff541b8642017-10-25 00:25:21728// arrow on the button. |text| is the text displayed by the bubble. This method
729// requires that |self.browserState| is not NULL.
Cooper Knaakd0a974cd2017-08-10 18:05:47730- (BubbleViewControllerPresenter*)
731bubblePresenterForFeature:(const base::Feature&)feature
732 direction:(BubbleArrowDirection)direction
733 alignment:(BubbleAlignment)alignment
734 text:(NSString*)text;
735
Cooper Knaak120cee5e2017-08-10 20:57:00736// Waits to present a bubble associated with the new tab tip in-product help
737// promotion until the feature engagement tracker database is fully initialized.
738// Does not present the bubble if |tabTipBubblePresenter.userEngaged| is |YES|
739// to prevent resetting |tabTipBubblePresenter| and affecting the value of
Cooper Knaake963d6702017-08-11 21:03:11740// |userEngaged|. Does not present the bubble if the feature engagement tracker
Gregory Chatzinoff541b8642017-10-25 00:25:21741// determines it is not valid to present it. This method requires that
742// |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00743- (void)presentNewTabTipBubbleOnInitialized;
Cooper Knaake963d6702017-08-11 21:03:11744// Optionally presents a bubble associated with the new tab tip in-product help
745// promotion. If the feature engagement tracker determines it is valid to show
746// the new tab tip, then it initializes |tabTipBubblePresenter| and presents
747// the bubble. If it is not valid to show the new tab tip,
Gregory Chatzinoff541b8642017-10-25 00:25:21748// |tabTipBubblePresenter| is set to |nil| and no bubble is shown. This method
749// requires that |self.browserState| is not NULL.
Cooper Knaak120cee5e2017-08-10 20:57:00750- (void)presentNewTabTipBubble;
Helen Yang9175bd52017-08-12 00:28:40751// Waits to present a bubble associated with the new incognito tab tip
752// in-product help promotion until the feature engagement tracker database is
Gregory Chatzinoff541b8642017-10-25 00:25:21753// fully initialized. This method requires that |self.browserState| is
754// not NULL.
Helen Yang9175bd52017-08-12 00:28:40755- (void)presentNewIncognitoTabTipBubbleOnInitialized;
756// Presents a bubble associated with the new incognito tab tip in-product help
Gregory Chatzinoff541b8642017-10-25 00:25:21757// promotion. This method requires that |self.browserState| is not NULL.
Helen Yang9175bd52017-08-12 00:28:40758- (void)presentNewIncognitoTabTipBubble;
Gregory Chatzinoff541b8642017-10-25 00:25:21759// Presents the New Tab Tip or New Incognito Tab Tip Bubble if one is
760// eligible. Only one can be eligible per session (as enforced by the
761// FeatureEngagementTracker). If neither is eligible, neither bubble is
762// presented. This method requires that |self.browserState| is not NULL.
763- (void)presentBubblesIfEligible;
Cooper Knaak120cee5e2017-08-10 20:57:00764
sdefresnee65fd872016-12-19 13:38:13765// Update find bar with model data. If |shouldFocus| is set to YES, the text
766// field will become first responder.
767- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus;
sdefresnee65fd872016-12-19 13:38:13768// Hide find bar.
769- (void)hideFindBarWithAnimation:(BOOL)animate;
770// Shows find bar. If |selectText| is YES, all text inside the Find Bar
771// textfield will be selected. If |shouldFocus| is set to YES, the textfield is
772// set to be first responder.
773- (void)showFindBarWithAnimation:(BOOL)animate
774 selectText:(BOOL)selectText
775 shouldFocus:(BOOL)shouldFocus;
Gregory Chatzinoff7d1144c02017-08-31 15:00:36776
sdefresnee65fd872016-12-19 13:38:13777// The infobar state (typically height) has changed.
778- (void)infoBarContainerStateChanged:(bool)is_animating;
779// Adds a CardView on top of the contentArea either taking the size of the full
780// screen or just the size of the space under the header.
781// Returns the CardView that was added.
782- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen;
783// Called when either a tab finishes loading or when a tab with finished content
784// is added directly to the model via pre-rendering. The tab must be non-nil and
785// must be a member of the tab model controlled by this BrowserViewController.
786- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success;
787// Evaluates Javascript asynchronously using the current page context.
788- (void)openJavascript:(NSString*)javascript;
edchineeb4d422017-10-02 17:39:36789// Shows a self-dismissing snackbar displaying |message|.
790- (void)showSnackbar:(NSString*)message;
sdefresnee65fd872016-12-19 13:38:13791// Induces an intentional crash in the browser process.
792- (void)induceBrowserCrash;
793// Saves the image or display error message, based on privacy settings.
gambard9efce7a2017-02-09 18:53:17794- (void)managePermissionAndSaveImage:(NSData*)data
795 withFileExtension:(NSString*)fileExtension;
sdefresnee65fd872016-12-19 13:38:13796// Saves the image. In order to keep the metadata of the image, the image is
Sylvain Defresnefd3ecf22017-07-12 18:47:24797// saved as a temporary file on disk then saved in photos. Saving will happen
798// on a background sequence and the completion block will be invoked on that
799// sequence.
800- (void)saveImage:(NSData*)data
801 withFileExtension:(NSString*)fileExtension
802 completion:(void (^)(BOOL, NSError*))completionBlock;
sdefresnee65fd872016-12-19 13:38:13803// Called when Chrome has been denied access to the photos or videos and the
804// user can change it.
805// Shows a privacy alert on the main queue, allowing the user to go to Chrome's
806// settings. Dismiss previous alert if it has not been dismissed yet.
807- (void)displayImageErrorAlertWithSettingsOnMainQueue;
808// Shows a privacy alert allowing the user to go to Chrome's settings. Dismiss
809// previous alert if it has not been dismissed yet.
810- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL;
811// Called when Chrome has been denied access to the photos or videos and the
812// user cannot change it.
813// Shows a privacy alert on the main queue, with errorContent as the message.
814// Dismisses previous alert if it has not been dismissed yet.
815- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent;
816// Called with the results of saving a picture in the photo album. If error is
817// nil the save succeeded.
818- (void)finishSavingImageWithError:(NSError*)error;
819// Provides a view that encompasses currently displayed infobar(s) or nil
820// if no infobar is presented.
821- (UIView*)infoBarOverlayViewForTab:(Tab*)tab;
822// Returns a vertical infobar offset relative to the tab content.
823- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab;
824// Provides a view that encompasses the voice search bar if it's displayed or
825// nil if the voice search bar isn't displayed.
826- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab;
827// Returns a vertical voice search bar offset relative to the tab content.
828- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab;
829// Lazily instantiates |_voiceSearchController|.
830- (void)ensureVoiceSearchControllerCreated;
831// Lazily instantiates |_voiceSearchBar| and adds it to the view.
832- (void)ensureVoiceSearchBarCreated;
833// Shows/hides the voice search bar.
834- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated;
835// The LogoAnimationControllerOwner to be used for the next logo transition
836// animation.
837- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner;
sdefresnee65fd872016-12-19 13:38:13838// Returns the footer view if one exists (e.g. the voice search bar).
839- (UIView*)footerView;
840// Returns the height of the header view for the tab model's current tab.
841- (CGFloat)headerHeight;
sdefresnee65fd872016-12-19 13:38:13842// Sets the frame for the headers.
stkhapugin952ecef2017-04-11 12:11:45843- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:13844 atOffset:(CGFloat)headerOffset;
845// Returns the y coordinate for the footer's frame when animating the footer
846// in/out of fullscreen.
847- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset;
848// Called when the animation for setting the header view's offset is finished.
849// |completed| should indicate if the animation finished completely or was
850// interrupted. |offset| should indicate the header offset after the animation.
851// |dragged| should indicate if the header moved due to the user dragging.
Kurt Horimoto62e97c72017-11-03 19:51:47852- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:13853 headerAnimationCompleted:(BOOL)completed
854 offset:(CGFloat)offset;
855// Performs a search with the image at the given url. The referrer is used to
856// download the image.
857- (void)searchByImageAtURL:(const GURL&)url
858 referrer:(const web::Referrer)referrer;
859// Saves the image at the given URL on the system's album. The referrer is used
860// to download the image.
861- (void)saveImageAtURL:(const GURL&)url referrer:(const web::Referrer&)referrer;
862
Mark Cogandfcdea72017-07-18 13:47:38863// Record the last tap point based on the |originPoint| (if any) passed in
864// |command|.
865- (void)setLastTapPoint:(OpenNewTabCommand*)command;
sdefresnee65fd872016-12-19 13:38:13866// Get return the last stored |_lastTapPoint| if it's been set within the past
867// second.
868- (CGPoint)lastTapPoint;
869// Store the tap CGPoint in |_lastTapPoint| and the current timestamp.
870- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer;
871// Returns the native controller being used by |tab|'s web controller.
872- (id)nativeControllerForTab:(Tab*)tab;
873// Installs the BVC as overscroll actions controller of |nativeContent| if
874// needed. Sets the style of the overscroll actions toolbar.
875- (void)setOverScrollActionControllerToStaticNativeContent:
876 (StaticHtmlNativeContent*)nativeContent;
877// Whether the BVC should declare keyboard commands.
878- (BOOL)shouldRegisterKeyboardCommands;
879// Adds the given url to the reading list.
880- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title;
881@end
882
883class InfoBarContainerDelegateIOS
884 : public infobars::InfoBarContainer::Delegate {
885 public:
886 explicit InfoBarContainerDelegateIOS(BrowserViewController* controller)
887 : controller_(controller) {}
888
889 ~InfoBarContainerDelegateIOS() override {}
890
891 private:
892 SkColor GetInfoBarSeparatorColor() const override {
893 NOTIMPLEMENTED();
894 return SK_ColorBLACK;
895 }
896
897 int ArrowTargetHeightForInfoBar(
898 size_t index,
899 const gfx::SlideAnimation& animation) const override {
900 return 0;
901 }
902
903 void ComputeInfoBarElementSizes(const gfx::SlideAnimation& animation,
904 int arrow_target_height,
905 int bar_target_height,
906 int* arrow_height,
907 int* arrow_half_width,
908 int* bar_height) const override {
909 DCHECK_NE(-1, bar_target_height)
910 << "Infobars don't have a default height on iOS";
911 *arrow_height = 0;
912 *arrow_half_width = 0;
913 *bar_height = animation.CurrentValueBetween(0, bar_target_height);
914 }
915
916 void InfoBarContainerStateChanged(bool is_animating) override {
917 [controller_ infoBarContainerStateChanged:is_animating];
918 }
919
920 bool DrawInfoBarArrows(int* x) const override { return false; }
921
stkhapuginf58b10d02017-04-10 13:36:17922 __weak BrowserViewController* controller_;
sdefresnee65fd872016-12-19 13:38:13923};
924
925// Called from the BrowserBookmarkModelBridge from C++ -> ObjC.
926@interface BrowserViewController (BookmarkBridgeMethods)
927// If a bookmark matching the currentTab url is added or moved, update the
928// toolbar state so the star highlight is in sync.
929- (void)bookmarkNodeModified:(const BookmarkNode*)node;
930- (void)allBookmarksRemoved;
931@end
932
933// Handle notification that bookmarks has been removed changed so we can update
934// the bookmarked star icon.
935class BrowserBookmarkModelBridge : public bookmarks::BookmarkModelObserver {
936 public:
937 explicit BrowserBookmarkModelBridge(BrowserViewController* owner)
938 : owner_(owner) {}
939
940 ~BrowserBookmarkModelBridge() override {}
941
942 void BookmarkNodeRemoved(bookmarks::BookmarkModel* model,
943 const BookmarkNode* parent,
944 int old_index,
945 const BookmarkNode* node,
946 const std::set<GURL>& removed_urls) override {
947 [owner_ bookmarkNodeModified:node];
948 }
949
950 void BookmarkModelLoaded(bookmarks::BookmarkModel* model,
951 bool ids_reassigned) override {}
952
953 void BookmarkNodeMoved(bookmarks::BookmarkModel* model,
954 const BookmarkNode* old_parent,
955 int old_index,
956 const BookmarkNode* new_parent,
957 int new_index) override {}
958
959 void BookmarkNodeAdded(bookmarks::BookmarkModel* model,
960 const BookmarkNode* parent,
961 int index) override {
962 [owner_ bookmarkNodeModified:parent->GetChild(index)];
963 }
964
965 void BookmarkNodeChanged(bookmarks::BookmarkModel* model,
966 const BookmarkNode* node) override {}
967
968 void BookmarkNodeFaviconChanged(bookmarks::BookmarkModel* model,
969 const BookmarkNode* node) override {}
970
971 void BookmarkNodeChildrenReordered(bookmarks::BookmarkModel* model,
972 const BookmarkNode* node) override {}
973
974 void BookmarkAllUserNodesRemoved(
975 bookmarks::BookmarkModel* model,
976 const std::set<GURL>& removed_urls) override {
977 [owner_ allBookmarksRemoved];
978 }
979
980 private:
stkhapuginf58b10d02017-04-10 13:36:17981 __weak BrowserViewController* owner_;
sdefresnee65fd872016-12-19 13:38:13982};
983
984@implementation BrowserViewController
985
986@synthesize contentArea = _contentArea;
987@synthesize typingShield = _typingShield;
988@synthesize active = _active;
989@synthesize visible = _visible;
990@synthesize viewVisible = _viewVisible;
991@synthesize dismissingModal = _dismissingModal;
992@synthesize hideStatusBar = _hideStatusBar;
993@synthesize activityOverlayCoordinator = _activityOverlayCoordinator;
994@synthesize presenting = _presenting;
peterlaurens90ac0d32017-06-08 21:13:39995@synthesize foregroundTabWasAddedCompletionBlock =
996 _foregroundTabWasAddedCompletionBlock;
Helen Yang9175bd52017-08-12 00:28:40997@synthesize tabTipBubblePresenter = _tabTipBubblePresenter;
998@synthesize incognitoTabTipBubblePresenter = _incognitoTabTipBubblePresenter;
Gauthier Ambardd4287fc2017-08-29 09:14:42999@synthesize recentTabsCoordinator = _recentTabsCoordinator;
edchinf5150c682017-09-18 02:50:031000@synthesize tabStripCoordinator = _tabStripCoordinator;
1001@synthesize tabStripView = _tabStripView;
sdefresnee65fd872016-12-19 13:38:131002
1003#pragma mark - Object lifecycle
1004
Mark Cogan5e3da152017-07-11 15:57:301005- (instancetype)
1006 initWithTabModel:(TabModel*)model
1007 browserState:(ios::ChromeBrowserState*)browserState
1008 dependencyFactory:(BrowserViewControllerDependencyFactory*)factory
1009applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
sdefresnee65fd872016-12-19 13:38:131010 self = [super initWithNibName:nil bundle:base::mac::FrameworkBundle()];
1011 if (self) {
1012 DCHECK(factory);
stkhapuginf58b10d02017-04-10 13:36:171013
stkhapuginc9eee7b2017-04-10 15:49:271014 _dependencyFactory = factory;
stkhapuginc9eee7b2017-04-10 15:49:271015 _dialogPresenter = [[DialogPresenter alloc] initWithDelegate:self
1016 presentingViewController:self];
justincohen75011c32017-04-28 16:31:391017 _dispatcher = [[CommandDispatcher alloc] init];
1018 [_dispatcher startDispatchingToTarget:self
1019 forProtocol:@protocol(UrlLoader)];
1020 [_dispatcher startDispatchingToTarget:self
1021 forProtocol:@protocol(WebToolbarDelegate)];
1022 [_dispatcher startDispatchingToTarget:self
Mark Cogan6c58ea92017-07-06 13:08:241023 forProtocol:@protocol(BrowserCommands)];
Mark Cogan5e3da152017-07-11 15:57:301024 [_dispatcher startDispatchingToTarget:applicationCommandEndpoint
1025 forProtocol:@protocol(ApplicationCommands)];
Mark Cogan83da264b12017-07-19 12:21:321026 // -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
1027 // passed protocol conforms to, so ApplicationSettingsCommands is explicitly
1028 // dispatched to the endpoint as well. Since this is potentially
1029 // fragile, DCHECK that it should still work (if the endpoint is nonnull).
1030 DCHECK(!applicationCommandEndpoint ||
1031 [applicationCommandEndpoint
1032 conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
1033 [_dispatcher
1034 startDispatchingToTarget:applicationCommandEndpoint
1035 forProtocol:@protocol(ApplicationSettingsCommands)];
justincohen75011c32017-04-28 16:31:391036
edchin7f210cd2017-09-28 08:03:531037 _snackbarCoordinator = [[SnackbarCoordinator alloc] init];
1038 _snackbarCoordinator.dispatcher = _dispatcher;
1039 [_snackbarCoordinator start];
1040
sdefresnee65fd872016-12-19 13:38:131041 _javaScriptDialogPresenter.reset(
1042 new JavaScriptDialogPresenterImpl(_dialogPresenter));
1043 _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
1044 // TODO(leng): Delay this.
sczs02ad28e2017-08-31 11:22:151045 [[UpgradeCenter sharedInstance] registerClient:self
1046 withDispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:131047 _inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131048 if (model && browserState)
1049 [self updateWithTabModel:model browserState:browserState];
1050 if ([[NSUserDefaults standardUserDefaults]
1051 boolForKey:@"fullScreenShowAlert"]) {
stkhapuginc9eee7b2017-04-10 15:49:271052 _fullScreenAlertShown = [[NSMutableSet alloc] init];
sdefresnee65fd872016-12-19 13:38:131053 }
1054 }
1055 return self;
1056}
1057
1058- (instancetype)initWithNibName:(NSString*)nibNameOrNil
1059 bundle:(NSBundle*)nibBundleOrNil {
1060 NOTREACHED();
1061 return nil;
1062}
1063
1064- (instancetype)initWithCoder:(NSCoder*)aDecoder {
1065 NOTREACHED();
1066 return nil;
1067}
1068
1069- (void)dealloc {
Sylvain Defresne41170aa2017-06-15 10:25:201070 DCHECK(_isShutdown) << "-shutdown must be called before dealloc.";
sdefresnee65fd872016-12-19 13:38:131071}
1072
1073#pragma mark - Accessibility
1074
1075- (BOOL)accessibilityPerformEscape {
1076 [self dismissPopups];
1077 return YES;
1078}
1079
1080#pragma mark - Properties
1081
edchin3365c7d2017-09-01 22:20:371082- (id<ApplicationCommands,
1083 BrowserCommands,
edchin3365c7d2017-09-01 22:20:371084 OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531085 SnackbarCommands,
edchin3365c7d2017-09-01 22:20:371086 UrlLoader,
1087 WebToolbarDelegate>)dispatcher {
Mark Cogan4c901302017-09-05 14:47:561088 return static_cast<id<ApplicationCommands, BrowserCommands, OmniboxFocuser,
edchin7f210cd2017-09-28 08:03:531089 SnackbarCommands, UrlLoader, WebToolbarDelegate>>(
1090 _dispatcher);
Mark Cogan6c58ea92017-07-06 13:08:241091}
1092
sdefresnee65fd872016-12-19 13:38:131093- (void)setActive:(BOOL)active {
1094 if (_active == active) {
1095 return;
1096 }
1097 _active = active;
1098
1099 // If not active, display an activity indicator overlay over the view to
1100 // prevent interaction with the web page.
1101 // TODO(crbug.com/637093): This coordinator should be managed by the
1102 // coordinator used to present BrowserViewController, when implemented.
1103 if (active) {
1104 [self.activityOverlayCoordinator stop];
1105 self.activityOverlayCoordinator = nil;
1106 } else if (!self.activityOverlayCoordinator) {
stkhapuginf58b10d02017-04-10 13:36:171107 self.activityOverlayCoordinator =
1108 [[ActivityOverlayCoordinator alloc] initWithBaseViewController:self];
sdefresnee65fd872016-12-19 13:38:131109 [self.activityOverlayCoordinator start];
1110 }
1111
1112 if (_browserState) {
Eugene Butc90499d52017-09-22 16:02:091113 ActiveStateManager* active_state_manager =
1114 ActiveStateManager::FromBrowserState(_browserState);
sdefresnee65fd872016-12-19 13:38:131115 active_state_manager->SetActive(active);
1116 }
1117
1118 [_model setWebUsageEnabled:active];
1119 [self updateDialogPresenterActiveState];
1120
1121 if (active) {
1122 // Make sure the tab (if any; it's possible to get here without a current
1123 // tab if the caller is about to create one) ends up on screen completely.
1124 Tab* currentTab = [_model currentTab];
1125 // Force loading the view in case it was not loaded yet.
Mark Cogan059ce7c2017-07-18 10:40:441126 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:131127 if (_expectingForegroundTab)
1128 [currentTab.webController setOverlayPreviewMode:YES];
1129 if (currentTab)
1130 [self displayTab:currentTab isNewSelection:YES];
eugenebutf8a138e62017-01-24 22:41:341131 } else {
1132 [_dialogPresenter cancelAllDialogs];
sdefresnee65fd872016-12-19 13:38:131133 }
sdefresnee65fd872016-12-19 13:38:131134 [_paymentRequestManager enablePaymentRequest:active];
1135
1136 [self setNeedsStatusBarAppearanceUpdate];
1137}
1138
1139- (void)setPrimary:(BOOL)primary {
1140 [_model setPrimary:primary];
1141 if (primary) {
1142 [self updateDialogPresenterActiveState];
1143 } else {
1144 self.dialogPresenter.active = false;
1145 }
1146}
1147
1148- (BOOL)isPlayingTTS {
1149 return _voiceSearchController && _voiceSearchController->IsPlayingAudio();
1150}
1151
sdefresne6165c8742017-01-16 15:42:021152- (ios::ChromeBrowserState*)browserState {
1153 return _browserState;
1154}
1155
1156- (TabModel*)tabModel {
stkhapuginc9eee7b2017-04-10 15:49:271157 return _model;
sdefresne6165c8742017-01-16 15:42:021158}
1159
sdefresnee65fd872016-12-19 13:38:131160- (SideSwipeController*)sideSwipeController {
1161 if (!_sideSwipeController) {
stkhapuginc9eee7b2017-04-10 15:49:271162 _sideSwipeController =
1163 [[SideSwipeController alloc] initWithTabModel:_model
1164 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:131165 [_sideSwipeController setSnapshotDelegate:self];
Gauthier Ambard29939db12017-10-30 16:47:311166 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sdefresnee65fd872016-12-19 13:38:131167 [_sideSwipeController setSwipeDelegate:self];
edchinf5150c682017-09-18 02:50:031168 [_sideSwipeController setTabStripDelegate:self.tabStripCoordinator];
sdefresnee65fd872016-12-19 13:38:131169 }
1170 return _sideSwipeController;
1171}
1172
sdefresnee65fd872016-12-19 13:38:131173- (DialogPresenter*)dialogPresenter {
1174 return _dialogPresenter;
1175}
1176
sdefresnee65fd872016-12-19 13:38:131177- (BOOL)canUseDesktopUserAgent {
1178 Tab* tab = [_model currentTab];
1179 if ([self isTabNativePage:tab])
1180 return NO;
1181
1182 // If |useDesktopUserAgent| is |NO|, allow useDesktopUserAgent.
liaoyukeb8453e12017-02-24 22:08:441183 return !tab.usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:131184}
1185
1186// Whether the sharing menu should be shown.
1187- (BOOL)canShowShareMenu {
Sylvain Defresnee7f2c8a2017-10-17 02:39:191188 const GURL& URL = [_model currentTab].webState->GetLastCommittedURL();
kkhorimotob110b262017-06-01 18:38:251189 return URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL);
sdefresnee65fd872016-12-19 13:38:131190}
1191
1192- (BOOL)canShowFindBar {
1193 // Make sure web controller can handle find in page.
1194 Tab* tab = [_model currentTab];
rohitrao005a6432017-03-16 20:52:421195 if (!tab) {
sdefresnee65fd872016-12-19 13:38:131196 return NO;
rohitrao005a6432017-03-16 20:52:421197 }
sdefresnee65fd872016-12-19 13:38:131198
rohitrao005a6432017-03-16 20:52:421199 auto* helper = FindTabHelper::FromWebState(tab.webState);
1200 return (helper && helper->CurrentPageSupportsFindInPage() &&
1201 !helper->IsFindUIActive());
sdefresnee65fd872016-12-19 13:38:131202}
1203
liaoyukeea9f3ee62017-03-07 22:05:391204- (web::UserAgentType)userAgentType {
1205 web::WebState* webState = [_model currentTab].webState;
1206 if (!webState)
1207 return web::UserAgentType::NONE;
1208 web::NavigationItem* visibleItem =
1209 webState->GetNavigationManager()->GetVisibleItem();
1210 if (!visibleItem)
1211 return web::UserAgentType::NONE;
1212
1213 return visibleItem->GetUserAgentType();
1214}
1215
sdefresnee65fd872016-12-19 13:38:131216- (void)setVisible:(BOOL)visible {
1217 if (_visible == visible)
1218 return;
1219 _visible = visible;
1220}
1221
1222- (void)setViewVisible:(BOOL)viewVisible {
1223 if (_viewVisible == viewVisible)
1224 return;
1225 _viewVisible = viewVisible;
1226 self.visible = viewVisible;
1227 [self updateDialogPresenterActiveState];
1228}
1229
1230- (BOOL)isToolbarOnScreen {
1231 return [self headerHeight] - [self currentHeaderOffset] > 0;
1232}
1233
kkhorimotoa44349c12017-04-12 23:02:121234- (void)setInNewTabAnimation:(BOOL)inNewTabAnimation {
1235 if (_inNewTabAnimation == inNewTabAnimation)
1236 return;
1237 _inNewTabAnimation = inNewTabAnimation;
1238 [self updateDialogPresenterActiveState];
1239}
1240
sdefresnee65fd872016-12-19 13:38:131241- (BOOL)isInNewTabAnimation {
1242 return _inNewTabAnimation;
1243}
1244
1245- (BOOL)shouldShowVoiceSearchBar {
1246 // On iPads, the voice search bar should only be shown for regular horizontal
1247 // size class configurations. It should always be shown for voice search
1248 // results Tabs on iPhones, including configurations with regular horizontal
1249 // size classes (i.e. landscape iPhone 6 Plus).
1250 BOOL compactWidth = self.traitCollection.horizontalSizeClass ==
1251 UIUserInterfaceSizeClassCompact;
1252 return self.tabModel.currentTab.isVoiceSearchResultsTab &&
1253 (!IsIPadIdiom() || compactWidth);
1254}
1255
1256- (void)setHideStatusBar:(BOOL)hideStatusBar {
1257 if (_hideStatusBar == hideStatusBar)
1258 return;
1259 _hideStatusBar = hideStatusBar;
1260 [self setNeedsStatusBarAppearanceUpdate];
1261}
1262
1263#pragma mark - IBActions
1264
1265- (void)shieldWasTapped:(id)sender {
sczsf1620e52017-10-02 22:54:461266 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:131267}
1268
Cooper Knaakd0a974cd2017-08-10 18:05:471269- (void)userEnteredTabSwitcher {
1270 if ([self.tabTipBubblePresenter isUserEngaged]) {
1271 base::RecordAction(UserMetricsAction("NewTabTipTargetSelected"));
1272 }
1273}
1274
Cooper Knaake963d6702017-08-11 21:03:111275- (void)presentBubblesIfEligible {
1276 [self presentNewTabTipBubbleOnInitialized];
Helen Yang9175bd52017-08-12 00:28:401277 [self presentNewIncognitoTabTipBubble];
Cooper Knaake963d6702017-08-11 21:03:111278}
1279
sdefresnee65fd872016-12-19 13:38:131280#pragma mark - UIViewController methods
1281
1282// Perform additional set up after loading the view, typically from a nib.
1283- (void)viewDidLoad {
Justin Cohen13b7c4322017-09-15 12:40:091284 CGRect initialViewsRect = self.view.bounds;
jif50d5ba252016-12-20 14:00:281285 initialViewsRect.origin.y += StatusBarHeight();
1286 initialViewsRect.size.height -= StatusBarHeight();
sdefresnee65fd872016-12-19 13:38:131287 UIViewAutoresizing initialViewAutoresizing =
1288 UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
1289
stkhapuginf58b10d02017-04-10 13:36:171290 self.contentArea =
1291 [[BrowserContainerView alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131292 self.contentArea.autoresizingMask = initialViewAutoresizing;
stkhapuginf58b10d02017-04-10 13:36:171293 self.typingShield = [[UIButton alloc] initWithFrame:initialViewsRect];
sdefresnee65fd872016-12-19 13:38:131294 self.typingShield.autoresizingMask = initialViewAutoresizing;
1295 [self.typingShield addTarget:self
1296 action:@selector(shieldWasTapped:)
1297 forControlEvents:UIControlEventTouchUpInside];
sdefresnee65fd872016-12-19 13:38:131298 self.view.autoresizingMask = initialViewAutoresizing;
1299 self.view.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1.0];
1300 [self.view addSubview:self.contentArea];
1301 [self.view addSubview:self.typingShield];
1302 [super viewDidLoad];
1303
1304 // Install fake status bar for iPad iOS7
1305 [self installFakeStatusBar];
1306 [self buildToolbarAndTabStrip];
Justin Cohen4eeada32017-11-13 18:21:281307 [self setUpViewLayout:YES];
Justin Cohenba27610e2017-11-08 19:34:451308 if (IsSafeAreaCompatibleToolbarEnabled()) {
Jean-François Geyelined4cde72017-10-11 11:34:501309 [self addConstraintsToToolbar];
1310 }
sdefresnee65fd872016-12-19 13:38:131311 // If the tab model and browser state are valid, finish initialization.
1312 if (_model && _browserState)
1313 [self addUIFunctionalityForModelAndBrowserState];
1314
1315 // Add a tap gesture recognizer to save the last tap location for the source
1316 // location of the new tab animation.
stkhapuginc9eee7b2017-04-10 15:49:271317 UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
1318 initWithTarget:self
1319 action:@selector(saveContentAreaTapLocation:)];
sdefresnee65fd872016-12-19 13:38:131320 [tapRecognizer setDelegate:self];
1321 [tapRecognizer setCancelsTouchesInView:NO];
1322 [_contentArea addGestureRecognizer:tapRecognizer];
1323}
1324
Justin Cohenb3170c32017-09-19 01:55:221325- (void)viewSafeAreaInsetsDidChange {
1326 [super viewSafeAreaInsetsDidChange];
1327 // Gate this behind iPhone X, since it's currently the only device that
1328 // needs layout updates here after startup.
Jean-François Geyelined4cde72017-10-11 11:34:501329 if (IsIPhoneX()) {
Justin Cohen4eeada32017-11-13 18:21:281330 [self setUpViewLayout:NO];
Jean-François Geyelined4cde72017-10-11 11:34:501331 }
Justin Cohenba27610e2017-11-08 19:34:451332 if (IsSafeAreaCompatibleToolbarEnabled()) {
Gauthier Ambard100670f72017-10-27 09:54:271333 // TODO(crbug.com/778236): Check if this call can be removed once the
1334 // Toolbar is a contained ViewController.
sczs42f7f7482017-11-08 01:13:271335 [_toolbarCoordinator.toolbarViewController viewSafeAreaInsetsDidChange];
Gauthier Ambard100670f72017-10-27 09:54:271336 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501337 }
Justin Cohenb3170c32017-09-19 01:55:221338}
1339
sdefresnee65fd872016-12-19 13:38:131340- (void)viewDidAppear:(BOOL)animated {
1341 [super viewDidAppear:animated];
1342 self.viewVisible = YES;
1343 [self updateDialogPresenterActiveState];
Gregory Chatzinoff541b8642017-10-25 00:25:211344
1345 // |viewDidAppear| can be called after |browserState| is destroyed. Since
1346 // |presentBubblesIfEligible| requires that |self.browserState| is not NULL,
1347 // check for |self.browserState| before calling the presenting the bubbles.
1348 if (self.browserState) {
1349 [self presentBubblesIfEligible];
1350 }
sdefresnee65fd872016-12-19 13:38:131351}
1352
1353- (void)viewWillAppear:(BOOL)animated {
1354 [super viewWillAppear:animated];
1355
sdefresnee65fd872016-12-19 13:38:131356 self.visible = YES;
1357
1358 // Restore hidden infobars.
Rohit Rao755c37b2017-11-10 14:05:521359 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131360 _infoBarContainer->RestoreInfobars();
1361 }
1362
1363 // If the controller is suspended, or has been paged out due to low memory,
1364 // updating the view will be handled when it's displayed again.
1365 if (![_model webUsageEnabled] || !self.contentArea)
1366 return;
1367 // Update the displayed tab (if any; the switcher may not have created one
1368 // yet) in case it changed while showing the switcher.
1369 Tab* currentTab = [_model currentTab];
1370 if (currentTab)
1371 [self displayTab:currentTab isNewSelection:YES];
1372}
1373
1374- (void)viewWillDisappear:(BOOL)animated {
1375 self.viewVisible = NO;
1376 [self updateDialogPresenterActiveState];
sdefresnee65fd872016-12-19 13:38:131377 [[_model currentTab] wasHidden];
1378 [_bookmarkInteractionController dismissSnackbar];
Rohit Rao755c37b2017-11-10 14:05:521379 if (IsIPadIdiom() && _infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:131380 _infoBarContainer->SuspendInfobars();
1381 }
1382 [super viewWillDisappear:animated];
1383}
1384
1385- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orient
1386 duration:(NSTimeInterval)duration {
1387 [super willRotateToInterfaceOrientation:orient duration:duration];
1388 [self dismissPopups];
1389 [self reshowFindBarIfNeededWithCoordinator:nil];
1390}
1391
1392- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orient {
1393 [super didRotateFromInterfaceOrientation:orient];
1394
1395 // This reinitializes the toolbar, including updating the Overlay View,
1396 // if there is one.
1397 [self updateToolbar];
1398 [self infoBarContainerStateChanged:false];
1399}
1400
1401- (BOOL)prefersStatusBarHidden {
1402 return self.hideStatusBar;
1403}
1404
1405// Called when in the foreground and the OS needs more memory. Release as much
1406// as possible.
1407- (void)didReceiveMemoryWarning {
1408 // Releases the view if it doesn't have a superview.
1409 [super didReceiveMemoryWarning];
1410
1411 // Release any cached data, images, etc that aren't in use.
1412 // TODO(pinkerton): This feels like it should go in the MemoryPurger class,
1413 // but since the FaviconCache uses obj-c in the header, it can't be included
1414 // there.
1415 if (_browserState) {
1416 FaviconLoader* loader =
1417 IOSChromeFaviconLoaderFactory::GetForBrowserStateIfExists(
1418 _browserState);
1419 if (loader)
1420 loader->PurgeCache();
1421 }
1422
1423 if (![self isViewLoaded]) {
1424 // Do not release |_infoBarContainer|, as this must have the same lifecycle
1425 // as the BrowserViewController.
1426 self.contentArea = nil;
1427 self.typingShield = nil;
stkhapuginc9eee7b2017-04-10 15:49:271428 if (_voiceSearchController)
sdefresnee65fd872016-12-19 13:38:131429 _voiceSearchController->SetDelegate(nil);
stkhapuginc9eee7b2017-04-10 15:49:271430 _readingListCoordinator = nil;
Gauthier Ambardd4287fc2017-08-29 09:14:421431 self.recentTabsCoordinator = nil;
sczsf1620e52017-10-02 22:54:461432 _toolbarCoordinator = nil;
stkhapuginc9eee7b2017-04-10 15:49:271433 _toolbarModelDelegate = nil;
1434 _toolbarModelIOS = nil;
edchinf5150c682017-09-18 02:50:031435 [self.tabStripCoordinator stop];
1436 self.tabStripCoordinator = nil;
1437 self.tabStripView = nil;
stkhapuginc9eee7b2017-04-10 15:49:271438 _sideSwipeController = nil;
sdefresnee65fd872016-12-19 13:38:131439 }
1440}
1441
1442- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
1443 [super traitCollectionDidChange:previousTraitCollection];
1444 // TODO(crbug.com/527092): - traitCollectionDidChange: is not always forwarded
1445 // because in some cases the presented view controller isn't a child of the
1446 // BVC in the view controller hierarchy (some intervening object isn't a
1447 // view controller).
1448 [self.presentedViewController
1449 traitCollectionDidChange:previousTraitCollection];
sdefresnee65fd872016-12-19 13:38:131450 // Update voice search bar visibility.
1451 [self updateVoiceSearchBarVisibilityAnimated:NO];
1452}
1453
1454- (void)viewWillTransitionToSize:(CGSize)size
1455 withTransitionCoordinator:
1456 (id<UIViewControllerTransitionCoordinator>)coordinator {
1457 [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
1458 [self dismissPopups];
1459 [self reshowFindBarIfNeededWithCoordinator:coordinator];
1460}
1461
1462- (void)reshowFindBarIfNeededWithCoordinator:
1463 (id<UIViewControllerTransitionCoordinator>)coordinator {
1464 if (![_findBarController isFindInPageShown])
1465 return;
1466
1467 // Record focused state.
1468 BOOL isFocusedBeforeReshow = [_findBarController isFocused];
1469
1470 [self hideFindBarWithAnimation:NO];
1471
stkhapuginc9eee7b2017-04-10 15:49:271472 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131473 void (^completion)(id<UIViewControllerTransitionCoordinatorContext>) = ^(
1474 id<UIViewControllerTransitionCoordinatorContext> context) {
stkhapuginc9eee7b2017-04-10 15:49:271475 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131476 if (strongSelf)
1477 [strongSelf showFindBarWithAnimation:NO
1478 selectText:NO
1479 shouldFocus:isFocusedBeforeReshow];
1480 };
1481
1482 BOOL enqueued =
1483 [coordinator animateAlongsideTransition:nil completion:completion];
1484 if (!enqueued) {
1485 completion(nil);
1486 }
1487}
1488
1489- (void)dismissViewControllerAnimated:(BOOL)flag
1490 completion:(void (^)())completion {
Rohit Rao685807a52017-11-10 20:50:111491 // It is an error to call this method when no VC is being presented.
1492 DCHECK(!TabSwitcherPresentsBVCEnabled() || self.presentedViewController);
1493
Rohit Raoa668c022017-11-08 00:04:441494 // Some calling code invokes |dismissViewControllerAnimated:completion:|
1495 // multiple times. When the BVC is displayed using VC containment, multiple
1496 // calls are effectively idempotent because only the first call has any effect
1497 // and subsequent calls do nothing. However, when the BVC is presented,
1498 // subsequent calls end up dismissing the BVC itself. This is never what we
Rohit Rao685807a52017-11-10 20:50:111499 // want, so check for this case and return early. It is not enough to check
1500 // |self.dismissingModal| because some dismissals do not go through
1501 // -[BrowserViewController dismissViewControllerAnimated:completion:|.
Rohit Raoa668c022017-11-08 00:04:441502 // TODO(crbug.com/782338): Fix callers and remove this early return.
Rohit Rao685807a52017-11-10 20:50:111503 if (TabSwitcherPresentsBVCEnabled() &&
1504 (self.dismissingModal || self.presentedViewController.isBeingDismissed)) {
Rohit Raoa668c022017-11-08 00:04:441505 return;
1506 }
1507
sdefresnee65fd872016-12-19 13:38:131508 self.dismissingModal = YES;
stkhapuginc9eee7b2017-04-10 15:49:271509 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:131510 [super dismissViewControllerAnimated:flag
1511 completion:^{
stkhapuginc9eee7b2017-04-10 15:49:271512 BrowserViewController* strongSelf = weakSelf;
sdefresnee65fd872016-12-19 13:38:131513 [strongSelf setDismissingModal:NO];
1514 [strongSelf setPresenting:NO];
1515 if (completion)
1516 completion();
1517 [[strongSelf dialogPresenter] tryToPresent];
1518 }];
1519}
1520
1521- (void)presentViewController:(UIViewController*)viewControllerToPresent
1522 animated:(BOOL)flag
1523 completion:(void (^)())completion {
stkhapuginc9eee7b2017-04-10 15:49:271524 ProceduralBlock finalCompletionHandler = [completion copy];
sdefresnee65fd872016-12-19 13:38:131525 // TODO(crbug.com/580098) This is an interim fix for the flicker between the
1526 // launch screen and the FRE Animation. The fix is, if the FRE is about to be
1527 // presented, to show a temporary view of the launch screen and then remove it
1528 // when the controller for the FRE has been presented. This fix should be
1529 // removed when the FRE startup code is rewritten.
1530 BOOL firstRunLaunch = (FirstRun::IsChromeFirstRun() ||
1531 experimental_flags::AlwaysDisplayFirstRun()) &&
1532 !tests_hook::DisableFirstRun();
1533 // These if statements check that |presentViewController| is being called for
1534 // the FRE case.
1535 if (firstRunLaunch &&
1536 [viewControllerToPresent isKindOfClass:[UINavigationController class]]) {
1537 UINavigationController* navController =
1538 base::mac::ObjCCastStrict<UINavigationController>(
1539 viewControllerToPresent);
1540 if ([navController.topViewController
1541 isMemberOfClass:[WelcomeToChromeViewController class]]) {
1542 self.hideStatusBar = YES;
1543
1544 // Load view from Launch Screen and add it to window.
1545 NSBundle* mainBundle = base::mac::FrameworkBundle();
1546 NSArray* topObjects =
1547 [mainBundle loadNibNamed:@"LaunchScreen" owner:self options:nil];
1548 UIViewController* launchScreenController =
1549 base::mac::ObjCCastStrict<UIViewController>([topObjects lastObject]);
1550 // |launchScreenView| is loaded as an autoreleased object, and is retained
1551 // by the |completion| block below.
1552 UIView* launchScreenView = launchScreenController.view;
1553 launchScreenView.userInteractionEnabled = NO;
1554 launchScreenView.frame = self.view.window.bounds;
1555 [self.view.window addSubview:launchScreenView];
1556
1557 // Replace the completion handler sent to the superclass with one which
1558 // removes |launchScreenView| and resets the status bar. If |completion|
1559 // exists, it is called from within the new completion handler.
stkhapuginc9eee7b2017-04-10 15:49:271560 __weak BrowserViewController* weakSelf = self;
1561 finalCompletionHandler = ^{
sdefresnee65fd872016-12-19 13:38:131562 [launchScreenView removeFromSuperview];
stkhapuginc9eee7b2017-04-10 15:49:271563 weakSelf.hideStatusBar = NO;
sdefresnee65fd872016-12-19 13:38:131564 if (completion)
1565 completion();
stkhapuginc9eee7b2017-04-10 15:49:271566 };
sdefresnee65fd872016-12-19 13:38:131567 }
1568 }
1569
1570 self.presenting = YES;
justincohen7e61cd92016-12-24 00:38:171571 if ([_sideSwipeController inSwipe]) {
1572 [_sideSwipeController resetContentView];
1573 }
sdefresnee65fd872016-12-19 13:38:131574
1575 [super presentViewController:viewControllerToPresent
1576 animated:flag
1577 completion:finalCompletionHandler];
1578}
1579
1580#pragma mark - Notification handling
1581
1582- (void)registerForNotifications {
1583 DCHECK(_model);
1584 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1585 [defaultCenter addObserver:self
1586 selector:@selector(pageLoadStarting:)
1587 name:kTabModelTabWillStartLoadingNotification
1588 object:_model];
1589 [defaultCenter addObserver:self
1590 selector:@selector(pageLoadStarted:)
1591 name:kTabModelTabDidStartLoadingNotification
1592 object:_model];
1593 [defaultCenter addObserver:self
1594 selector:@selector(pageLoadComplete:)
1595 name:kTabModelTabDidFinishLoadingNotification
1596 object:_model];
1597 [defaultCenter addObserver:self
1598 selector:@selector(tabDeselected:)
1599 name:kTabModelTabDeselectedNotification
1600 object:_model];
1601 [defaultCenter addObserver:self
1602 selector:@selector(tabWasAdded:)
1603 name:kTabModelNewTabWillOpenNotification
1604 object:_model];
1605}
1606
1607- (void)pageLoadStarting:(NSNotification*)notify {
1608 Tab* tab = notify.userInfo[kTabModelTabKey];
1609 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
rohitrao6866d252017-04-12 12:03:511610
1611 // Stop any Find in Page searches and close the find bar when navigating to a
1612 // new page.
1613 [self closeFindInPage];
rohitraob2bf3cb2017-02-10 14:10:361614
sdefresnee65fd872016-12-19 13:38:131615 if (tab == [_model currentTab]) {
1616 // TODO(pinkerton): Fill in here about hiding the forward button on
1617 // navigation.
1618 }
1619}
1620
1621- (void)pageLoadStarted:(NSNotification*)notify {
1622 Tab* tab = notify.userInfo[kTabModelTabKey];
1623 DCHECK(tab);
1624 if (tab == [_model currentTab]) {
1625 if (![self isTabNativePage:tab]) {
sczsf1620e52017-10-02 22:54:461626 [_toolbarCoordinator currentPageLoadStarted];
sdefresnee65fd872016-12-19 13:38:131627 }
1628 [self updateVoiceSearchBarVisibilityAnimated:NO];
1629 }
1630}
1631
1632- (void)pageLoadComplete:(NSNotification*)notify {
1633 // Update the UI, but only if the current tab.
1634 Tab* tab = notify.userInfo[kTabModelTabKey];
1635 if (tab == [_model currentTab]) {
1636 // There isn't any need to update the toolbar here. When the page finishes,
1637 // it will have already sent us |-tabModel:didChangeTab:| which will do it.
1638 }
1639
1640 BOOL loadingSucceeded = [notify.userInfo[kTabModelPageLoadSuccess] boolValue];
1641
1642 [self tabLoadComplete:tab withSuccess:loadingSucceeded];
1643}
1644
1645- (void)tabDeselected:(NSNotification*)notify {
1646 DCHECK(notify);
1647 Tab* tab = notify.userInfo[kTabModelTabKey];
1648 DCHECK(tab);
1649 [tab wasHidden];
olivierrobin342024852017-03-16 15:33:221650 [self dismissPopups];
sdefresnee65fd872016-12-19 13:38:131651}
1652
1653- (void)tabWasAdded:(NSNotification*)notify {
1654 Tab* tab = notify.userInfo[kTabModelTabKey];
1655 DCHECK(tab);
1656
Eugene But56efc322017-08-11 14:03:441657 _temporaryNativeController = nil;
sdefresnee65fd872016-12-19 13:38:131658
1659 // When adding new tabs, check what kind of reminder infobar should
1660 // be added to the new tab. Try to add only one of them.
1661 // This check is done when a new tab is added either through the Tools Menu
1662 // "New Tab" or through "New Tab" in Stack View Controller. This method
1663 // is called after a new tab has added and finished initial navigation.
1664 // If this is added earlier, the initial navigation may end up clearing
1665 // the infobar(s) that are just added. See http://crbug/340250 for details.
Rohit Raoaf46af92017-08-10 12:52:301666 web::WebState* webState = tab.webState;
1667 DCHECK(webState);
1668
1669 infobars::InfoBarManager* infoBarManager =
1670 InfoBarManagerImpl::FromWebState(webState);
1671 [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
sdefresnee65fd872016-12-19 13:38:131672 forTabId:[tab tabId]];
edchin9e7a1112017-11-07 18:28:031673 if (!ReSignInInfoBarDelegate::Create(_browserState, tab,
1674 self /* id<SigninPresenter> */)) {
edchin95c927072017-11-04 00:35:071675 DisplaySyncErrors(_browserState, tab, self /* id<SyncPresenter> */);
sdefresnee65fd872016-12-19 13:38:131676 }
1677
1678 // The rest of this function initiates the new tab animation, which is
Kurt Horimotoca8bd7de2017-08-22 17:42:501679 // phone-specific. Call the foreground tab added completion block; for
1680 // iPhones, this will get executed after the animation has finished.
1681 if (IsIPadIdiom()) {
1682 if (self.foregroundTabWasAddedCompletionBlock) {
Olivier Robinc7e46242017-09-06 07:55:431683 // This callback is called before webState is activated (on
1684 // kTabModelNewTabWillOpenNotification notification). Dispatch the
1685 // callback asynchronously to be sure the activation is complete.
1686 dispatch_async(dispatch_get_main_queue(), ^() {
Olivier Robin89647972017-09-06 12:41:011687 // Test existence again as the block may have been deleted.
1688 if (self.foregroundTabWasAddedCompletionBlock) {
1689 self.foregroundTabWasAddedCompletionBlock();
1690 self.foregroundTabWasAddedCompletionBlock = nil;
1691 }
Olivier Robinc7e46242017-09-06 07:55:431692 });
Kurt Horimotoca8bd7de2017-08-22 17:42:501693 }
sdefresnee65fd872016-12-19 13:38:131694 return;
Kurt Horimotoca8bd7de2017-08-22 17:42:501695 }
sdefresnee65fd872016-12-19 13:38:131696
1697 // Do nothing if browsing is currently suspended. The BVC will set everything
1698 // up correctly when browsing resumes.
1699 if (!self.visible || ![_model webUsageEnabled])
1700 return;
1701
1702 BOOL inBackground = [notify.userInfo[kTabModelOpenInBackgroundKey] boolValue];
1703
1704 // Block that starts voice search at the end of new Tab animation if
1705 // necessary.
1706 ProceduralBlock startVoiceSearchIfNecessaryBlock = ^void() {
1707 if (_startVoiceSearchAfterNewTabAnimation) {
1708 _startVoiceSearchAfterNewTabAnimation = NO;
Jean-François Geyelin5d2e184c2017-07-28 19:48:001709 [self startVoiceSearchWithOriginView:nil];
sdefresnee65fd872016-12-19 13:38:131710 }
1711 };
1712
kkhorimotoa44349c12017-04-12 23:02:121713 self.inNewTabAnimation = YES;
sdefresnee65fd872016-12-19 13:38:131714 if (!inBackground) {
1715 UIView* animationParentView = _contentArea;
1716 // Create the new page image, and load with the new tab page snapshot.
1717 CGFloat newPageOffset = 0;
1718 UIImageView* newPage;
Sylvain Defresnee7f2c8a2017-10-17 02:39:191719 if (tab.webState->GetLastCommittedURL() == kChromeUINewTabURL &&
1720 !_isOffTheRecord && !IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:131721 animationParentView = self.view;
1722 newPage = [self pageFullScreenOpenCloseAnimationView];
1723 } else {
1724 newPage = [self pageOpenCloseAnimationView];
1725 }
1726 newPageOffset = newPage.frame.origin.y;
1727
1728 [tab view].frame = _contentArea.bounds;
1729 newPage.image = [tab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1730 [animationParentView addSubview:newPage];
1731 CGPoint origin = [self lastTapPoint];
Sylvain Defresneed8c0db2017-08-31 16:29:521732 page_animation_util::AnimateInPaperWithAnimationAndCompletion(
sdefresnee65fd872016-12-19 13:38:131733 newPage, -newPageOffset,
1734 newPage.frame.size.height - newPage.image.size.height, origin,
1735 _isOffTheRecord, NULL, ^{
1736 [newPage removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121737 self.inNewTabAnimation = NO;
michaeldof49c9b2c2016-12-20 23:07:421738 // Use the model's currentTab here because it is possible that it can
1739 // be reset to a new value before the new Tab animation finished (e.g.
1740 // if another Tab shows a dialog via |dialogPresenter|). However, that
1741 // tab's view hasn't been displayed yet because it was in a new tab
1742 // animation.
1743 Tab* currentTab = [_model currentTab];
1744 if (currentTab) {
1745 [self tabSelected:currentTab];
1746 }
sdefresnee65fd872016-12-19 13:38:131747 startVoiceSearchIfNecessaryBlock();
peterlaurens90ac0d32017-06-08 21:13:391748
1749 if (self.foregroundTabWasAddedCompletionBlock) {
1750 self.foregroundTabWasAddedCompletionBlock();
peterlaurens9f1b6e02017-06-22 17:46:451751 self.foregroundTabWasAddedCompletionBlock = nil;
peterlaurens90ac0d32017-06-08 21:13:391752 }
sdefresnee65fd872016-12-19 13:38:131753 });
1754 } else {
1755 // -updateSnapshotWithOverlay will force a screen redraw, so take the
1756 // snapshot before adding the views needed for the background animation.
1757 Tab* topTab = [_model currentTab];
1758 UIImage* image = [topTab updateSnapshotWithOverlay:YES
1759 visibleFrameOnly:self.isToolbarOnScreen];
1760 // Add three layers in order on top of the contentArea for the animation:
1761 // 1. The black "background" screen.
stkhapuginc9eee7b2017-04-10 15:49:271762 UIView* background = [[UIView alloc] initWithFrame:[_contentArea bounds]];
sdefresnee65fd872016-12-19 13:38:131763 InstallBackgroundInView(background);
1764 [_contentArea addSubview:background];
1765
1766 // 2. A CardView displaying the data from the current tab.
1767 CardView* topCard = [self addCardViewInFullscreen:!self.isToolbarOnScreen];
1768 NSString* title = [topTab title];
1769 if (![title length])
1770 title = [topTab urlDisplayString];
1771 [topCard setTitle:title];
sdefresnee65fd872016-12-19 13:38:131772 [topCard setImage:image];
Sylvain Defresne7178d4c2017-09-14 13:22:371773 [topCard setFavicon:nil];
1774
1775 favicon::FaviconDriver* faviconDriver =
1776 favicon::WebFaviconDriver::FromWebState(topTab.webState);
1777 if (faviconDriver && faviconDriver->FaviconIsValid()) {
1778 gfx::Image favicon = faviconDriver->GetFavicon();
1779 if (!favicon.IsEmpty())
1780 [topCard setFavicon:favicon.ToUIImage()];
1781 }
sdefresnee65fd872016-12-19 13:38:131782
1783 // 3. A new, blank CardView to represent the new tab being added.
1784 // Launch the new background tab animation.
Sylvain Defresneed8c0db2017-08-31 16:29:521785 page_animation_util::AnimateNewBackgroundPageWithCompletion(
sdefresnee65fd872016-12-19 13:38:131786 topCard, [_contentArea frame], IsPortrait(), ^{
1787 [background removeFromSuperview];
1788 [topCard removeFromSuperview];
kkhorimotoa44349c12017-04-12 23:02:121789 self.inNewTabAnimation = NO;
sdefresnee65fd872016-12-19 13:38:131790 // Resnapshot the top card if it has its own toolbar, as the toolbar
1791 // will be captured in the new tab animation, but isn't desired for
1792 // the stack view snapshots.
1793 id nativeController = [self nativeControllerForTab:topTab];
1794 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)])
1795 [topTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
1796 startVoiceSearchIfNecessaryBlock();
1797 });
peterlaurens9f1b6e02017-06-22 17:46:451798 // Reset the foreground tab completion block so that it can never be
1799 // called more than once regardless of foreground/background tab
1800 // appearances.
1801 self.foregroundTabWasAddedCompletionBlock = nil;
sdefresnee65fd872016-12-19 13:38:131802 }
1803}
1804
1805#pragma mark - UI Configuration and Layout
1806
1807- (void)updateWithTabModel:(TabModel*)model
1808 browserState:(ios::ChromeBrowserState*)browserState {
1809 DCHECK(model);
1810 DCHECK(browserState);
1811 DCHECK(!_model);
1812 DCHECK(!_browserState);
1813 _browserState = browserState;
1814 _isOffTheRecord = browserState->IsOffTheRecord() ? YES : NO;
stkhapuginc9eee7b2017-04-10 15:49:271815 _model = model;
Mark Cogandfcdea72017-07-18 13:47:381816
sdefresnee65fd872016-12-19 13:38:131817 [_model addObserver:self];
1818
1819 if (!_isOffTheRecord) {
1820 [DefaultIOSWebViewFactory
1821 registerWebViewFactory:[ChromeWebViewFactory class]];
1822 }
1823 NSUInteger count = [_model count];
1824 for (NSUInteger index = 0; index < count; ++index)
1825 [self installDelegatesForTab:[_model tabAtIndex:index]];
1826
1827 [self registerForNotifications];
1828
gambardbdc07cc2017-02-03 16:43:111829 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>(
Sylvain Defresne4aa6efc2017-08-10 16:14:121830 _browserState->GetRequestContext());
stkhapuginc9eee7b2017-04-10 15:49:271831 _dominantColorCache = [[NSMutableDictionary alloc] init];
sdefresnee65fd872016-12-19 13:38:131832
sdefresnedc432f42017-01-17 14:36:591833 // Register for bookmark changed notification (BookmarkModel may be null
1834 // during testing, so explicitly support this).
sdefresnee65fd872016-12-19 13:38:131835 _bookmarkModel = ios::BookmarkModelFactory::GetForBrowserState(_browserState);
sdefresnedc432f42017-01-17 14:36:591836 if (_bookmarkModel) {
1837 _bookmarkModelBridge.reset(new BrowserBookmarkModelBridge(self));
1838 _bookmarkModel->AddObserver(_bookmarkModelBridge.get());
1839 }
sdefresnee65fd872016-12-19 13:38:131840}
1841
sdefresnee65fd872016-12-19 13:38:131842- (void)browserStateDestroyed {
1843 [self setActive:NO];
sdefresnee65fd872016-12-19 13:38:131844 [_paymentRequestManager close];
stkhapuginc9eee7b2017-04-10 15:49:271845 _paymentRequestManager = nil;
sczsf1620e52017-10-02 22:54:461846 [_toolbarCoordinator browserStateDestroyed];
sdefresnee65fd872016-12-19 13:38:131847 [_model browserStateDestroyed];
sczsdd860eba2017-08-10 01:55:381848
1849 // Disconnect child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431850 [_activityServiceCoordinator disconnect];
Rohit Raocda0a992017-08-16 15:37:111851 [_qrScannerCoordinator disconnect];
sczsdd860eba2017-08-10 01:55:381852 [_tabHistoryCoordinator disconnect];
Gregory Chatzinoffdf93d692017-09-09 01:32:271853 [_pageInfoCoordinator disconnect];
Louis Romerod11747a2017-10-20 20:10:351854 [_externalSearchCoordinator disconnect];
edchinf5150c682017-09-18 02:50:031855 [self.tabStripCoordinator stop];
1856 self.tabStripCoordinator = nil;
1857 self.tabStripView = nil;
sczsdd860eba2017-08-10 01:55:381858
sdefresnee65fd872016-12-19 13:38:131859 _browserState = nullptr;
justincohen75011c32017-04-28 16:31:391860 [_dispatcher stopDispatchingToTarget:self];
1861 _dispatcher = nil;
sdefresnee65fd872016-12-19 13:38:131862}
1863
1864- (void)installFakeStatusBar {
Justin Cohenb3170c32017-09-19 01:55:221865 CGFloat statusBarHeight = StatusBarHeight();
1866 CGRect statusBarFrame =
1867 CGRectMake(0, 0, [[self view] frame].size.width, statusBarHeight);
1868 _fakeStatusBarView = [[UIView alloc] initWithFrame:statusBarFrame];
1869 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
sdefresnee65fd872016-12-19 13:38:131870 if (IsIPadIdiom()) {
Justin Cohenb3170c32017-09-19 01:55:221871 [_fakeStatusBarView setBackgroundColor:StatusBarBackgroundColor()];
1872 [_fakeStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
1873 [_fakeStatusBarView layer].zPosition = 99;
1874 [[self view] addSubview:_fakeStatusBarView];
1875 } else {
1876 // Add a white bar on phone so that the status bar on the NTP is white.
1877 [_fakeStatusBarView setBackgroundColor:[UIColor whiteColor]];
1878 [self.view insertSubview:_fakeStatusBarView atIndex:0];
sdefresnee65fd872016-12-19 13:38:131879 }
1880}
1881
1882// Create the UI elements. May or may not have valid browser state & tab model.
1883- (void)buildToolbarAndTabStrip {
1884 DCHECK([self isViewLoaded]);
1885 DCHECK(!_toolbarModelDelegate);
1886
Rohit Rao44f204302017-08-10 14:49:541887 // Initialize the prerender service before creating the toolbar controller.
1888 PrerenderService* prerenderService =
1889 PrerenderServiceFactory::GetForBrowserState(self.browserState);
1890 if (prerenderService) {
1891 prerenderService->SetDelegate(self);
sdefresnee65fd872016-12-19 13:38:131892 }
1893
1894 // Create the toolbar model and controller.
rohitrao8c4c7fd2017-04-03 15:31:201895 _toolbarModelDelegate.reset(
1896 new ToolbarModelDelegateIOS([_model webStateList]));
sdefresnee65fd872016-12-19 13:38:131897 _toolbarModelIOS.reset([_dependencyFactory
1898 newToolbarModelIOSWithDelegate:_toolbarModelDelegate.get()]);
sczsf1620e52017-10-02 22:54:461899 _toolbarCoordinator =
1900 [[LegacyToolbarCoordinator alloc] initWithBaseViewController:self];
Gauthier Ambard29939db12017-10-30 16:47:311901 _sideSwipeController.toolbarInteractionHandler = _toolbarCoordinator;
sczsf1620e52017-10-02 22:54:461902 _toolbarCoordinator.tabModel = _model;
Gauthier Ambard82c8cc52017-10-26 15:59:051903 [_toolbarCoordinator
sczs32cfde82017-11-14 20:43:221904 setToolbarController:
1905 [_dependencyFactory
1906 newToolbarControllerWithDelegate:self
1907 urlLoader:self
1908 dispatcher:self.dispatcher]];
sczsf1620e52017-10-02 22:54:461909 [_dispatcher startDispatchingToTarget:_toolbarCoordinator
justincohen75011c32017-04-28 16:31:391910 forProtocol:@protocol(OmniboxFocuser)];
sczsf1620e52017-10-02 22:54:461911 [_toolbarCoordinator setTabCount:[_model count]];
stkhapuginc9eee7b2017-04-10 15:49:271912 if (_voiceSearchController)
sczsf1620e52017-10-02 22:54:461913 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:051914 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:131915
sdefresnee65fd872016-12-19 13:38:131916 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:031917 self.tabStripCoordinator =
1918 [[TabStripLegacyCoordinator alloc] initWithBaseViewController:self];
1919 self.tabStripCoordinator.browserState = _browserState;
1920 self.tabStripCoordinator.dispatcher = _dispatcher;
1921 self.tabStripCoordinator.tabModel = _model;
1922 self.tabStripCoordinator.presentationProvider = self;
1923 self.tabStripCoordinator.animationWaitDuration =
Kurt Horimoto62e97c72017-11-03 19:51:471924 kLegacyFullscreenControllerToolbarAnimationDuration;
edchinf5150c682017-09-18 02:50:031925 [self.tabStripCoordinator start];
sdefresnee65fd872016-12-19 13:38:131926 }
1927
1928 // Create infobar container.
1929 if (!_infoBarContainerDelegate) {
1930 _infoBarContainerDelegate.reset(new InfoBarContainerDelegateIOS(self));
1931 _infoBarContainer.reset(
1932 new InfoBarContainerIOS(_infoBarContainerDelegate.get()));
1933 }
1934}
1935
Jean-François Geyelined4cde72017-10-11 11:34:501936- (void)addConstraintsToToolbar {
Jean-François Geyelince0a4742017-10-25 12:34:111937 NSLayoutYAxisAnchor* topAnchor;
1938 // On iPad, the toolbar is underneath the tab strip.
1939 // On iPhone, it is underneath the top of the screen.
1940 if (IsIPadIdiom()) {
1941 topAnchor = self.tabStripView.bottomAnchor;
1942 } else {
1943 topAnchor = [self view].topAnchor;
1944 }
1945
Gauthier Ambard100670f72017-10-27 09:54:271946 [_toolbarCoordinator adjustToolbarHeight];
Jean-François Geyelined4cde72017-10-11 11:34:501947
1948 [NSLayoutConstraint activateConstraints:@[
sczs42f7f7482017-11-08 01:13:271949 [_toolbarCoordinator.toolbarViewController.view.leadingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501950 constraintEqualToAnchor:[self view].leadingAnchor],
sczs42f7f7482017-11-08 01:13:271951 [_toolbarCoordinator.toolbarViewController.view.topAnchor
1952 constraintEqualToAnchor:topAnchor],
1953 [_toolbarCoordinator.toolbarViewController.view.trailingAnchor
Jean-François Geyelined4cde72017-10-11 11:34:501954 constraintEqualToAnchor:[self view].trailingAnchor],
Jean-François Geyelined4cde72017-10-11 11:34:501955 ]];
1956 [[self view] layoutIfNeeded];
1957}
1958
sdefresnee65fd872016-12-19 13:38:131959// Enable functionality that only makes sense if the views are loaded and
1960// both browser state and tab model are valid.
1961- (void)addUIFunctionalityForModelAndBrowserState {
1962 DCHECK(_browserState);
Randall Raymond8b66a402017-06-09 14:19:051963 DCHECK(_toolbarModelIOS);
sdefresnee65fd872016-12-19 13:38:131964 DCHECK(_model);
1965 DCHECK([self isViewLoaded]);
1966
1967 [self.sideSwipeController addHorizontalGesturesToView:self.view];
1968
Rohit Raoaf46af92017-08-10 12:52:301969 infobars::InfoBarManager* infoBarManager = nullptr;
1970 if (_model.currentTab) {
1971 DCHECK(_model.currentTab.webState);
1972 infoBarManager =
1973 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
1974 }
sdefresnee65fd872016-12-19 13:38:131975 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
1976
sczsdd860eba2017-08-10 01:55:381977 // Create child coordinators.
Rohit Rao01e0e002017-08-14 20:49:431978 _activityServiceCoordinator = [[ActivityServiceLegacyCoordinator alloc]
1979 initWithBaseViewController:self];
1980 _activityServiceCoordinator.dispatcher = _dispatcher;
1981 _activityServiceCoordinator.tabModel = _model;
1982 _activityServiceCoordinator.browserState = _browserState;
sczsf1620e52017-10-02 22:54:461983 _activityServiceCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051984 [_toolbarCoordinator activityServicePositioner];
Rohit Rao01e0e002017-08-14 20:49:431985 _activityServiceCoordinator.presentationProvider = self;
Rohit Rao01e0e002017-08-14 20:49:431986
Rohit Raocda0a992017-08-16 15:37:111987 _qrScannerCoordinator =
1988 [[QRScannerLegacyCoordinator alloc] initWithBaseViewController:self];
1989 _qrScannerCoordinator.dispatcher = _dispatcher;
Gauthier Ambard82c8cc52017-10-26 15:59:051990 _qrScannerCoordinator.loadProvider =
1991 [_toolbarCoordinator QRScannerResultLoader];
Rohit Raocda0a992017-08-16 15:37:111992 _qrScannerCoordinator.presentationProvider = self;
1993
sczsdd860eba2017-08-10 01:55:381994 _tabHistoryCoordinator =
sczs0a726d22017-08-21 22:40:131995 [[LegacyTabHistoryCoordinator alloc] initWithBaseViewController:self];
sczsdd860eba2017-08-10 01:55:381996 _tabHistoryCoordinator.dispatcher = _dispatcher;
sczsf1620e52017-10-02 22:54:461997 _tabHistoryCoordinator.positionProvider =
Gauthier Ambard82c8cc52017-10-26 15:59:051998 [_toolbarCoordinator tabHistoryPositioner];
sczsdd860eba2017-08-10 01:55:381999 _tabHistoryCoordinator.tabModel = _model;
2000 _tabHistoryCoordinator.presentationProvider = self;
sczsf1620e52017-10-02 22:54:462001 _tabHistoryCoordinator.tabHistoryUIUpdater =
Gauthier Ambard82c8cc52017-10-26 15:59:052002 [_toolbarCoordinator tabHistoryUIUpdater];
sczsdd860eba2017-08-10 01:55:382003
sczs6ae47ad2017-09-06 17:26:532004 _sadTabCoordinator = [[SadTabLegacyCoordinator alloc] init];
edchin9eaf25f52017-10-26 02:42:202005 _sadTabCoordinator.baseViewController = self;
2006 _sadTabCoordinator.dispatcher = self.dispatcher;
sczs6ae47ad2017-09-06 17:26:532007
Gregory Chatzinoffdf93d692017-09-09 01:32:272008 _pageInfoCoordinator =
2009 [[PageInfoLegacyCoordinator alloc] initWithBaseViewController:self];
2010 _pageInfoCoordinator.browserState = _browserState;
2011 _pageInfoCoordinator.dispatcher = _dispatcher;
2012 _pageInfoCoordinator.loader = self;
2013 _pageInfoCoordinator.presentationProvider = self;
2014 _pageInfoCoordinator.tabModel = _model;
2015
Louis Romerod11747a2017-10-20 20:10:352016 _externalSearchCoordinator = [[ExternalSearchCoordinator alloc] init];
2017 _externalSearchCoordinator.dispatcher = _dispatcher;
2018
mathp9b4c11d2017-07-06 20:24:132019 if (base::FeatureList::IsEnabled(payments::features::kWebPayments)) {
stkhapuginc9eee7b2017-04-10 15:49:272020 _paymentRequestManager = [[PaymentRequestManager alloc]
sdefresnee65fd872016-12-19 13:38:132021 initWithBaseViewController:self
Gregory Chatzinoff1c96f802017-08-18 19:02:202022 browserState:_browserState
2023 dispatcher:self.dispatcher];
Randall Raymond8b66a402017-06-09 14:19:052024 [_paymentRequestManager setToolbarModel:_toolbarModelIOS.get()];
Mohamad Ahmadi7d09ec32017-07-11 22:32:192025 [_paymentRequestManager setActiveWebState:[_model currentTab].webState];
sdefresnee65fd872016-12-19 13:38:132026 }
2027}
2028
2029// Set the frame for the various views. View must be loaded.
Justin Cohen4eeada32017-11-13 18:21:282030- (void)setUpViewLayout:(BOOL)initialLayout {
sdefresnee65fd872016-12-19 13:38:132031 DCHECK([self isViewLoaded]);
sdefresnee65fd872016-12-19 13:38:132032 CGFloat widthOfView = CGRectGetWidth([self view].bounds);
sdefresnee65fd872016-12-19 13:38:132033 CGFloat minY = [self headerOffset];
2034
Justin Cohenb3170c32017-09-19 01:55:222035 // Update the fake toolbar background height.
2036 CGRect fakeStatusBarFrame = _fakeStatusBarView.frame;
2037 fakeStatusBarFrame.size.height = StatusBarHeight();
2038 _fakeStatusBarView.frame = fakeStatusBarFrame;
2039
edchinf5150c682017-09-18 02:50:032040 if (self.tabStripView) {
2041 minY += CGRectGetHeight([self.tabStripView frame]);
sdefresnee65fd872016-12-19 13:38:132042 }
2043
2044 // Position the toolbar next, either at the top of the browser view or
2045 // directly under the tabstrip.
Justin Cohen4eeada32017-11-13 18:21:282046 if (initialLayout)
2047 [self addChildViewController:_toolbarCoordinator.toolbarViewController];
sczs42f7f7482017-11-08 01:13:272048 CGRect toolbarFrame = _toolbarCoordinator.toolbarViewController.view.frame;
sdefresnee65fd872016-12-19 13:38:132049 toolbarFrame.origin = CGPointMake(0, minY);
2050 toolbarFrame.size.width = widthOfView;
Justin Cohenba27610e2017-11-08 19:34:452051 if (!IsSafeAreaCompatibleToolbarEnabled()) {
sczs42f7f7482017-11-08 01:13:272052 [_toolbarCoordinator.toolbarViewController.view setFrame:toolbarFrame];
Jean-François Geyelined4cde72017-10-11 11:34:502053 }
sdefresnee65fd872016-12-19 13:38:132054
2055 // Place the infobar container above the content area.
2056 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
Justin Cohen4eeada32017-11-13 18:21:282057 if (initialLayout)
2058 [self.view insertSubview:infoBarContainerView aboveSubview:_contentArea];
sdefresnee65fd872016-12-19 13:38:132059
2060 // Place the toolbar controller above the infobar container.
Justin Cohen4eeada32017-11-13 18:21:282061 if (initialLayout)
2062 [[self view] insertSubview:_toolbarCoordinator.toolbarViewController.view
2063 aboveSubview:infoBarContainerView];
sdefresnee65fd872016-12-19 13:38:132064 minY += CGRectGetHeight(toolbarFrame);
Justin Cohen4eeada32017-11-13 18:21:282065 if (initialLayout)
2066 [_toolbarCoordinator.toolbarViewController
2067 didMoveToParentViewController:self];
sdefresnee65fd872016-12-19 13:38:132068
2069 // Account for the toolbar's drop shadow. The toolbar overlaps with the web
2070 // content slightly.
sczs8c837782017-10-03 02:57:242071 minY -= 0.0;
sdefresnee65fd872016-12-19 13:38:132072
2073 // Adjust the content area to be under the toolbar, for fullscreen or below
2074 // the toolbar is not fullscreen.
2075 CGRect contentFrame = [_contentArea frame];
2076 CGFloat marginWithHeader = StatusBarHeight();
Justin Cohenb3170c32017-09-19 01:55:222077 contentFrame.size.height = CGRectGetMaxY(contentFrame) - marginWithHeader;
2078 contentFrame.origin.y = marginWithHeader;
sdefresnee65fd872016-12-19 13:38:132079 [_contentArea setFrame:contentFrame];
2080
2081 // Adjust the infobar container to be either at the bottom of the screen
2082 // (iPhone) or on the lower toolbar edge (iPad).
2083 CGRect infoBarFrame = contentFrame;
2084 infoBarFrame.origin.y = CGRectGetMaxY(contentFrame);
2085 infoBarFrame.size.height = 0;
2086 [infoBarContainerView setFrame:infoBarFrame];
2087
2088 // Attach the typing shield to the content area but have it hidden.
2089 [_typingShield setFrame:[_contentArea frame]];
Justin Cohen4eeada32017-11-13 18:21:282090 if (initialLayout)
2091 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
sdefresnee65fd872016-12-19 13:38:132092 [_typingShield setHidden:YES];
2093 _typingShield.accessibilityIdentifier = @"Typing Shield";
2094 _typingShield.accessibilityLabel = l10n_util::GetNSString(IDS_CANCEL);
2095}
2096
sdefresnee65fd872016-12-19 13:38:132097- (void)displayTab:(Tab*)tab isNewSelection:(BOOL)newSelection {
2098 DCHECK(tab);
Mark Cogan059ce7c2017-07-18 10:40:442099 [self loadViewIfNeeded];
sdefresnee65fd872016-12-19 13:38:132100
kkhorimotoa44349c12017-04-12 23:02:122101 if (!self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132102 // Hide findbar. |updateToolbar| will restore the findbar later.
2103 [self hideFindBarWithAnimation:NO];
2104
2105 // Make new content visible, resizing it first as the orientation may
2106 // have changed from the last time it was displayed.
2107 [[tab view] setFrame:_contentArea.bounds];
2108 [_contentArea displayContentView:[tab view]];
2109 }
2110 [self updateToolbar];
2111
2112 if (newSelection)
sczsf1620e52017-10-02 22:54:462113 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:132114
2115 // Notify the Tab that it was displayed.
2116 [tab wasShown];
2117}
2118
2119- (void)initializeBookmarkInteractionController {
2120 if (_bookmarkInteractionController)
2121 return;
edchinbb8ba892017-09-12 15:44:032122 _bookmarkInteractionController = [[BookmarkInteractionController alloc]
2123 initWithBrowserState:_browserState
2124 loader:self
2125 parentController:self
2126 dispatcher:self.dispatcher];
sdefresnee65fd872016-12-19 13:38:132127}
2128
2129// Update the state of back and forward buttons, hiding the forward button if
2130// there is nowhere to go. Assumes the model's current tab is up to date.
2131- (void)updateToolbar {
2132 // If the BVC has been partially torn down for low memory, wait for the
2133 // view rebuild to handle toolbar updates.
2134 if (!(_toolbarModelIOS && _browserState))
2135 return;
2136
2137 Tab* tab = [_model currentTab];
2138 if (![tab navigationManager])
2139 return;
sczsf1620e52017-10-02 22:54:462140 [_toolbarCoordinator updateToolbarState];
2141 [_toolbarCoordinator setShareButtonEnabled:self.canShowShareMenu];
sdefresnee65fd872016-12-19 13:38:132142
Sylvain Defresnef5d2d952017-11-14 11:15:312143 if (_insertedTabWasPrerenderedTab && !_toolbarModelIOS->IsLoading())
sczsf1620e52017-10-02 22:54:462144 [_toolbarCoordinator showPrerenderingAnimation];
sdefresnee65fd872016-12-19 13:38:132145
2146 // Also update the loading state for the tools menu (that is really an
2147 // extension of the toolbar on the iPhone).
2148 if (!IsIPadIdiom())
sczsf1620e52017-10-02 22:54:462149 [[_toolbarCoordinator toolsPopupController]
sdefresnee65fd872016-12-19 13:38:132150 setIsTabLoading:_toolbarModelIOS->IsLoading()];
2151
rohitrao005a6432017-03-16 20:52:422152 auto* findHelper = FindTabHelper::FromWebState(tab.webState);
2153 if (findHelper && findHelper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:132154 [self showFindBarWithAnimation:NO
2155 selectText:YES
2156 shouldFocus:[_findBarController isFocused]];
rohitraob2bf3cb2017-02-10 14:10:362157 }
sdefresnee65fd872016-12-19 13:38:132158
2159 // Hide the toolbar if displaying phone NTP.
2160 if (!IsIPadIdiom()) {
kkhorimoto7aed9e262017-03-04 02:28:552161 web::NavigationItem* item = [tab navigationManager]->GetVisibleItem();
sdefresnee65fd872016-12-19 13:38:132162 BOOL hideToolbar = NO;
kkhorimoto7aed9e262017-03-04 02:28:552163 if (item) {
2164 GURL url = item->GetURL();
sdefresnee65fd872016-12-19 13:38:132165 BOOL isNTP = url.GetOrigin() == GURL(kChromeUINewTabURL);
2166 hideToolbar = isNTP && !_isOffTheRecord &&
sczsf1620e52017-10-02 22:54:462167 ![_toolbarCoordinator isOmniboxFirstResponder] &&
2168 ![_toolbarCoordinator showingOmniboxPopup];
sdefresnee65fd872016-12-19 13:38:132169 }
sczs42f7f7482017-11-08 01:13:272170 [_toolbarCoordinator.toolbarViewController.view setHidden:hideToolbar];
sdefresnee65fd872016-12-19 13:38:132171 }
2172}
2173
2174- (void)updateDialogPresenterActiveState {
kkhorimotoa44349c12017-04-12 23:02:122175 self.dialogPresenter.active =
2176 self.active && self.viewVisible && !self.inNewTabAnimation;
sdefresnee65fd872016-12-19 13:38:132177}
2178
2179- (void)dismissPopups {
sczsf1620e52017-10-02 22:54:462180 [_toolbarCoordinator dismissToolsMenuPopup];
Gregory Chatzinoffdf93d692017-09-09 01:32:272181 [self.dispatcher hidePageInfo];
Gauthier Ambardbf382242017-10-19 14:51:282182 [_tabHistoryCoordinator dismissHistoryPopup];
Cooper Knaakd0a974cd2017-08-10 18:05:472183 [self.tabTipBubblePresenter dismissAnimated:YES];
Cooper Knaak33f9f402017-08-09 18:04:382184}
2185
Cooper Knaakd0a974cd2017-08-10 18:05:472186- (BubbleViewControllerPresenter*)
2187bubblePresenterForFeature:(const base::Feature&)feature
2188 direction:(BubbleArrowDirection)direction
2189 alignment:(BubbleAlignment)alignment
2190 text:(NSString*)text {
Gregory Chatzinoff541b8642017-10-25 00:25:212191 DCHECK(self.browserState);
2192 if (!feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
Cooper Knaak33f9f402017-08-09 18:04:382193 ->ShouldTriggerHelpUI(feature)) {
Cooper Knaakd0a974cd2017-08-10 18:05:472194 return nil;
Cooper Knaak33f9f402017-08-09 18:04:382195 }
2196 // Capture |weakSelf| instead of the feature engagement tracker object
2197 // because |weakSelf| will safely become |nil| if it is deallocated, whereas
2198 // the feature engagement tracker will remain pointing to invalid memory if
2199 // its owner (the ChromeBrowserState) is deallocated.
2200 __weak BrowserViewController* weakSelf = self;
2201 void (^dismissalCallback)(void) = ^() {
2202 BrowserViewController* strongSelf = weakSelf;
2203 if (strongSelf) {
2204 feature_engagement::TrackerFactory::GetForBrowserState(
2205 strongSelf.browserState)
2206 ->Dismissed(feature);
2207 }
2208 };
2209
Cooper Knaakd0a974cd2017-08-10 18:05:472210 BubbleViewControllerPresenter* bubbleViewControllerPresenter =
Cooper Knaak33f9f402017-08-09 18:04:382211 [[BubbleViewControllerPresenter alloc] initWithText:text
2212 arrowDirection:direction
2213 alignment:alignment
2214 dismissalCallback:dismissalCallback];
2215
Cooper Knaakd0a974cd2017-08-10 18:05:472216 return bubbleViewControllerPresenter;
sdefresnee65fd872016-12-19 13:38:132217}
2218
Cooper Knaak120cee5e2017-08-10 20:57:002219- (void)presentNewTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212220 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002221 // If the tab tip bubble has already been presented and the user is still
2222 // considered engaged, it can't be overwritten or set to |nil| or else it will
2223 // reset the |userEngaged| property. Once the user is not engaged, the bubble
2224 // can be safely overwritten or set to |nil|.
2225 if (!self.tabTipBubblePresenter.isUserEngaged) {
2226 __weak BrowserViewController* weakSelf = self;
2227 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2228 [weakSelf presentNewTabTipBubble];
2229 };
2230
2231 // Because the new tab tip occurs on startup, the feature engagement
2232 // tracker's database is not guaranteed to be loaded by this time. For the
2233 // bubble to appear properly, a callback is used to guarantee the event data
2234 // is loaded before the check to see if the promotion should be displayed.
2235 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2236 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2237 }
2238}
2239
2240- (void)presentNewTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212241 DCHECK(self.browserState);
Cooper Knaak120cee5e2017-08-10 20:57:002242 NSString* text =
2243 l10n_util::GetNSStringWithFixup(IDS_IOS_NEW_TAB_IPH_PROMOTION_TEXT);
2244 CGPoint tabSwitcherAnchor;
2245 if (IsIPadIdiom()) {
edchinf5150c682017-09-18 02:50:032246 DCHECK([self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002247 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
edchinf5150c682017-09-18 02:50:032248 tabSwitcherAnchor = [self.tabStripCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002249 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2250 } else {
sczsf1620e52017-10-02 22:54:462251 DCHECK([_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002252 respondsToSelector:@selector(anchorPointForTabSwitcherButton:)]);
sczsf1620e52017-10-02 22:54:462253 tabSwitcherAnchor = [_toolbarCoordinator
Cooper Knaak120cee5e2017-08-10 20:57:002254 anchorPointForTabSwitcherButton:BubbleArrowDirectionUp];
2255 }
Cooper Knaake963d6702017-08-11 21:03:112256 // If the feature engagement tracker does not consider it valid to display
2257 // the new tab tip, then |bubblePresenterForFeature| returns |nil| and the
2258 // call to |presentInViewController| is a no-op.
Cooper Knaak120cee5e2017-08-10 20:57:002259 self.tabTipBubblePresenter =
2260 [self bubblePresenterForFeature:feature_engagement::kIPHNewTabTipFeature
2261 direction:BubbleArrowDirectionUp
2262 alignment:BubbleAlignmentTrailing
2263 text:text];
2264 [self.tabTipBubblePresenter presentInViewController:self
2265 view:self.view
2266 anchorPoint:tabSwitcherAnchor];
2267}
2268
Helen Yang9175bd52017-08-12 00:28:402269- (void)presentNewIncognitoTabTipBubbleOnInitialized {
Gregory Chatzinoff541b8642017-10-25 00:25:212270 DCHECK(self.browserState);
Helen Yang9175bd52017-08-12 00:28:402271 // Do not override |incognitoTabtipBubblePresenter| or set it to nil if the
2272 // user is still considered engaged.
2273 if (!self.incognitoTabTipBubblePresenter.isUserEngaged) {
2274 __weak BrowserViewController* weakSelf = self;
2275 void (^onInitializedBlock)(bool) = ^(bool successfullyLoaded) {
2276 [weakSelf presentNewIncognitoTabTipBubble];
2277 };
2278
2279 // Use a callback in case the new incognito tab tip should be shown on
2280 // startup. This ensures that the tracker's database will be fully loaded
2281 // before checking if the promotion should be displayed.
2282 feature_engagement::TrackerFactory::GetForBrowserState(self.browserState)
2283 ->AddOnInitializedCallback(base::BindBlockArc(onInitializedBlock));
2284 }
2285}
2286
2287- (void)presentNewIncognitoTabTipBubble {
Gregory Chatzinoff541b8642017-10-25 00:25:212288 DCHECK(self.browserState);
sczsf1620e52017-10-02 22:54:462289 DCHECK([_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402290 respondsToSelector:@selector(anchorPointForToolsMenuButton:)]);
2291 NSString* text = l10n_util::GetNSStringWithFixup(
2292 IDS_IOS_NEW_INCOGNITO_TAB_IPH_PROMOTION_TEXT);
sczsf1620e52017-10-02 22:54:462293 CGPoint toolsButtonAnchor = [_toolbarCoordinator
Helen Yang9175bd52017-08-12 00:28:402294 anchorPointForToolsMenuButton:BubbleArrowDirectionUp];
2295 self.incognitoTabTipBubblePresenter =
2296 [self bubblePresenterForFeature:feature_engagement::
2297 kIPHNewIncognitoTabTipFeature
2298 direction:BubbleArrowDirectionUp
2299 alignment:BubbleAlignmentTrailing
2300 text:text];
2301 [self.incognitoTabTipBubblePresenter
2302 presentInViewController:self
2303 view:self.view
2304 anchorPoint:toolsButtonAnchor];
2305 // Only trigger the tools menu button animation if the bubble is shown.
2306 if (self.incognitoTabTipBubblePresenter) {
sczsf1620e52017-10-02 22:54:462307 [_toolbarCoordinator triggerToolsMenuButtonAnimation];
Helen Yang9175bd52017-08-12 00:28:402308 }
2309}
2310
sdefresnee65fd872016-12-19 13:38:132311#pragma mark - Tap handling
2312
Mark Cogandfcdea72017-07-18 13:47:382313- (void)setLastTapPoint:(OpenNewTabCommand*)command {
Mark Cogane01ebce2017-07-12 19:31:032314 if (CGPointEqualToPoint(command.originPoint, CGPointZero)) {
2315 _lastTapPoint = CGPointZero;
2316 } else {
2317 _lastTapPoint =
2318 [self.view.window convertPoint:command.originPoint toView:self.view];
sdefresnee65fd872016-12-19 13:38:132319 }
Mark Cogane01ebce2017-07-12 19:31:032320 _lastTapTime = CACurrentMediaTime();
sdefresnee65fd872016-12-19 13:38:132321}
2322
2323- (CGPoint)lastTapPoint {
2324 if (CACurrentMediaTime() - _lastTapTime < 1) {
2325 return _lastTapPoint;
2326 }
2327 return CGPointZero;
2328}
2329
2330- (void)saveContentAreaTapLocation:(UIGestureRecognizer*)gestureRecognizer {
2331 UIView* view = gestureRecognizer.view;
2332 CGPoint viewCoordinate = [gestureRecognizer locationInView:view];
2333 _lastTapPoint =
2334 [[view superview] convertPoint:viewCoordinate toView:self.view];
2335 _lastTapTime = CACurrentMediaTime();
2336}
2337
2338- (BOOL)addTabIfNoTabWithNormalBrowserState {
2339 if (![_model count]) {
2340 if (!_isOffTheRecord) {
2341 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
2342 transition:ui::PAGE_TRANSITION_TYPED];
2343 return YES;
2344 }
2345 }
2346 return NO;
2347}
2348
2349#pragma mark - Tab creation and selection
2350
2351// Called when either a tab finishes loading or when a tab with finished content
2352// is added directly to the model via pre-rendering.
2353- (void)tabLoadComplete:(Tab*)tab withSuccess:(BOOL)success {
2354 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
2355
2356 // Persist the session on a delay.
2357 [_model saveSessionImmediately:NO];
2358}
2359
2360- (Tab*)addSelectedTabWithURL:(const GURL&)url
2361 postData:(TemplateURLRef::PostContent*)postData
2362 transition:(ui::PageTransition)transition {
2363 return [self addSelectedTabWithURL:url
2364 postData:postData
2365 atIndex:[_model count]
Olivier Robind508a5632017-07-19 16:29:492366 transition:transition
2367 tabAddedCompletion:nil];
sdefresnee65fd872016-12-19 13:38:132368}
2369
2370- (Tab*)addSelectedTabWithURL:(const GURL&)url
2371 transition:(ui::PageTransition)transition {
2372 return [self addSelectedTabWithURL:url
2373 atIndex:[_model count]
2374 transition:transition];
2375}
2376
2377- (Tab*)addSelectedTabWithURL:(const GURL&)url
2378 atIndex:(NSUInteger)position
2379 transition:(ui::PageTransition)transition {
2380 return [self addSelectedTabWithURL:url
Olivier Robind508a5632017-07-19 16:29:492381 atIndex:position
2382 transition:transition
2383 tabAddedCompletion:nil];
2384}
2385
2386- (Tab*)addSelectedTabWithURL:(const GURL&)url
2387 atIndex:(NSUInteger)position
2388 transition:(ui::PageTransition)transition
2389 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
2390 return [self addSelectedTabWithURL:url
sdefresnee65fd872016-12-19 13:38:132391 postData:NULL
2392 atIndex:position
Olivier Robind508a5632017-07-19 16:29:492393 transition:transition
2394 tabAddedCompletion:tabAddedCompletion];
sdefresnee65fd872016-12-19 13:38:132395}
2396
2397- (Tab*)addSelectedTabWithURL:(const GURL&)URL
2398 postData:(TemplateURLRef::PostContent*)postData
2399 atIndex:(NSUInteger)position
Olivier Robind508a5632017-07-19 16:29:492400 transition:(ui::PageTransition)transition
2401 tabAddedCompletion:(ProceduralBlock)tabAddedCompletion {
sdefresnee65fd872016-12-19 13:38:132402 if (position == NSNotFound)
2403 position = [_model count];
2404 DCHECK(position <= [_model count]);
2405
2406 web::NavigationManager::WebLoadParams params(URL);
2407 params.transition_type = transition;
2408 if (postData) {
2409 // Extract the content type and post params from |postData| and add them
2410 // to the load params.
2411 NSString* contentType = base::SysUTF8ToNSString(postData->first);
2412 NSData* data = [NSData dataWithBytes:(void*)postData->second.data()
2413 length:postData->second.length()];
stkhapuginf58b10d02017-04-10 13:36:172414 params.post_data.reset(data);
2415 params.extra_headers.reset(@{ @"Content-Type" : contentType });
sdefresnee65fd872016-12-19 13:38:132416 }
Olivier Robind508a5632017-07-19 16:29:492417
2418 if (tabAddedCompletion) {
2419 if (self.foregroundTabWasAddedCompletionBlock) {
2420 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
2421 self.foregroundTabWasAddedCompletionBlock;
2422 self.foregroundTabWasAddedCompletionBlock = ^{
2423 oldForegroundTabWasAddedCompletionBlock();
2424 tabAddedCompletion();
2425 };
2426 } else {
2427 self.foregroundTabWasAddedCompletionBlock = tabAddedCompletion;
2428 }
2429 }
2430
sdefresnea6395912017-03-01 01:14:352431 Tab* tab = [_model insertTabWithLoadParams:params
2432 opener:nil
2433 openedByDOM:NO
2434 atIndex:position
2435 inBackground:NO];
sdefresnee65fd872016-12-19 13:38:132436 return tab;
2437}
2438
olivierrobin889af53f2017-03-01 14:56:322439// Whether the given tab's URL is an application specific URL.
sdefresnee65fd872016-12-19 13:38:132440- (BOOL)isTabNativePage:(Tab*)tab {
olivierrobin889af53f2017-03-01 14:56:322441 web::WebState* webState = tab.webState;
2442 if (!webState)
2443 return NO;
liaoyukeea9f3ee62017-03-07 22:05:392444 web::NavigationItem* visibleItem =
2445 webState->GetNavigationManager()->GetVisibleItem();
olivierrobin889af53f2017-03-01 14:56:322446 if (!visibleItem)
2447 return NO;
2448 return web::GetWebClient()->IsAppSpecificURL(visibleItem->GetURL());
sdefresnee65fd872016-12-19 13:38:132449}
2450
2451- (void)expectNewForegroundTab {
2452 _expectingForegroundTab = YES;
2453}
2454
2455- (UIImageView*)pageFullScreenOpenCloseAnimationView {
2456 CGRect viewBounds, remainder;
2457 CGRectDivide(self.view.bounds, &remainder, &viewBounds, StatusBarHeight(),
2458 CGRectMinYEdge);
stkhapuginf58b10d02017-04-10 13:36:172459 return [[UIImageView alloc] initWithFrame:viewBounds];
sdefresnee65fd872016-12-19 13:38:132460}
2461
2462- (UIImageView*)pageOpenCloseAnimationView {
2463 CGRect frame = [_contentArea bounds];
2464
2465 frame.size.height = frame.size.height - [self headerHeight];
2466 frame.origin.y = [self headerHeight];
2467
stkhapuginf58b10d02017-04-10 13:36:172468 UIImageView* pageView = [[UIImageView alloc] initWithFrame:frame];
sdefresnee65fd872016-12-19 13:38:132469 CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
2470 pageView.center = center;
2471
2472 pageView.backgroundColor = [UIColor whiteColor];
2473 return pageView;
2474}
2475
2476- (void)installDelegatesForTab:(Tab*)tab {
sdefresne49cf2862017-03-15 13:46:142477 // Unregistration happens when the Tab is removed from the TabModel.
Sylvain Defresnef5d2d952017-11-14 11:15:312478 DCHECK_NE(tab.webState->GetDelegate(), _webStateDelegate.get());
2479
2480 // There should be no pre-rendered Tabs in TabModel.
2481 PrerenderService* prerenderService =
2482 PrerenderServiceFactory::GetForBrowserState(_browserState);
2483 DCHECK(!prerenderService ||
2484 !prerenderService->IsWebStatePrerendered(tab.webState));
edchincd32fdf2017-10-25 12:45:452485
2486 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2487 if (PasswordTabHelper* passwordTabHelper =
2488 PasswordTabHelper::FromWebState(tab.webState)) {
2489 passwordTabHelper->SetDispatcher(self.dispatcher);
2490 passwordTabHelper->SetPasswordControllerDelegate(self);
2491 }
2492
sdefresnee65fd872016-12-19 13:38:132493 tab.dialogDelegate = self;
2494 tab.snapshotOverlayProvider = self;
sdefresnee65fd872016-12-19 13:38:132495 tab.passKitDialogProvider = self;
Kurt Horimotoa5a922a2017-11-07 00:21:092496 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472497 tab.legacyFullscreenControllerDelegate = self;
Kurt Horimoto803840622017-10-28 01:20:372498 }
sdefresnee65fd872016-12-19 13:38:132499 if (!IsIPadIdiom()) {
2500 tab.overscrollActionsControllerDelegate = self;
2501 }
olivierrobin9ce77b82017-01-12 17:29:192502 tab.tabHeadersDelegate = self;
sdefresnee65fd872016-12-19 13:38:132503 tab.tabSnapshottingDelegate = self;
2504 // Install the proper CRWWebController delegates.
2505 tab.webController.nativeProvider = self;
2506 tab.webController.swipeRecognizerProvider = self.sideSwipeController;
pkld6e73e52017-03-08 15:56:512507 // BrowserViewController presents SKStoreKitViewController on behalf of a
2508 // tab.
2509 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2510 if (tabHelper)
2511 tabHelper->SetLauncher(self);
sdefresnee65fd872016-12-19 13:38:132512 tab.webState->SetDelegate(_webStateDelegate.get());
sczs6ae47ad2017-09-06 17:26:532513 // BrowserViewController owns the coordinator that displays the Sad Tab.
sczsdfef35b2017-10-27 01:39:292514 if (!SadTabTabHelper::FromWebState(tab.webState)) {
sczs6ae47ad2017-09-06 17:26:532515 SadTabTabHelper::CreateForWebState(tab.webState, _sadTabCoordinator);
sczsdfef35b2017-10-27 01:39:292516 }
Sylvain Defresnecacc3a52017-09-12 13:51:042517 PrintTabHelper::CreateForWebState(tab.webState, self);
Eugene But35ded552017-09-13 23:31:592518 RepostFormTabHelper::CreateForWebState(tab.webState, self);
Gregory Chatzinoff5f9f7f02017-09-19 02:04:572519 NetExportTabHelper::CreateForWebState(tab.webState, self);
Mike Dougherty4620cf8e2017-10-31 23:37:092520 CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
edchincd32fdf2017-10-25 12:45:452521
2522 if (AccountConsistencyService* accountConsistencyService =
2523 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2524 self.browserState)) {
2525 accountConsistencyService->SetWebStateHandler(tab.webState, self);
Tomasz Garbusb844e992017-09-29 12:44:552526 }
sdefresnee65fd872016-12-19 13:38:132527}
2528
sdefresne49cf2862017-03-15 13:46:142529- (void)uninstallDelegatesForTab:(Tab*)tab {
edchin5b3d1072017-10-24 13:43:112530 DCHECK_EQ(tab.webState->GetDelegate(), _webStateDelegate.get());
edchincd32fdf2017-10-25 12:45:452531
2532 // TODO(crbug.com/777557): do not pass the dispatcher to PasswordTabHelper.
2533 if (PasswordTabHelper* passwordTabHelper =
2534 PasswordTabHelper::FromWebState(tab.webState))
2535 passwordTabHelper->SetDispatcher(nil);
2536
sdefresne49cf2862017-03-15 13:46:142537 tab.dialogDelegate = nil;
2538 tab.snapshotOverlayProvider = nil;
2539 tab.passKitDialogProvider = nil;
Kurt Horimotoa5a922a2017-11-07 00:21:092540 if (!base::FeatureList::IsEnabled(fullscreen::features::kNewFullscreen)) {
Kurt Horimoto62e97c72017-11-03 19:51:472541 tab.legacyFullscreenControllerDelegate = nil;
Kurt Horimoto803840622017-10-28 01:20:372542 }
sdefresne49cf2862017-03-15 13:46:142543 if (!IsIPadIdiom()) {
2544 tab.overscrollActionsControllerDelegate = nil;
2545 }
2546 tab.tabHeadersDelegate = nil;
2547 tab.tabSnapshottingDelegate = nil;
2548 tab.webController.nativeProvider = nil;
2549 tab.webController.swipeRecognizerProvider = nil;
2550 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(tab.webState);
2551 if (tabHelper)
2552 tabHelper->SetLauncher(nil);
2553 tab.webState->SetDelegate(nullptr);
edchincd32fdf2017-10-25 12:45:452554 if (AccountConsistencyService* accountConsistencyService =
2555 ios::AccountConsistencyServiceFactory::GetForBrowserState(
2556 self.browserState)) {
2557 accountConsistencyService->RemoveWebStateHandler(tab.webState);
2558 }
sdefresne49cf2862017-03-15 13:46:142559}
2560
sdefresnee65fd872016-12-19 13:38:132561// Called when a tab is selected in the model. Make any required view changes.
2562// The notification will not be sent when the tab is already the selected tab.
2563- (void)tabSelected:(Tab*)tab {
2564 DCHECK(tab);
2565
2566 // Ignore changes while the tab stack view is visible (or while suspended).
2567 // The display will be refreshed when this view becomes active again.
2568 if (!self.visible || ![_model webUsageEnabled])
2569 return;
2570
2571 [self displayTab:tab isNewSelection:YES];
2572
kkhorimotoa44349c12017-04-12 23:02:122573 if (_expectingForegroundTab && !self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:132574 // Now that the new tab has been displayed, return to normal. Rather than
2575 // keep a reference to the previous tab, just turn off preview mode for all
2576 // tabs (since doing so is a no-op for the tabs that don't have it set).
2577 _expectingForegroundTab = NO;
stkhapuginc9eee7b2017-04-10 15:49:272578 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:132579 [tab.webController setOverlayPreviewMode:NO];
2580 }
2581 }
2582}
2583
edchinf5150c682017-09-18 02:50:032584- (UIView<TabStripFoldAnimation>*)tabStripPlaceholderView {
2585 return [self.tabStripCoordinator placeholderView];
2586}
2587
Sylvain Defresne41170aa2017-06-15 10:25:202588- (void)shutdown {
2589 DCHECK(!_isShutdown);
2590 _isShutdown = YES;
edchinf5150c682017-09-18 02:50:032591 [self.tabStripCoordinator stop];
2592 self.tabStripCoordinator = nil;
sczs42f7f7482017-11-08 01:13:272593 [_toolbarCoordinator stop];
2594 _toolbarCoordinator = nil;
edchinf5150c682017-09-18 02:50:032595 self.tabStripView = nil;
Sylvain Defresne41170aa2017-06-15 10:25:202596 _infoBarContainer = nil;
2597 _readingListMenuNotifier = nil;
2598 if (_bookmarkModel)
2599 _bookmarkModel->RemoveObserver(_bookmarkModelBridge.get());
2600 [_model removeObserver:self];
2601 [[UpgradeCenter sharedInstance] unregisterClient:self];
2602 [[NSNotificationCenter defaultCenter] removeObserver:self];
Gauthier Ambard82c8cc52017-10-26 15:59:052603 [_toolbarCoordinator setToolbarDelegate:nil];
Sylvain Defresne41170aa2017-06-15 10:25:202604 if (_voiceSearchController)
2605 _voiceSearchController->SetDelegate(nil);
2606 [_rateThisAppDialog setDelegate:nil];
2607 [_model closeAllTabs];
Mohamad Ahmadibec07eb2017-09-12 19:38:462608 [_paymentRequestManager setActiveWebState:nullptr];
Sylvain Defresne41170aa2017-06-15 10:25:202609}
2610
sdefresnee65fd872016-12-19 13:38:132611#pragma mark - SnapshotOverlayProvider methods
2612
2613- (NSArray*)snapshotOverlaysForTab:(Tab*)tab {
2614 NSMutableArray* overlays = [NSMutableArray array];
2615 if (![_model webUsageEnabled]) {
2616 return overlays;
2617 }
2618 UIView* voiceSearchView = [self voiceSearchOverlayViewForTab:tab];
2619 if (voiceSearchView) {
2620 CGFloat voiceSearchYOffset = [self voiceSearchOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272621 SnapshotOverlay* voiceSearchOverlay =
sdefresnee65fd872016-12-19 13:38:132622 [[SnapshotOverlay alloc] initWithView:voiceSearchView
stkhapuginc9eee7b2017-04-10 15:49:272623 yOffset:voiceSearchYOffset];
sdefresnee65fd872016-12-19 13:38:132624 [overlays addObject:voiceSearchOverlay];
2625 }
2626 UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
2627 if (infoBarView) {
2628 CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
stkhapuginc9eee7b2017-04-10 15:49:272629 SnapshotOverlay* infoBarOverlay =
sdefresnee65fd872016-12-19 13:38:132630 [[SnapshotOverlay alloc] initWithView:infoBarView
stkhapuginc9eee7b2017-04-10 15:49:272631 yOffset:infoBarYOffset];
sdefresnee65fd872016-12-19 13:38:132632 [overlays addObject:infoBarOverlay];
2633 }
2634 return overlays;
2635}
2636
2637#pragma mark -
2638
2639- (UIView*)infoBarOverlayViewForTab:(Tab*)tab {
2640 if (IsIPadIdiom()) {
2641 // Not using overlays on iPad because the content is pushed down by
2642 // infobar and the transition between snapshot and fresh page can
2643 // cause both snapshot and real infobars to appear at the same time.
2644 return nil;
2645 }
2646 Tab* currentTab = [_model currentTab];
Rohit Raoaf46af92017-08-10 12:52:302647 if (currentTab && tab == currentTab) {
2648 DCHECK(currentTab.webState);
2649 infobars::InfoBarManager* infoBarManager =
2650 InfoBarManagerImpl::FromWebState(currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132651 if (infoBarManager->infobar_count() > 0) {
2652 DCHECK(_infoBarContainer);
2653 return _infoBarContainer->view();
2654 }
2655 }
2656 return nil;
2657}
2658
2659- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
stkhapuginc9eee7b2017-04-10 15:49:272660 if (tab != [_model currentTab] || !_infoBarContainer) {
sdefresnee65fd872016-12-19 13:38:132661 // There is no UI representation for non-current tabs or there is
2662 // no _infoBarContainer instantiated yet.
2663 // Return offset outside of tab.
2664 return CGRectGetMaxY(self.view.frame);
2665 } else if (IsIPadIdiom()) {
2666 // The infobars on iPad are display at the top of a tab.
2667 return CGRectGetMinY([[_model currentTab].webController visibleFrame]);
2668 } else {
2669 // The infobars on iPhone are displayed at the bottom of a tab.
2670 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2671 return CGRectGetMaxY(visibleFrame) -
2672 CGRectGetHeight(_infoBarContainer->view().frame);
2673 }
2674}
2675
2676- (UIView*)voiceSearchOverlayViewForTab:(Tab*)tab {
2677 Tab* currentTab = [_model currentTab];
2678 if (tab && tab == currentTab && tab.isVoiceSearchResultsTab &&
2679 _voiceSearchBar && ![_voiceSearchBar isHidden]) {
2680 return _voiceSearchBar;
2681 }
2682 return nil;
2683}
2684
2685- (CGFloat)voiceSearchOverlayYOffsetForTab:(Tab*)tab {
2686 if (tab != [_model currentTab] || [_voiceSearchBar isHidden]) {
2687 // There is no UI representation for non-current tabs or there is
2688 // no visible voice search. Return offset outside of tab.
2689 return CGRectGetMaxY(self.view.frame);
2690 } else {
2691 // The voice search bar on iPhone is displayed at the bottom of a tab.
2692 CGRect visibleFrame = [[_model currentTab].webController visibleFrame];
2693 return CGRectGetMaxY(visibleFrame) - kVoiceSearchBarHeight;
2694 }
2695}
2696
2697- (void)ensureVoiceSearchControllerCreated {
stkhapuginc9eee7b2017-04-10 15:49:272698 if (!_voiceSearchController) {
sdefresnee65fd872016-12-19 13:38:132699 VoiceSearchProvider* provider =
2700 ios::GetChromeBrowserProvider()->GetVoiceSearchProvider();
2701 if (provider) {
2702 _voiceSearchController =
2703 provider->CreateVoiceSearchController(_browserState);
sczsf1620e52017-10-02 22:54:462704 _voiceSearchController->SetDelegate(
Gauthier Ambard82c8cc52017-10-26 15:59:052705 [_toolbarCoordinator voiceSearchDelegate]);
sdefresnee65fd872016-12-19 13:38:132706 }
2707 }
2708}
2709
2710- (void)ensureVoiceSearchBarCreated {
2711 if (_voiceSearchBar)
2712 return;
2713
2714 CGFloat width = CGRectGetWidth([[self view] bounds]);
2715 CGFloat y = CGRectGetHeight([[self view] bounds]) - kVoiceSearchBarHeight;
2716 CGRect frame = CGRectMake(0.0, y, width, kVoiceSearchBarHeight);
stkhapuginc9eee7b2017-04-10 15:49:272717 _voiceSearchBar = ios::GetChromeBrowserProvider()
2718 ->GetVoiceSearchProvider()
Jean-François Geyelin5d2e184c2017-07-28 19:48:002719 ->BuildVoiceSearchBar(frame, self.dispatcher);
sdefresnee65fd872016-12-19 13:38:132720 [_voiceSearchBar setVoiceSearchBarDelegate:self];
2721 [_voiceSearchBar setHidden:YES];
2722 [_voiceSearchBar setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin |
2723 UIViewAutoresizingFlexibleWidth];
2724 [self.view insertSubview:_voiceSearchBar
2725 belowSubview:_infoBarContainer->view()];
2726}
2727
2728- (void)updateVoiceSearchBarVisibilityAnimated:(BOOL)animated {
2729 // Voice search bar exists and is shown/hidden.
2730 BOOL show = self.shouldShowVoiceSearchBar;
stkhapuginc9eee7b2017-04-10 15:49:272731 if (_voiceSearchBar && _voiceSearchBar.hidden != show)
sdefresnee65fd872016-12-19 13:38:132732 return;
2733
2734 // Voice search bar doesn't exist and thus is not visible.
2735 if (!_voiceSearchBar && !show)
2736 return;
2737
2738 if (animated)
stkhapuginc9eee7b2017-04-10 15:49:272739 [_voiceSearchBar animateToBecomeVisible:show];
sdefresnee65fd872016-12-19 13:38:132740 else
stkhapuginc9eee7b2017-04-10 15:49:272741 _voiceSearchBar.hidden = !show;
sdefresnee65fd872016-12-19 13:38:132742}
2743
2744- (id<LogoAnimationControllerOwner>)currentLogoAnimationControllerOwner {
2745 Protocol* ownerProtocol = @protocol(LogoAnimationControllerOwner);
2746 if ([_voiceSearchBar conformsToProtocol:ownerProtocol] &&
2747 self.shouldShowVoiceSearchBar) {
2748 // Use |_voiceSearchBar| for VoiceSearch results tab and dismissal
2749 // animations.
stkhapuginc9eee7b2017-04-10 15:49:272750 return static_cast<id<LogoAnimationControllerOwner>>(_voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:132751 }
2752 id currentNativeController =
2753 [self nativeControllerForTab:self.tabModel.currentTab];
2754 Protocol* possibleOwnerProtocol =
2755 @protocol(LogoAnimationControllerOwnerOwner);
2756 if ([currentNativeController conformsToProtocol:possibleOwnerProtocol] &&
2757 [currentNativeController logoAnimationControllerOwner]) {
2758 // If the current native controller is showing a GLIF view (e.g. the NTP
2759 // when there is no doodle), use that GLIFControllerOwner.
2760 return [currentNativeController logoAnimationControllerOwner];
2761 }
2762 return nil;
2763}
2764
2765#pragma mark - PassKitDialogProvider methods
2766
2767- (void)presentPassKitDialog:(NSData*)data {
2768 NSError* error = nil;
stkhapuginc9eee7b2017-04-10 15:49:272769 PKPass* pass = nil;
sdefresnee65fd872016-12-19 13:38:132770 if (data)
stkhapuginc9eee7b2017-04-10 15:49:272771 pass = [[PKPass alloc] initWithData:data error:&error];
sdefresnee65fd872016-12-19 13:38:132772 if (error || !data) {
2773 if ([_model currentTab]) {
Rohit Raoaf46af92017-08-10 12:52:302774 DCHECK(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132775 infobars::InfoBarManager* infoBarManager =
Rohit Raoaf46af92017-08-10 12:52:302776 InfoBarManagerImpl::FromWebState(_model.currentTab.webState);
sdefresnee65fd872016-12-19 13:38:132777 // TODO(crbug.com/227994): Infobar cleanup (infoBarManager should never be
2778 // NULL, replace if with DCHECK).
2779 if (infoBarManager)
2780 [_dependencyFactory showPassKitErrorInfoBarForManager:infoBarManager];
2781 }
2782 } else {
2783 PKAddPassesViewController* passKitViewController =
2784 [_dependencyFactory newPassKitViewControllerForPass:pass];
2785 if (passKitViewController) {
2786 [self presentViewController:passKitViewController
2787 animated:YES
2788 completion:^{
2789 }];
2790 }
2791 }
2792}
2793
2794- (UIStatusBarStyle)preferredStatusBarStyle {
2795 return (IsIPadIdiom() || _isOffTheRecord) ? UIStatusBarStyleLightContent
2796 : UIStatusBarStyleDefault;
2797}
2798
Tomasz Garbusb844e992017-09-29 12:44:552799#pragma mark - PasswordControllerDelegate methods
2800
2801- (BOOL)displaySignInNotification:(UIViewController*)viewController
2802 fromTabId:(NSString*)tabId {
2803 // Check if the call comes from currently visible tab.
2804 if ([tabId isEqual:[_model currentTab].tabId]) {
2805 [self addChildViewController:viewController];
2806 [self.view addSubview:viewController.view];
2807 [viewController didMoveToParentViewController:self];
2808 return YES;
2809 } else {
2810 return NO;
2811 }
2812}
2813
sdefresnee65fd872016-12-19 13:38:132814#pragma mark - CRWWebStateDelegate methods.
2815
eugenebut75a06fa72017-01-09 17:09:552816- (web::WebState*)webState:(web::WebState*)webState
eugenebut275f5892017-03-09 22:20:512817 createNewWebStateForURL:(const GURL&)URL
2818 openerURL:(const GURL&)openerURL
2819 initiatedByUser:(BOOL)initiatedByUser {
2820 // Check if requested web state is a popup and block it if necessary.
2821 if (!initiatedByUser) {
2822 auto* helper = BlockedPopupTabHelper::FromWebState(webState);
2823 if (helper->ShouldBlockPopup(openerURL)) {
kkhorimoto069cf2c2017-05-09 22:00:102824 // It's possible for a page to inject a popup into a window created via
2825 // window.open before its initial load is committed. Rather than relying
2826 // on the last committed or pending NavigationItem's referrer policy, just
2827 // use ReferrerPolicyDefault.
2828 // TODO(crbug.com/719993): Update this to a more appropriate referrer
2829 // policy once referrer policies are correctly recorded in
2830 // NavigationItems.
2831 web::Referrer referrer(openerURL, web::ReferrerPolicyDefault);
eugenebut275f5892017-03-09 22:20:512832 helper->HandlePopup(URL, referrer);
2833 return nil;
2834 }
2835 }
2836
2837 // Requested web state should not be blocked from opening.
2838 Tab* currentTab = LegacyTabHelper::GetTabForWebState(webState);
2839 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
2840
2841 // Tabs open by DOM are always renderer initiated.
2842 web::NavigationManager::WebLoadParams params(GURL{});
2843 params.transition_type = ui::PAGE_TRANSITION_LINK;
2844 params.is_renderer_initiated = true;
2845 Tab* childTab = [[self tabModel]
2846 insertTabWithLoadParams:params
2847 opener:currentTab
2848 openedByDOM:YES
2849 atIndex:TabModelConstants::kTabPositionAutomatically
2850 inBackground:NO];
2851 return childTab.webState;
2852}
2853
eugenebutb46b2122017-03-14 02:43:262854- (void)closeWebState:(web::WebState*)webState {
2855 // Only allow a web page to close itself if it was opened by DOM, or if there
2856 // are no navigation items.
2857 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
kkhorimotoa8ee9dec2017-03-21 01:53:582858 DCHECK(webState->HasOpener() || ![tab navigationManager]->GetItemCount());
eugenebutb46b2122017-03-14 02:43:262859
2860 if (![self tabModel])
2861 return;
2862
2863 NSUInteger index = [[self tabModel] indexOfTab:tab];
2864 if (index != NSNotFound)
2865 [[self tabModel] closeTabAtIndex:index];
2866}
2867
eugenebut275f5892017-03-09 22:20:512868- (web::WebState*)webState:(web::WebState*)webState
eugenebut75a06fa72017-01-09 17:09:552869 openURLWithParams:(const web::WebState::OpenURLParams&)params {
2870 switch (params.disposition) {
2871 case WindowOpenDisposition::NEW_FOREGROUND_TAB:
2872 case WindowOpenDisposition::NEW_BACKGROUND_TAB: {
2873 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352874 insertTabWithURL:params.url
2875 referrer:params.referrer
2876 transition:params.transition
2877 opener:LegacyTabHelper::GetTabForWebState(webState)
2878 openedByDOM:NO
2879 atIndex:TabModelConstants::kTabPositionAutomatically
2880 inBackground:(params.disposition ==
2881 WindowOpenDisposition::NEW_BACKGROUND_TAB)];
eugenebut75a06fa72017-01-09 17:09:552882 return tab.webState;
2883 }
2884 case WindowOpenDisposition::CURRENT_TAB: {
2885 web::NavigationManager::WebLoadParams loadParams(params.url);
2886 loadParams.referrer = params.referrer;
2887 loadParams.transition_type = params.transition;
2888 loadParams.is_renderer_initiated = params.is_renderer_initiated;
2889 webState->GetNavigationManager()->LoadURLWithParams(loadParams);
2890 return webState;
2891 }
eugenebutd0984e82017-02-22 23:47:512892 case WindowOpenDisposition::NEW_POPUP: {
2893 Tab* tab = [[self tabModel]
sdefresnea6395912017-03-01 01:14:352894 insertTabWithURL:params.url
2895 referrer:params.referrer
2896 transition:params.transition
2897 opener:LegacyTabHelper::GetTabForWebState(webState)
2898 openedByDOM:YES
2899 atIndex:TabModelConstants::kTabPositionAutomatically
2900 inBackground:NO];
eugenebutd0984e82017-02-22 23:47:512901 return tab.webState;
2902 }
eugenebut75a06fa72017-01-09 17:09:552903 default:
2904 NOTIMPLEMENTED();
2905 return nullptr;
2906 };
2907}
2908
Mike Dougherty4e6b3a32017-08-23 18:49:212909- (void)webState:(web::WebState*)webState
sdefresnee65fd872016-12-19 13:38:132910 handleContextMenu:(const web::ContextMenuParams&)params {
2911 // Prevent context menu from displaying for a tab which is no longer the
2912 // current one.
2913 if (webState != [_model currentTab].webState) {
Mike Dougherty4e6b3a32017-08-23 18:49:212914 return;
sdefresnee65fd872016-12-19 13:38:132915 }
2916
2917 // No custom context menu if no valid url is available in |params|.
2918 if (!params.link_url.is_valid() && !params.src_url.is_valid()) {
Mike Dougherty4e6b3a32017-08-23 18:49:212919 return;
sdefresnee65fd872016-12-19 13:38:132920 }
2921
2922 DCHECK(_browserState);
sdefresnee65fd872016-12-19 13:38:132923
stkhapuginc9eee7b2017-04-10 15:49:272924 _contextMenuCoordinator =
2925 [[ContextMenuCoordinator alloc] initWithBaseViewController:self
2926 params:params];
sdefresnee65fd872016-12-19 13:38:132927
2928 NSString* title = nil;
2929 ProceduralBlock action = nil;
2930
stkhapuginc9eee7b2017-04-10 15:49:272931 __weak BrowserViewController* weakSelf = self;
sdefresnee65fd872016-12-19 13:38:132932 GURL link = params.link_url;
2933 bool isLink = link.is_valid();
2934 GURL imageUrl = params.src_url;
2935 bool isImage = imageUrl.is_valid();
Sylvain Defresnee7f2c8a2017-10-17 02:39:192936 const GURL& lastCommittedURL = webState->GetLastCommittedURL();
sdefresnee65fd872016-12-19 13:38:132937
2938 if (isLink) {
2939 if (link.SchemeIs(url::kJavaScriptScheme)) {
2940 // Open
2941 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPEN);
2942 action = ^{
2943 Record(ACTION_OPEN_JAVASCRIPT, isImage, isLink);
2944 [weakSelf openJavascript:base::SysUTF8ToNSString(link.GetContent())];
2945 };
2946 [_contextMenuCoordinator addItemWithTitle:title action:action];
2947 }
2948
2949 if (web::UrlHasWebScheme(link)) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:192950 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:132951
sdefresnee65fd872016-12-19 13:38:132952 // Open in New Tab.
2953 title = l10n_util::GetNSStringWithFixup(
2954 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWTAB);
2955 action = ^{
2956 Record(ACTION_OPEN_IN_NEW_TAB, isImage, isLink);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002957 // The "New Tab" item in the context menu opens a new tab in the current
2958 // browser state. |isOffTheRecord| indicates whether or not the current
2959 // browser state is incognito.
sdefresnee65fd872016-12-19 13:38:132960 [weakSelf webPageOrderedOpen:link
2961 referrer:referrer
Cooper Knaak9ae6b4f4a2017-07-25 18:56:002962 inIncognito:weakSelf.isOffTheRecord
sdefresnee65fd872016-12-19 13:38:132963 inBackground:YES
2964 appendTo:kCurrentTab];
2965 };
2966 [_contextMenuCoordinator addItemWithTitle:title action:action];
2967 if (!_isOffTheRecord) {
2968 // Open in Incognito Tab.
2969 title = l10n_util::GetNSStringWithFixup(
2970 IDS_IOS_CONTENT_CONTEXT_OPENLINKNEWINCOGNITOTAB);
2971 action = ^{
2972 Record(ACTION_OPEN_IN_INCOGNITO_TAB, isImage, isLink);
2973 [weakSelf webPageOrderedOpen:link
2974 referrer:referrer
sdefresnee65fd872016-12-19 13:38:132975 inIncognito:YES
2976 inBackground:NO
2977 appendTo:kCurrentTab];
2978 };
2979 [_contextMenuCoordinator addItemWithTitle:title action:action];
2980 }
olivierrobin51d4cf42017-01-17 13:32:352981 }
gambard65d69152017-03-23 17:44:222982 if (link.SchemeIsHTTPOrHTTPS()) {
olivierrobin51d4cf42017-01-17 13:32:352983 NSString* innerText = params.link_text;
2984 if ([innerText length] > 0) {
2985 // Add to reading list.
2986 title = l10n_util::GetNSStringWithFixup(
2987 IDS_IOS_CONTENT_CONTEXT_ADDTOREADINGLIST);
2988 action = ^{
2989 Record(ACTION_READ_LATER, isImage, isLink);
2990 [weakSelf addToReadingListURL:link title:innerText];
2991 };
2992 [_contextMenuCoordinator addItemWithTitle:title action:action];
gambard5fd403492017-01-17 09:17:532993 }
sdefresnee65fd872016-12-19 13:38:132994 }
2995 // Copy Link.
2996 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_COPY);
2997 action = ^{
2998 Record(ACTION_COPY_LINK_ADDRESS, isImage, isLink);
gambard6a138362017-02-06 17:19:282999 StoreURLInPasteboard(link);
sdefresnee65fd872016-12-19 13:38:133000 };
3001 [_contextMenuCoordinator addItemWithTitle:title action:action];
3002 }
3003 if (isImage) {
Sylvain Defresnee7f2c8a2017-10-17 02:39:193004 web::Referrer referrer(lastCommittedURL, params.referrer_policy);
sdefresnee65fd872016-12-19 13:38:133005 // Save Image.
gambard98b4ddf2017-04-18 07:14:053006 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_SAVEIMAGE);
sdefresnee65fd872016-12-19 13:38:133007 action = ^{
3008 Record(ACTION_SAVE_IMAGE, isImage, isLink);
3009 [weakSelf saveImageAtURL:imageUrl referrer:referrer];
3010 };
3011 [_contextMenuCoordinator addItemWithTitle:title action:action];
3012 // Open Image.
3013 title = l10n_util::GetNSStringWithFixup(IDS_IOS_CONTENT_CONTEXT_OPENIMAGE);
3014 action = ^{
3015 Record(ACTION_OPEN_IMAGE, isImage, isLink);
3016 [weakSelf loadURL:imageUrl
3017 referrer:referrer
3018 transition:ui::PAGE_TRANSITION_LINK
3019 rendererInitiated:YES];
3020 };
3021 [_contextMenuCoordinator addItemWithTitle:title action:action];
3022 // Open Image In New Tab.
3023 title = l10n_util::GetNSStringWithFixup(
3024 IDS_IOS_CONTENT_CONTEXT_OPENIMAGENEWTAB);
3025 action = ^{
3026 Record(ACTION_OPEN_IMAGE_IN_NEW_TAB, isImage, isLink);
3027 [weakSelf webPageOrderedOpen:imageUrl
3028 referrer:referrer
sdefresnee65fd872016-12-19 13:38:133029 inBackground:true
3030 appendTo:kCurrentTab];
3031 };
3032 [_contextMenuCoordinator addItemWithTitle:title action:action];
3033
3034 TemplateURLService* service =
3035 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103036 const TemplateURL* defaultURL = service->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133037 if (defaultURL && !defaultURL->image_url().empty() &&
3038 defaultURL->image_url_ref().IsValid(service->search_terms_data())) {
3039 title = l10n_util::GetNSStringF(IDS_IOS_CONTEXT_MENU_SEARCHWEBFORIMAGE,
3040 defaultURL->short_name());
3041 action = ^{
3042 Record(ACTION_SEARCH_BY_IMAGE, isImage, isLink);
3043 [weakSelf searchByImageAtURL:imageUrl referrer:referrer];
3044 };
3045 [_contextMenuCoordinator addItemWithTitle:title action:action];
3046 }
3047 }
3048
3049 [_contextMenuCoordinator start];
sdefresnee65fd872016-12-19 13:38:133050}
3051
eugenebutb739bdc2017-01-25 06:32:483052- (void)webState:(web::WebState*)webState
3053 runRepostFormDialogWithCompletionHandler:(void (^)(BOOL))handler {
3054 // Display the action sheet with the arrow pointing at the top center of the
3055 // web contents.
sdefresne0452a9d2017-02-09 15:33:283056 Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
eugenebutb739bdc2017-01-25 06:32:483057 UIView* view = webState->GetView();
3058 CGPoint dialogLocation =
3059 CGPointMake(CGRectGetMidX(view.frame),
sdefresne0452a9d2017-02-09 15:33:283060 CGRectGetMinY(view.frame) + [self headerHeightForTab:tab]);
vmpstr843b41a2017-03-01 21:15:033061 auto* helper = RepostFormTabHelper::FromWebState(webState);
stkhapuginf58b10d02017-04-10 13:36:173062 helper->PresentDialog(dialogLocation,
3063 base::BindBlockArc(^(bool shouldContinue) {
eugenebutcae3d9e62017-01-27 20:01:053064 handler(shouldContinue);
3065 }));
eugenebutb739bdc2017-01-25 06:32:483066}
3067
sdefresnee65fd872016-12-19 13:38:133068- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
3069 (web::WebState*)webState {
3070 return _javaScriptDialogPresenter.get();
3071}
3072
eugenebut63232102017-01-19 16:19:403073- (void)webState:(web::WebState*)webState
3074 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
3075 proposedCredential:(NSURLCredential*)proposedCredential
3076 completionHandler:(void (^)(NSString* username,
3077 NSString* password))handler {
3078 [self.dialogPresenter runAuthDialogForProtectionSpace:protectionSpace
3079 proposedCredential:proposedCredential
3080 webState:webState
3081 completionHandler:handler];
3082}
3083
Kurt Horimoto62e97c72017-11-03 19:51:473084#pragma mark - LegacyFullscreenControllerDelegate methods
sdefresnee65fd872016-12-19 13:38:133085
3086- (CGFloat)headerOffset {
3087 if (IsIPadIdiom())
3088 return StatusBarHeight();
3089 return 0.0;
3090}
3091
stkhapugin952ecef2017-04-11 12:11:453092- (NSArray<HeaderDefinition*>*)headerViews {
3093 NSMutableArray<HeaderDefinition*>* results = [[NSMutableArray alloc] init];
sdefresnee65fd872016-12-19 13:38:133094 if (![self isViewLoaded])
3095 return results;
3096
3097 if (!IsIPadIdiom()) {
sczs42f7f7482017-11-08 01:13:273098 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453099 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273100 definitionWithView:_toolbarCoordinator
3101 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453102 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243103 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453104 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133105 }
3106 } else {
edchinf5150c682017-09-18 02:50:033107 if (self.tabStripView) {
3108 [results addObject:[HeaderDefinition definitionWithView:self.tabStripView
3109 headerBehaviour:Hideable
3110 heightAdjustment:0.0
3111 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133112 }
sczs42f7f7482017-11-08 01:13:273113 if (_toolbarCoordinator.toolbarViewController.view) {
stkhapugin952ecef2017-04-11 12:11:453114 [results addObject:[HeaderDefinition
sczs42f7f7482017-11-08 01:13:273115 definitionWithView:_toolbarCoordinator
3116 .toolbarViewController.view
stkhapugin952ecef2017-04-11 12:11:453117 headerBehaviour:Hideable
sczs8c837782017-10-03 02:57:243118 heightAdjustment:0.0
stkhapugin952ecef2017-04-11 12:11:453119 inset:0.0]];
sdefresnee65fd872016-12-19 13:38:133120 }
3121 if ([_findBarController view]) {
stkhapugin952ecef2017-04-11 12:11:453122 [results addObject:[HeaderDefinition
3123 definitionWithView:[_findBarController view]
3124 headerBehaviour:Overlap
3125 heightAdjustment:0.0
3126 inset:kIPadFindBarOverlap]];
sdefresnee65fd872016-12-19 13:38:133127 }
3128 }
stkhapugin952ecef2017-04-11 12:11:453129 return [results copy];
sdefresnee65fd872016-12-19 13:38:133130}
3131
3132- (UIView*)footerView {
3133 return _voiceSearchBar;
3134}
3135
3136- (CGFloat)headerHeight {
3137 return [self headerHeightForTab:[_model currentTab]];
3138}
3139
3140- (CGFloat)headerHeightForTab:(Tab*)tab {
3141 id nativeController = [self nativeControllerForTab:tab];
3142 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)] &&
3143 [nativeController respondsToSelector:@selector(toolbarHeight)] &&
3144 [nativeController toolbarHeight] > 0.0 && !IsIPadIdiom()) {
3145 // On iPhone, don't add any header height for ToolbarOwner native
3146 // controllers when they're displaying their own toolbar.
3147 return 0;
3148 }
3149
stkhapugin952ecef2017-04-11 12:11:453150 NSArray<HeaderDefinition*>* views = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133151
3152 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453153 for (HeaderDefinition* header in views) {
sdefresnee65fd872016-12-19 13:38:133154 if (header.view && header.behaviour == Hideable) {
3155 height += CGRectGetHeight([header.view frame]) -
3156 header.heightAdjustement - header.inset;
3157 }
3158 }
3159
3160 return height - StatusBarHeight();
3161}
3162
3163- (BOOL)isTabWithIDCurrent:(NSString*)sessionID {
sdefresneb7309482017-01-23 17:14:193164 return self.visible && [sessionID isEqualToString:[_model currentTab].tabId];
sdefresnee65fd872016-12-19 13:38:133165}
3166
3167- (CGFloat)currentHeaderOffset {
stkhapugin952ecef2017-04-11 12:11:453168 NSArray<HeaderDefinition*>* headers = [self headerViews];
3169 if (!headers.count)
sdefresnee65fd872016-12-19 13:38:133170 return 0.0;
3171
3172 // Prerender tab does not have a toolbar, return |headerHeight| as promised by
3173 // API documentation.
Sylvain Defresnef5d2d952017-11-14 11:15:313174 if (_insertedTabWasPrerenderedTab)
sdefresnee65fd872016-12-19 13:38:133175 return [self headerHeight];
3176
3177 UIView* topHeader = headers[0].view;
3178 return -(topHeader.frame.origin.y - [self headerOffset]);
3179}
3180
3181- (CGFloat)footerYForHeaderOffset:(CGFloat)headerOffset {
3182 UIView* footer = [self footerView];
3183 CGFloat headerHeight = [self headerHeight];
3184 if (!footer || headerHeight == 0)
3185 return 0.0;
3186
3187 CGFloat footerHeight = CGRectGetHeight(footer.frame);
3188 CGFloat offset = headerOffset * footerHeight / headerHeight;
3189 return std::ceil(CGRectGetHeight(self.view.bounds) - footerHeight + offset);
3190}
3191
Kurt Horimoto62e97c72017-11-03 19:51:473192- (void)fullScreenController:(LegacyFullscreenController*)controller
sdefresnee65fd872016-12-19 13:38:133193 headerAnimationCompleted:(BOOL)completed
3194 offset:(CGFloat)offset {
3195 if (completed)
justincohen04c27772016-12-21 20:16:593196 [controller setToolbarInsetsForHeaderOffset:offset];
sdefresnee65fd872016-12-19 13:38:133197}
3198
stkhapugin952ecef2017-04-11 12:11:453199- (void)setFramesForHeaders:(NSArray<HeaderDefinition*>*)headers
sdefresnee65fd872016-12-19 13:38:133200 atOffset:(CGFloat)headerOffset {
3201 CGFloat height = [self headerOffset];
stkhapugin952ecef2017-04-11 12:11:453202 for (HeaderDefinition* header in headers) {
sdefresnee65fd872016-12-19 13:38:133203 CGRect frame = [header.view frame];
3204 frame.origin.y = height - headerOffset - header.inset;
3205 [header.view setFrame:frame];
3206 if (header.behaviour != Overlap)
3207 height += CGRectGetHeight(frame);
3208 }
3209}
3210
Kurt Horimoto62e97c72017-11-03 19:51:473211- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133212 drawHeaderViewFromOffset:(CGFloat)headerOffset
3213 animate:(BOOL)animate {
3214 if ([_sideSwipeController inSwipe])
3215 return;
3216
3217 CGRect footerFrame = CGRectZero;
3218 UIView* footer = nil;
3219 // Only animate the voice search bar if the tab is a voice search results tab.
3220 if ([_model currentTab].isVoiceSearchResultsTab) {
3221 footer = [self footerView];
3222 footerFrame = footer.frame;
3223 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3224 }
3225
stkhapugin952ecef2017-04-11 12:11:453226 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133227 void (^block)(void) = ^{
3228 [self setFramesForHeaders:headers atOffset:headerOffset];
3229 footer.frame = footerFrame;
3230 };
3231 void (^completion)(BOOL) = ^(BOOL finished) {
3232 [self fullScreenController:fullScreenController
3233 headerAnimationCompleted:finished
3234 offset:headerOffset];
3235 };
3236 if (animate) {
Kurt Horimoto62e97c72017-11-03 19:51:473237 [UIView
3238 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3239 delay:0.0
3240 options:UIViewAnimationOptionBeginFromCurrentState
3241 animations:block
3242 completion:completion];
sdefresnee65fd872016-12-19 13:38:133243 } else {
3244 block();
3245 completion(YES);
3246 }
3247}
3248
Kurt Horimoto62e97c72017-11-03 19:51:473249- (void)fullScreenController:(LegacyFullscreenController*)fullScreenController
sdefresnee65fd872016-12-19 13:38:133250 drawHeaderViewFromOffset:(CGFloat)headerOffset
3251 onWebViewProxy:(id<CRWWebViewProxy>)webViewProxy
3252 changeTopContentPadding:(BOOL)changeTopContentPadding
3253 scrollingToOffset:(CGFloat)contentOffset {
3254 DCHECK(webViewProxy);
3255 if ([_sideSwipeController inSwipe])
3256 return;
3257
3258 CGRect footerFrame;
3259 UIView* footer = nil;
3260 // Only animate the voice search bar if the tab is a voice search results tab.
3261 if ([_model currentTab].isVoiceSearchResultsTab) {
3262 footer = [self footerView];
3263 footerFrame = footer.frame;
3264 footerFrame.origin.y = [self footerYForHeaderOffset:headerOffset];
3265 }
3266
stkhapugin952ecef2017-04-11 12:11:453267 NSArray<HeaderDefinition*>* headers = [self headerViews];
sdefresnee65fd872016-12-19 13:38:133268 void (^block)(void) = ^{
3269 [self setFramesForHeaders:headers atOffset:headerOffset];
3270 footer.frame = footerFrame;
3271 webViewProxy.scrollViewProxy.contentOffset = CGPointMake(
3272 webViewProxy.scrollViewProxy.contentOffset.x, contentOffset);
3273 if (changeTopContentPadding)
3274 webViewProxy.topContentPadding = contentOffset;
3275 };
3276 void (^completion)(BOOL) = ^(BOOL finished) {
3277 [self fullScreenController:fullScreenController
3278 headerAnimationCompleted:finished
3279 offset:headerOffset];
3280 };
3281
Kurt Horimoto62e97c72017-11-03 19:51:473282 [UIView
3283 animateWithDuration:kLegacyFullscreenControllerToolbarAnimationDuration
3284 delay:0.0
3285 options:UIViewAnimationOptionBeginFromCurrentState
3286 animations:block
3287 completion:completion];
sdefresnee65fd872016-12-19 13:38:133288}
3289
3290#pragma mark - VoiceSearchBarOwner
3291
3292- (id<VoiceSearchBar>)voiceSearchBar {
3293 return _voiceSearchBar;
3294}
3295
3296#pragma mark - Install OverScrollActionController method.
3297- (void)setOverScrollActionControllerToStaticNativeContent:
3298 (StaticHtmlNativeContent*)nativeContent {
Olivier Robin0f801b82017-07-21 09:56:343299 if (!IsIPadIdiom()) {
sdefresnee65fd872016-12-19 13:38:133300 OverscrollActionsController* controller =
stkhapuginf58b10d02017-04-10 13:36:173301 [[OverscrollActionsController alloc]
3302 initWithScrollView:[nativeContent scrollView]];
sdefresnee65fd872016-12-19 13:38:133303 [controller setDelegate:self];
rohitrao922b7111c2017-01-03 14:31:053304 OverscrollStyle style = _isOffTheRecord
3305 ? OverscrollStyle::REGULAR_PAGE_INCOGNITO
3306 : OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO;
sdefresnee65fd872016-12-19 13:38:133307 controller.style = style;
3308 nativeContent.overscrollActionsController = controller;
3309 }
3310}
3311
3312#pragma mark - OverscrollActionsControllerDelegate methods.
3313
3314- (void)overscrollActionsController:(OverscrollActionsController*)controller
rohitrao922b7111c2017-01-03 14:31:053315 didTriggerAction:(OverscrollAction)action {
sdefresnee65fd872016-12-19 13:38:133316 switch (action) {
rohitrao922b7111c2017-01-03 14:31:053317 case OverscrollAction::NEW_TAB:
Mark Cogandfcdea72017-07-18 13:47:383318 [self.dispatcher
3319 openNewTab:[OpenNewTabCommand
3320 commandWithIncognito:self.isOffTheRecord]];
sdefresnee65fd872016-12-19 13:38:133321 break;
rohitrao922b7111c2017-01-03 14:31:053322 case OverscrollAction::CLOSE_TAB:
Mark Cogan6c58ea92017-07-06 13:08:243323 [self.dispatcher closeCurrentTab];
sdefresnee65fd872016-12-19 13:38:133324 break;
liaoyuke563dc4a2017-03-17 18:36:293325 case OverscrollAction::REFRESH: {
liaoyuke563dc4a2017-03-17 18:36:293326 web::WebState* webState = [_model currentTab].webState;
Eugene But083b6c7a2017-10-02 15:49:383327 if (webState) {
3328 if (webState->IsLoading()) {
3329 webState->Stop();
3330 }
liaoyuke563dc4a2017-03-17 18:36:293331 // |check_for_repost| is true because the reload is explicitly initiated
3332 // by the user.
3333 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
3334 true /* check_for_repost */);
Eugene But083b6c7a2017-10-02 15:49:383335 }
sdefresnee65fd872016-12-19 13:38:133336 break;
liaoyuke563dc4a2017-03-17 18:36:293337 }
rohitrao922b7111c2017-01-03 14:31:053338 case OverscrollAction::NONE:
sdefresnee65fd872016-12-19 13:38:133339 NOTREACHED();
3340 break;
3341 }
3342}
3343
3344- (BOOL)shouldAllowOverscrollActions {
3345 return YES;
3346}
3347
3348- (UIView*)headerView {
sczs42f7f7482017-11-08 01:13:273349 return _toolbarCoordinator.toolbarViewController.view;
sdefresnee65fd872016-12-19 13:38:133350}
3351
3352- (UIView*)toolbarSnapshotView {
sczs42f7f7482017-11-08 01:13:273353 return [_toolbarCoordinator.toolbarViewController.view
3354 snapshotViewAfterScreenUpdates:NO];
sdefresnee65fd872016-12-19 13:38:133355}
3356
3357- (CGFloat)overscrollActionsControllerHeaderInset:
3358 (OverscrollActionsController*)controller {
3359 if (controller == [[[self tabModel] currentTab] overscrollActionsController])
3360 return [self headerHeight];
3361 else
3362 return 0;
3363}
3364
3365- (CGFloat)overscrollHeaderHeight {
3366 return [self headerHeight] + StatusBarHeight();
3367}
3368
3369#pragma mark - TabSnapshottingDelegate methods.
3370
3371- (CGRect)snapshotContentAreaForTab:(Tab*)tab {
3372 CGRect pageContentArea = _contentArea.bounds;
3373 if ([_model webUsageEnabled])
3374 pageContentArea = tab.view.bounds;
3375 CGFloat headerHeight = [self headerHeightForTab:tab];
3376 id nativeController = [self nativeControllerForTab:tab];
3377 if ([nativeController respondsToSelector:@selector(toolbarHeight)])
3378 headerHeight += [nativeController toolbarHeight];
3379 UIEdgeInsets contentInsets = UIEdgeInsetsMake(headerHeight, 0.0, 0.0, 0.0);
3380 return UIEdgeInsetsInsetRect(pageContentArea, contentInsets);
3381}
3382
3383#pragma mark - NewTabPageObserver methods.
3384
3385- (void)selectedPanelDidChange {
3386 [self updateToolbar];
3387}
3388
3389#pragma mark - CRWNativeContentProvider methods
3390
3391- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3392 withError:(NSError*)error
3393 isPost:(BOOL)isPost {
3394 ErrorPageContent* errorPageContent =
stkhapuginf58b10d02017-04-10 13:36:173395 [[ErrorPageContent alloc] initWithLoader:self
3396 browserState:self.browserState
3397 url:url
3398 error:error
3399 isPost:isPost
3400 isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:133401 [self setOverScrollActionControllerToStaticNativeContent:errorPageContent];
3402 return errorPageContent;
3403}
3404
3405- (BOOL)hasControllerForURL:(const GURL&)url {
Marti Wong64481ec2017-10-31 03:38:003406 base::StringPiece host = url.host_piece();
olivierrobin5c861c22017-04-07 15:56:453407 if (host == kChromeUIOfflineHost) {
3408 // Only allow offline URL that are fully specified.
3409 return reading_list::IsOfflineURLValid(
3410 url, ReadingListModelFactory::GetForBrowserState(_browserState));
3411 }
sdefresnee65fd872016-12-19 13:38:133412
Justin Cohen8679e852017-08-14 16:35:253413 if (host == kChromeUIBookmarksHost) {
Marti Wong64481ec2017-10-31 03:38:003414 return IsBookmarksHostEnabled();
Justin Cohen8679e852017-08-14 16:35:253415 }
3416
3417 return host == kChromeUINewTabHost;
sdefresnee65fd872016-12-19 13:38:133418}
3419
olivierrobind43eecb2017-01-27 20:35:263420- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
3421 webState:(web::WebState*)webState {
sdefresnee65fd872016-12-19 13:38:133422 DCHECK(url.SchemeIs(kChromeUIScheme));
3423
3424 id<CRWNativeContent> nativeController = nil;
Marti Wong64481ec2017-10-31 03:38:003425 base::StringPiece url_host = url.host_piece();
Justin Cohen49715952017-08-22 14:12:193426 if (url_host == kChromeUINewTabHost ||
Marti Wong64481ec2017-10-31 03:38:003427 (url_host == kChromeUIBookmarksHost && IsBookmarksHostEnabled())) {
Gauthier Ambardd8890452017-09-29 12:07:463428 CGFloat fakeStatusBarHeight = _fakeStatusBarView.frame.size.height;
3429 UIEdgeInsets safeAreaInset = UIEdgeInsetsZero;
3430 if (@available(iOS 11.0, *)) {
3431 safeAreaInset = self.view.safeAreaInsets;
3432 }
3433 safeAreaInset.top = MAX(safeAreaInset.top - fakeStatusBarHeight, 0);
3434
sdefresnee65fd872016-12-19 13:38:133435 NewTabPageController* pageController =
stkhapuginf58b10d02017-04-10 13:36:173436 [[NewTabPageController alloc] initWithUrl:url
3437 loader:self
sczsf1620e52017-10-02 22:54:463438 focuser:_toolbarCoordinator
stkhapuginf58b10d02017-04-10 13:36:173439 ntpObserver:self
3440 browserState:_browserState
3441 colorCache:_dominantColorCache
sczsf1620e52017-10-02 22:54:463442 toolbarDelegate:_toolbarCoordinator
justincohenbc913632017-04-18 14:41:453443 tabModel:_model
justincohen75011c32017-04-28 16:31:393444 parentViewController:self
Gauthier Ambardd8890452017-09-29 12:07:463445 dispatcher:self.dispatcher
3446 safeAreaInset:safeAreaInset];
sdefresnee65fd872016-12-19 13:38:133447 pageController.swipeRecognizerProvider = self.sideSwipeController;
3448
3449 // Panel is always NTP for iPhone.
Gauthier Ambardf520c022017-08-29 07:42:233450 ntp_home::PanelIdentifier panelType = ntp_home::HOME_PANEL;
sdefresnee65fd872016-12-19 13:38:133451
Marti Wong64481ec2017-10-31 03:38:003452 if (IsBookmarksHostEnabled()) {
sdefresnee65fd872016-12-19 13:38:133453 // New Tab Page can have multiple panels. Each panel is addressable
3454 // by a #fragment, e.g. chrome://newtab/#most_visited takes user to
3455 // the Most Visited page, chrome://newtab/#bookmarks takes user to
3456 // the Bookmark Manager, etc.
3457 // The utility functions NewTabPage::IdentifierFromFragment() and
3458 // FragmentFromIdentifier() map an identifier to/from a #fragment.
3459 // If the URL is chrome://bookmarks, pre-select the #bookmarks panel
3460 // without changing the URL since the URL may be chrome://bookmarks/#123.
3461 // If the URL is chrome://newtab/, pre-select the panel based on the
3462 // #fragment.
3463 panelType = url_host == kChromeUIBookmarksHost
Gauthier Ambardf520c022017-08-29 07:42:233464 ? ntp_home::BOOKMARKS_PANEL
sdefresnee65fd872016-12-19 13:38:133465 : NewTabPage::IdentifierFromFragment(url.ref());
3466 }
3467 [pageController selectPanel:panelType];
3468 nativeController = pageController;
olivierrobin5c861c22017-04-07 15:56:453469 } else if (url_host == kChromeUIOfflineHost &&
3470 [self hasControllerForURL:url]) {
sdefresnee65fd872016-12-19 13:38:133471 StaticHtmlNativeContent* staticNativeController =
stkhapuginf58b10d02017-04-10 13:36:173472 [[OfflinePageNativeContent alloc] initWithLoader:self
3473 browserState:_browserState
3474 webState:webState
3475 URL:url];
sdefresnee65fd872016-12-19 13:38:133476 [self setOverScrollActionControllerToStaticNativeContent:
3477 staticNativeController];
3478 nativeController = staticNativeController;
3479 } else if (url_host == kChromeUIExternalFileHost) {
3480 // Return an instance of the |ExternalFileController| only if the file is
3481 // still in the sandbox.
3482 NSString* filePath = [ExternalFileController pathForExternalFileURL:url];
3483 if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
stkhapuginf58b10d02017-04-10 13:36:173484 nativeController =
3485 [[ExternalFileController alloc] initWithURL:url
3486 browserState:_browserState];
sdefresnee65fd872016-12-19 13:38:133487 }
peterlaurens44615d02017-05-23 20:23:093488 } else if (url_host == kChromeUICrashHost) {
3489 // There is no native controller for kChromeUICrashHost, it is instead
3490 // handled as any other renderer crash by the SadTabTabHelper.
3491 // nativeController must be set to nil to prevent defaulting to a
3492 // PageNotAvailableController.
3493 nativeController = nil;
sdefresnee65fd872016-12-19 13:38:133494 } else {
3495 DCHECK(![self hasControllerForURL:url]);
3496 // In any other case the PageNotAvailableController is returned.
stkhapuginf58b10d02017-04-10 13:36:173497 nativeController = [[PageNotAvailableController alloc] initWithUrl:url];
sdefresnee65fd872016-12-19 13:38:133498 }
3499 // If a native controller is vended before its tab is added to the tab model,
3500 // use the temporary key and add it under the new tab's tabId in the
3501 // TabModelObserver callback. This happens:
3502 // - when there is no current tab (occurs when vending the NTP controller for
3503 // the first tab that is opened),
3504 // - when the current tab's url doesn't match |url| (occurs when a native
3505 // controller is opened in a new tab)
3506 // - when the current tab's url matches |url| and there is already a native
3507 // controller of the appropriate type vended to it (occurs when a native
3508 // controller is opened in a new tab from a tab with a matching URL, e.g.
3509 // opening an NTP when an NTP is already displayed in the current tab).
3510 // For normal page loads, history navigations, tab restorations, and crash
3511 // recoveries, the tab will already exist in the tab model and the tabId can
3512 // be used as the native controller key.
3513 // TODO(crbug.com/498568): To reduce complexity here, refactor the flow so
3514 // that native controllers vended here always correspond to the current tab.
3515 Tab* currentTab = [_model currentTab];
Sylvain Defresnee7f2c8a2017-10-17 02:39:193516 if (!currentTab.webState ||
3517 currentTab.webState->GetLastCommittedURL() != url ||
Eugene But56efc322017-08-11 14:03:443518 [currentTab.webController.nativeController
sdefresnee65fd872016-12-19 13:38:133519 isKindOfClass:[nativeController class]]) {
Eugene But56efc322017-08-11 14:03:443520 _temporaryNativeController = nativeController;
sdefresnee65fd872016-12-19 13:38:133521 }
sdefresnee65fd872016-12-19 13:38:133522 return nativeController;
3523}
3524
3525- (id)nativeControllerForTab:(Tab*)tab {
Eugene But56efc322017-08-11 14:03:443526 id nativeController = tab.webController.nativeController;
3527 return nativeController ? nativeController : _temporaryNativeController;
sdefresnee65fd872016-12-19 13:38:133528}
3529
3530#pragma mark - DialogPresenterDelegate methods
3531
3532- (void)dialogPresenter:(DialogPresenter*)presenter
3533 willShowDialogForWebState:(web::WebState*)webState {
3534 for (Tab* iteratedTab in self.tabModel) {
3535 if ([iteratedTab webState] == webState) {
3536 self.tabModel.currentTab = iteratedTab;
3537 DCHECK([[iteratedTab view] isDescendantOfView:self.contentArea]);
3538 break;
3539 }
3540 }
3541}
3542
3543#pragma mark - Context menu methods
3544
3545- (void)searchByImageAtURL:(const GURL&)url
3546 referrer:(const web::Referrer)referrer {
3547 DCHECK(url.is_valid());
stkhapuginc9eee7b2017-04-10 15:49:273548 __weak BrowserViewController* weakSelf = self;
gambardbdc07cc2017-02-03 16:43:113549 const GURL image_source_url = url;
gambard9efce7a2017-02-09 18:53:173550 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3551 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113552 DCHECK(data);
3553 dispatch_async(dispatch_get_main_queue(), ^{
3554 [weakSelf searchByImageData:data atURL:image_source_url];
3555 });
3556 };
3557 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133558 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3559 web::PolicyForNavigation(url, referrer));
3560}
3561
3562- (void)searchByImageData:(NSData*)data atURL:(const GURL&)imageURL {
3563 NSData* imageData = data;
3564 UIImage* image = [UIImage imageWithData:imageData];
3565 // Downsize the image if its area exceeds kSearchByImageMaxImageArea AND
3566 // (either its width exceeds kSearchByImageMaxImageWidth OR its height exceeds
3567 // kSearchByImageMaxImageHeight).
3568 if (image &&
3569 image.size.height * image.size.width > kSearchByImageMaxImageArea &&
3570 (image.size.width > kSearchByImageMaxImageWidth ||
3571 image.size.height > kSearchByImageMaxImageHeight)) {
3572 CGSize newImageSize =
3573 CGSizeMake(kSearchByImageMaxImageWidth, kSearchByImageMaxImageHeight);
3574 image = [image gtm_imageByResizingToSize:newImageSize
3575 preserveAspectRatio:YES
3576 trimToFit:NO];
3577 imageData = UIImageJPEGRepresentation(image, 1.0);
3578 }
3579
3580 char const* bytes = reinterpret_cast<const char*>([imageData bytes]);
3581 std::string byteString(bytes, [imageData length]);
3582
3583 TemplateURLService* templateUrlService =
3584 ios::TemplateURLServiceFactory::GetForBrowserState(_browserState);
jeffschiller8aa7a4e2017-04-23 02:22:103585 const TemplateURL* defaultURL =
3586 templateUrlService->GetDefaultSearchProvider();
sdefresnee65fd872016-12-19 13:38:133587 DCHECK(!defaultURL->image_url().empty());
3588 DCHECK(defaultURL->image_url_ref().IsValid(
3589 templateUrlService->search_terms_data()));
3590 TemplateURLRef::SearchTermsArgs search_args(base::ASCIIToUTF16(""));
3591 search_args.image_url = imageURL;
3592 search_args.image_thumbnail_content = byteString;
3593
3594 // Generate the URL and populate |post_content| with the content type and
3595 // HTTP body for the request.
3596 TemplateURLRef::PostContent post_content;
3597 GURL result(defaultURL->image_url_ref().ReplaceSearchTerms(
3598 search_args, templateUrlService->search_terms_data(), &post_content));
3599 [self addSelectedTabWithURL:result
3600 postData:&post_content
3601 transition:ui::PAGE_TRANSITION_TYPED];
3602}
3603
3604- (void)saveImageAtURL:(const GURL&)url
3605 referrer:(const web::Referrer&)referrer {
3606 DCHECK(url.is_valid());
3607
gambard9efce7a2017-02-09 18:53:173608 image_fetcher::IOSImageDataFetcherCallback callback = ^(
3609 NSData* data, const image_fetcher::RequestMetadata& metadata) {
gambardbdc07cc2017-02-03 16:43:113610 DCHECK(data);
sdefresnee65fd872016-12-19 13:38:133611
gambardbbf85c42017-06-29 11:15:343612 if ([data length] == 0) {
3613 [self displayPrivacyErrorAlertOnMainQueue:
3614 l10n_util::GetNSString(
3615 IDS_IOS_SAVE_IMAGE_NO_INTERNET_CONNECTION)];
3616 return;
3617 }
3618
gambard9efce7a2017-02-09 18:53:173619 base::FilePath::StringType extension;
3620
3621 bool extensionSuccess =
3622 net::GetPreferredExtensionForMimeType(metadata.mime_type, &extension);
3623 if (!extensionSuccess || extension.length() == 0) {
3624 extension = "png";
3625 }
3626
3627 NSString* fileExtension =
3628 [@"." stringByAppendingString:base::SysUTF8ToNSString(extension)];
3629 [self managePermissionAndSaveImage:data withFileExtension:fileExtension];
gambardbdc07cc2017-02-03 16:43:113630 };
3631 _imageFetcher->FetchImageDataWebpDecoded(
sdefresnee65fd872016-12-19 13:38:133632 url, callback, web::ReferrerHeaderValueForNavigation(url, referrer),
3633 web::PolicyForNavigation(url, referrer));
3634}
3635
gambard9efce7a2017-02-09 18:53:173636- (void)managePermissionAndSaveImage:(NSData*)data
3637 withFileExtension:(NSString*)fileExtension {
sdefresnee65fd872016-12-19 13:38:133638 switch ([PHPhotoLibrary authorizationStatus]) {
3639 // User was never asked for permission to access photos.
stkhapuginf58b10d02017-04-10 13:36:173640 case PHAuthorizationStatusNotDetermined: {
sdefresnee65fd872016-12-19 13:38:133641 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
3642 // Call -saveImage again to check if chrome needs to display an error or
3643 // saves the image.
3644 if (status != PHAuthorizationStatusNotDetermined)
gambard9efce7a2017-02-09 18:53:173645 [self managePermissionAndSaveImage:data
3646 withFileExtension:fileExtension];
sdefresnee65fd872016-12-19 13:38:133647 }];
3648 break;
stkhapuginf58b10d02017-04-10 13:36:173649 }
sdefresnee65fd872016-12-19 13:38:133650
3651 // The application doesn't have permission to access photo and the user
3652 // cannot grant it.
3653 case PHAuthorizationStatusRestricted:
3654 [self displayPrivacyErrorAlertOnMainQueue:
3655 l10n_util::GetNSString(
3656 IDS_IOS_SAVE_IMAGE_RESTRICTED_PRIVACY_ALERT_MESSAGE)];
3657 break;
3658
3659 // The application doesn't have permission to access photo and the user
3660 // can grant it.
3661 case PHAuthorizationStatusDenied:
3662 [self displayImageErrorAlertWithSettingsOnMainQueue];
3663 break;
3664
3665 // The application has permission to access the photos.
Sylvain Defresnefd3ecf22017-07-12 18:47:243666 default:
3667 __weak BrowserViewController* weakSelf = self;
3668 [self saveImage:data
3669 withFileExtension:fileExtension
3670 completion:^(BOOL success, NSError* error) {
3671 [weakSelf finishSavingImageWithError:error];
3672 }];
sdefresnee65fd872016-12-19 13:38:133673 break;
sdefresnee65fd872016-12-19 13:38:133674 }
3675}
3676
Sylvain Defresnefd3ecf22017-07-12 18:47:243677- (void)saveImage:(NSData*)data
3678 withFileExtension:(NSString*)fileExtension
3679 completion:(void (^)(BOOL, NSError*))completion {
3680 base::PostTaskWithTraits(
3681 FROM_HERE,
3682 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3683 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3684 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373685 base::AssertBlockingAllowed();
sdefresnee65fd872016-12-19 13:38:133686
Sylvain Defresnefd3ecf22017-07-12 18:47:243687 NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString]
3688 stringByAppendingString:fileExtension];
3689 NSURL* fileURL = [NSURL
3690 fileURLWithPath:[NSTemporaryDirectory()
3691 stringByAppendingPathComponent:fileName]];
3692 NSError* error = nil;
3693 [data writeToURL:fileURL options:NSDataWritingAtomic error:&error];
3694 if (error) {
3695 if (completion)
3696 completion(NO, error);
3697 return;
3698 }
sdefresnee65fd872016-12-19 13:38:133699
Sylvain Defresnefd3ecf22017-07-12 18:47:243700 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
3701 [PHAssetChangeRequest
3702 creationRequestForAssetFromImageAtFileURL:fileURL];
3703 }
3704 completionHandler:^(BOOL success, NSError* error) {
3705 base::PostTaskWithTraits(
3706 FROM_HERE,
3707 {base::MayBlock(), base::TaskPriority::BACKGROUND,
3708 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
3709 base::BindBlockArc(^{
Francois Doray66bdfd82017-10-20 13:50:373710 base::AssertBlockingAllowed();
Sylvain Defresnefd3ecf22017-07-12 18:47:243711 if (completion)
3712 completion(success, error);
sdefresnee65fd872016-12-19 13:38:133713
Sylvain Defresnefd3ecf22017-07-12 18:47:243714 // Cleanup the temporary file.
3715 NSError* deleteFileError = nil;
3716 [[NSFileManager defaultManager]
3717 removeItemAtURL:fileURL
3718 error:&deleteFileError];
3719 }));
3720 }];
3721 }));
sdefresnee65fd872016-12-19 13:38:133722}
3723
3724- (void)displayImageErrorAlertWithSettingsOnMainQueue {
3725 NSURL* settingURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
3726 BOOL canGoToSetting =
3727 [[UIApplication sharedApplication] canOpenURL:settingURL];
3728 if (canGoToSetting) {
3729 dispatch_async(dispatch_get_main_queue(), ^{
3730 [self displayImageErrorAlertWithSettings:settingURL];
3731 });
3732 } else {
3733 [self displayPrivacyErrorAlertOnMainQueue:
3734 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE)];
3735 }
3736}
3737
3738- (void)displayImageErrorAlertWithSettings:(NSURL*)settingURL {
3739 // Dismiss current alert.
3740 [_alertCoordinator stop];
3741
3742 NSString* title =
3743 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3744 NSString* message = l10n_util::GetNSString(
3745 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_MESSAGE_GO_TO_SETTINGS);
3746
stkhapuginc9eee7b2017-04-10 15:49:273747 _alertCoordinator =
3748 [[AlertCoordinator alloc] initWithBaseViewController:self
3749 title:title
3750 message:message];
sdefresnee65fd872016-12-19 13:38:133751
3752 [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
3753 action:nil
3754 style:UIAlertActionStyleCancel];
3755
3756 [_alertCoordinator
3757 addItemWithTitle:l10n_util::GetNSString(
3758 IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_GO_TO_SETTINGS)
3759 action:^{
3760 OpenUrlWithCompletionHandler(settingURL, nil);
3761 }
3762 style:UIAlertActionStyleDefault];
3763
3764 [_alertCoordinator start];
3765}
3766
3767- (void)displayPrivacyErrorAlertOnMainQueue:(NSString*)errorContent {
3768 dispatch_async(dispatch_get_main_queue(), ^{
3769 NSString* title =
3770 l10n_util::GetNSString(IDS_IOS_SAVE_IMAGE_PRIVACY_ALERT_TITLE);
3771 [self showErrorAlertWithStringTitle:title message:errorContent];
3772 });
3773}
3774
3775// This callback is triggered when the image is effectively saved onto the photo
3776// album, or if the save failed for some reason.
3777- (void)finishSavingImageWithError:(NSError*)error {
3778 // Was there an error?
3779 if (error) {
3780 // Saving photo failed even though user has granted access to Photos.
3781 // Display the error information from the NSError object for user.
3782 NSString* errorMessage = [NSString
3783 stringWithFormat:@"%@ (%@ %" PRIdNS ")", [error localizedDescription],
3784 [error domain], [error code]];
3785 // This code may be execute outside of the main thread. Make sure to display
3786 // the error on the main thread.
3787 [self displayPrivacyErrorAlertOnMainQueue:errorMessage];
3788 } else {
3789 // TODO(noyau): Ideally I'd like to show an infobar with a link to switch to
3790 // the photo application. The current behaviour is to create the photo there
3791 // but not providing any link to it is suboptimal. That's what Safari is
3792 // doing, and what the PM want, but it doesn't make it right.
3793 }
3794}
3795
sdefresnee65fd872016-12-19 13:38:133796#pragma mark - Showing popups
3797
sdefresnee65fd872016-12-19 13:38:133798- (void)addToReadingListURL:(const GURL&)URL title:(NSString*)title {
sdefresnee65fd872016-12-19 13:38:133799 base::RecordAction(UserMetricsAction("MobileReadingListAdd"));
3800
3801 ReadingListModel* readingModel =
3802 ReadingListModelFactory::GetForBrowserState(_browserState);
jife0e60112017-01-16 13:20:013803 readingModel->AddEntry(URL, base::SysNSStringToUTF8(title),
3804 reading_list::ADDED_VIA_CURRENT_APP);
sdefresnee65fd872016-12-19 13:38:133805
pinkerton07e27842017-03-02 15:29:023806 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
edchineeb4d422017-10-02 17:39:363807 [self showSnackbar:l10n_util::GetNSString(
3808 IDS_IOS_READING_LIST_SNACKBAR_MESSAGE)];
sdefresnee65fd872016-12-19 13:38:133809}
3810
3811#pragma mark - Keyboard commands management
3812
3813- (BOOL)shouldRegisterKeyboardCommands {
3814 if ([self presentedViewController])
3815 return NO;
3816
3817 if (_voiceSearchController && _voiceSearchController->IsVisible())
3818 return NO;
3819
3820 // If there is no first responder, try to make the webview the first
3821 // responder.
3822 if (!GetFirstResponder()) {
Eugene But08be7d02017-10-02 15:49:303823 web::WebState* webState = _model.currentTab.webState;
3824 if (webState)
3825 [webState->GetWebViewProxy() becomeFirstResponder];
sdefresnee65fd872016-12-19 13:38:133826 }
3827
3828 return YES;
3829}
3830
3831- (KeyCommandsProvider*)keyCommandsProvider {
3832 if (!_keyCommandsProvider) {
stkhapuginc9eee7b2017-04-10 15:49:273833 _keyCommandsProvider = [_dependencyFactory newKeyCommandsProvider];
sdefresnee65fd872016-12-19 13:38:133834 }
stkhapuginc9eee7b2017-04-10 15:49:273835 return _keyCommandsProvider;
sdefresnee65fd872016-12-19 13:38:133836}
3837
3838#pragma mark - KeyCommandsPlumbing
3839
3840- (BOOL)isOffTheRecord {
3841 return _isOffTheRecord;
3842}
3843
3844- (NSUInteger)tabsCount {
3845 return [_model count];
3846}
3847
lpromero47ea8862017-01-13 17:51:063848- (BOOL)canGoBack {
3849 return [_model currentTab].canGoBack;
3850}
3851
3852- (BOOL)canGoForward {
3853 return [_model currentTab].canGoForward;
3854}
3855
sdefresnee65fd872016-12-19 13:38:133856- (void)focusTabAtIndex:(NSUInteger)index {
3857 if ([_model count] > index) {
3858 [_model setCurrentTab:[_model tabAtIndex:index]];
3859 }
3860}
3861
3862- (void)focusNextTab {
3863 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3864 NSInteger modelCount = [_model count];
3865 if (currentTabIndex < modelCount - 1) {
3866 Tab* nextTab = [_model tabAtIndex:currentTabIndex + 1];
3867 [_model setCurrentTab:nextTab];
3868 } else {
3869 [_model setCurrentTab:[_model tabAtIndex:0]];
3870 }
3871}
3872
3873- (void)focusPreviousTab {
3874 NSInteger currentTabIndex = [_model indexOfTab:[_model currentTab]];
3875 if (currentTabIndex > 0) {
3876 Tab* previousTab = [_model tabAtIndex:currentTabIndex - 1];
3877 [_model setCurrentTab:previousTab];
3878 } else {
3879 Tab* lastTab = [_model tabAtIndex:[_model count] - 1];
3880 [_model setCurrentTab:lastTab];
3881 }
3882}
3883
3884- (void)reopenClosedTab {
3885 sessions::TabRestoreService* const tabRestoreService =
3886 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
3887 if (!tabRestoreService || tabRestoreService->entries().empty())
3888 return;
3889
3890 const std::unique_ptr<sessions::TabRestoreService::Entry>& entry =
3891 tabRestoreService->entries().front();
3892 // Only handle the TAB type.
3893 if (entry->type != sessions::TabRestoreService::TAB)
3894 return;
3895
Mark Cogandfcdea72017-07-18 13:47:383896 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
sdefresnee65fd872016-12-19 13:38:133897 TabRestoreServiceDelegateImplIOS* const delegate =
3898 TabRestoreServiceDelegateImplIOSFactory::GetForBrowserState(
3899 _browserState);
3900 tabRestoreService->RestoreEntryById(delegate, entry->id,
3901 WindowOpenDisposition::CURRENT_TAB);
3902}
3903
3904- (void)focusOmnibox {
sczsf1620e52017-10-02 22:54:463905 [_toolbarCoordinator focusOmnibox];
sdefresnee65fd872016-12-19 13:38:133906}
3907
3908#pragma mark - UIResponder
3909
3910- (NSArray*)keyCommands {
3911 if (![self shouldRegisterKeyboardCommands]) {
3912 return nil;
3913 }
3914 return [self.keyCommandsProvider
3915 keyCommandsForConsumer:self
edchin8e4cfe032017-10-25 13:25:543916 baseViewController:self
Mark Cogan6c58ea92017-07-06 13:08:243917 dispatcher:self.dispatcher
sdefresnee65fd872016-12-19 13:38:133918 editingText:![self isFirstResponder]];
3919}
3920
3921#pragma mark -
3922
3923// Induce an intentional crash in the browser process.
3924- (void)induceBrowserCrash {
3925 CHECK(false);
3926 // Call another function, so that the above CHECK can't be tail-call
3927 // optimized. This ensures that this method's name will show up in the stack
3928 // for easier identification.
3929 CHECK(true);
3930}
3931
3932- (void)loadURL:(const GURL&)url
3933 referrer:(const web::Referrer&)referrer
3934 transition:(ui::PageTransition)transition
3935 rendererInitiated:(BOOL)rendererInitiated {
3936 [[OmniboxGeolocationController sharedInstance]
3937 locationBarDidSubmitURL:url
3938 transition:transition
3939 browserState:_browserState];
3940
3941 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:YES];
3942 if (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) {
3943 new_tab_page_uma::RecordActionFromOmnibox(_browserState, url, transition);
3944 }
3945
3946 // NOTE: This check for the Crash Host URL is here to avoid the URL from
dbeam25b548f2017-05-05 18:05:243947 // ending up in the history causing the app to crash at every subsequent
sdefresnee65fd872016-12-19 13:38:133948 // restart.
3949 if (url.host() == kChromeUIBrowserCrashHost) {
3950 [self induceBrowserCrash];
3951 // In debug the app can continue working even after the CHECK. Adding a
3952 // return avoids the crash url to be added to the history.
3953 return;
3954 }
3955
Danyao Wang85389a82017-10-25 18:56:273956 bool typed_or_generated_transition =
3957 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
3958 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED);
3959
Rohit Rao44f204302017-08-10 14:49:543960 PrerenderService* prerenderService =
3961 PrerenderServiceFactory::GetForBrowserState(self.browserState);
3962 if (prerenderService && prerenderService->HasPrerenderForUrl(url)) {
sdefresne2c600c52017-04-04 16:49:593963 std::unique_ptr<web::WebState> newWebState =
Rohit Rao44f204302017-08-10 14:49:543964 prerenderService->ReleasePrerenderContents();
sdefresne2c600c52017-04-04 16:49:593965 DCHECK(newWebState);
3966
sdefresnee65fd872016-12-19 13:38:133967 Tab* oldTab = [_model currentTab];
sdefresne2c600c52017-04-04 16:49:593968 Tab* newTab = LegacyTabHelper::GetTabForWebState(newWebState.get());
sdefresnee65fd872016-12-19 13:38:133969 DCHECK(oldTab);
3970 DCHECK(newTab);
sdefresne2c600c52017-04-04 16:49:593971
kkhorimotod804c5732017-03-15 23:44:523972 bool canPruneItems =
3973 [newTab navigationManager]->CanPruneAllButLastCommittedItem();
sdefresne2c600c52017-04-04 16:49:593974
kkhorimotod804c5732017-03-15 23:44:523975 if (oldTab && newTab && canPruneItems) {
kkhorimotod804c5732017-03-15 23:44:523976 [newTab navigationManager]->CopyStateFromAndPrune(
3977 [oldTab navigationManager]);
sdefresne2c600c52017-04-04 16:49:593978
Sylvain Defresnef5d2d952017-11-14 11:15:313979 // Set _insertedTabWasPrerenderedTab to YES while the Tab is inserted
3980 // so that the correct toolbar height is used and animation are played.
3981 _insertedTabWasPrerenderedTab = YES;
sdefresne2c600c52017-04-04 16:49:593982 [_model webStateList]->ReplaceWebStateAt([_model indexOfTab:oldTab],
3983 std::move(newWebState));
Sylvain Defresnef5d2d952017-11-14 11:15:313984 _insertedTabWasPrerenderedTab = NO;
sdefresnee65fd872016-12-19 13:38:133985
Danyao Wang85389a82017-10-25 18:56:273986 if (typed_or_generated_transition) {
3987 LoadTimingTabHelper::FromWebState(newTab.webState)
3988 ->DidPromotePrerenderTab();
3989 }
sdefresnee65fd872016-12-19 13:38:133990
sdefresne2f7781c2017-03-02 19:12:463991 [self tabLoadComplete:newTab withSuccess:newTab.loadFinished];
sdefresnee65fd872016-12-19 13:38:133992 return;
3993 }
3994 }
3995
3996 GURL urlToLoad = url;
Rohit Rao44f204302017-08-10 14:49:543997 if (prerenderService) {
3998 prerenderService->CancelPrerender();
sdefresnee65fd872016-12-19 13:38:133999 }
4000
sdefresnee65fd872016-12-19 13:38:134001 // Some URLs are not allowed while in incognito. If we are in incognito and
4002 // load a disallowed URL, instead create a new tab not in the incognito state.
4003 if (_isOffTheRecord && !IsURLAllowedInIncognito(url)) {
4004 [self webPageOrderedOpen:url
4005 referrer:web::Referrer()
sdefresnee65fd872016-12-19 13:38:134006 inIncognito:NO
4007 inBackground:NO
4008 appendTo:kCurrentTab];
4009 return;
4010 }
4011
Danyao Wang85389a82017-10-25 18:56:274012 if (typed_or_generated_transition) {
4013 LoadTimingTabHelper::FromWebState([_model currentTab].webState)
4014 ->DidInitiatePageLoad();
4015 }
4016
mrefaata84d5a02017-06-08 17:13:294017 // If this is a reload initiated from the omnibox.
4018 // TODO(crbug.com/730192): Add DCHECK to verify that whenever urlToLood is the
4019 // same as the old url, the transition type is ui::PAGE_TRANSITION_RELOAD.
4020 if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) {
4021 [[_model currentTab] navigationManager]->Reload(
4022 web::ReloadType::NORMAL, true /* check_for_repost */);
4023 return;
4024 }
4025
sdefresnee65fd872016-12-19 13:38:134026 web::NavigationManager::WebLoadParams params(urlToLoad);
4027 params.referrer = referrer;
4028 params.transition_type = transition;
4029 params.is_renderer_initiated = rendererInitiated;
Kurt Horimoto208a1e82017-10-27 01:41:104030 Tab* currentTab = [_model currentTab];
4031 DCHECK(currentTab);
4032 BOOL wasVoiceSearchTab = currentTab.isVoiceSearchResultsTab;
4033 currentTab.navigationManager->LoadURLWithParams(params);
4034 // When a Tab becomes a voice search Tab, the voice search bar doesn't need
4035 // to be animated on screen because the transition animator will handle the
4036 // animations. When a Tab stops being a voice search Tab, the voice search
4037 // bar should be animated away.
4038 if (currentTab.isVoiceSearchResultsTab != wasVoiceSearchTab)
4039 [self updateVoiceSearchBarVisibilityAnimated:wasVoiceSearchTab];
sdefresnee65fd872016-12-19 13:38:134040}
4041
4042- (void)loadJavaScriptFromLocationBar:(NSString*)script {
Rohit Rao44f204302017-08-10 14:49:544043 PrerenderService* prerenderService =
4044 PrerenderServiceFactory::GetForBrowserState(self.browserState);
4045 if (prerenderService) {
4046 prerenderService->CancelPrerender();
4047 }
sdefresnee65fd872016-12-19 13:38:134048 DCHECK([_model currentTab]);
Eugene But897b28a2017-08-01 17:23:184049 if ([self currentWebState])
4050 [self currentWebState]->ExecuteUserJavaScript(script);
sdefresnee65fd872016-12-19 13:38:134051}
4052
4053- (web::WebState*)currentWebState {
4054 return [[_model currentTab] webState];
4055}
4056
sdefresnee65fd872016-12-19 13:38:134057// Load a new URL on a new page/tab.
4058- (void)webPageOrderedOpen:(const GURL&)URL
4059 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134060 inBackground:(BOOL)inBackground
4061 appendTo:(OpenPosition)appendTo {
4062 Tab* adjacentTab = nil;
4063 if (appendTo == kCurrentTab)
4064 adjacentTab = [_model currentTab];
sdefresnea6395912017-03-01 01:14:354065 [_model insertTabWithURL:URL
4066 referrer:referrer
4067 transition:ui::PAGE_TRANSITION_LINK
4068 opener:adjacentTab
4069 openedByDOM:NO
4070 atIndex:TabModelConstants::kTabPositionAutomatically
4071 inBackground:inBackground];
sdefresnee65fd872016-12-19 13:38:134072}
4073
4074- (void)webPageOrderedOpen:(const GURL&)url
4075 referrer:(const web::Referrer&)referrer
sdefresnee65fd872016-12-19 13:38:134076 inIncognito:(BOOL)inIncognito
4077 inBackground:(BOOL)inBackground
4078 appendTo:(OpenPosition)appendTo {
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004079 // Send either the "New Tab Opened" or "New Incognito Tab" opened to the
Tommy Nyquistc1d6dea12017-07-26 20:37:234080 // feature_engagement::Tracker based on |inIncognito|.
4081 feature_engagement::NotifyNewTabEvent(_model.browserState, inIncognito);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004082
sdefresnee65fd872016-12-19 13:38:134083 if (inIncognito == _isOffTheRecord) {
4084 [self webPageOrderedOpen:url
4085 referrer:referrer
sdefresnee65fd872016-12-19 13:38:134086 inBackground:inBackground
4087 appendTo:appendTo];
4088 return;
4089 }
4090 // When sending an open command that switches modes, ensure the tab
4091 // ends up appended to the end of the model, not just next to what is
4092 // currently selected in the other mode. This is done with the |append|
4093 // parameter.
stkhapuginc9eee7b2017-04-10 15:49:274094 OpenUrlCommand* command = [[OpenUrlCommand alloc]
sdefresnee65fd872016-12-19 13:38:134095 initWithURL:url
4096 referrer:web::Referrer() // Strip referrer when switching modes.
sdefresnee65fd872016-12-19 13:38:134097 inIncognito:inIncognito
4098 inBackground:inBackground
stkhapuginc9eee7b2017-04-10 15:49:274099 appendTo:kLastTab];
sczs02ad28e2017-08-31 11:22:154100 [self.dispatcher openURL:command];
sdefresnee65fd872016-12-19 13:38:134101}
4102
4103- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab {
Sylvain Defresnef2e00d9b2017-08-24 10:54:054104 WebStateList* webStateList = [_model webStateList];
4105 webStateList->ReplaceWebStateAt(
4106 webStateList->active_index(),
4107 session_util::CreateWebStateWithNavigationEntries(
4108 [_model browserState], sessionTab->current_navigation_index,
4109 sessionTab->navigations));
sdefresnee65fd872016-12-19 13:38:134110}
4111
4112- (void)openJavascript:(NSString*)javascript {
rohitrao746baec2017-01-20 16:20:434113 DCHECK(javascript);
4114 javascript = [javascript stringByRemovingPercentEncoding];
4115 web::WebState* webState = [[_model currentTab] webState];
4116 if (webState) {
4117 webState->ExecuteJavaScript(base::SysNSStringToUTF16(javascript));
4118 }
sdefresnee65fd872016-12-19 13:38:134119}
4120
4121#pragma mark - WebToolbarDelegate methods
4122
Gauthier Ambard45963ce22017-11-17 15:49:114123- (void)locationBarDidBecomeFirstResponder {
sdefresnee65fd872016-12-19 13:38:134124 if (_locationBarHasFocus)
4125 return; // TODO(crbug.com/244366): This should not be necessary.
4126 _locationBarHasFocus = YES;
4127 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524128 postNotificationName:kLocationBarBecomesFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134129 object:nil];
4130 [_sideSwipeController setEnabled:NO];
4131 if ([[_model currentTab].webController wantsKeyboardShield]) {
4132 [[self view] insertSubview:_typingShield aboveSubview:_contentArea];
4133 [_typingShield setAlpha:0.0];
4134 [_typingShield setHidden:NO];
4135 [UIView animateWithDuration:0.3
4136 animations:^{
4137 [_typingShield setAlpha:1.0];
4138 }];
4139 }
4140 [[OmniboxGeolocationController sharedInstance]
4141 locationBarDidBecomeFirstResponder:_browserState];
4142}
4143
Gauthier Ambard45963ce22017-11-17 15:49:114144- (void)locationBarDidResignFirstResponder {
sdefresnee65fd872016-12-19 13:38:134145 if (!_locationBarHasFocus)
4146 return; // TODO(crbug.com/244366): This should not be necessary.
4147 _locationBarHasFocus = NO;
4148 [_sideSwipeController setEnabled:YES];
4149 [[NSNotificationCenter defaultCenter]
Sylvain Defresneed8c0db2017-08-31 16:29:524150 postNotificationName:kLocationBarResignsFirstResponderNotification
sdefresnee65fd872016-12-19 13:38:134151 object:nil];
4152 [UIView animateWithDuration:0.3
4153 animations:^{
4154 [_typingShield setAlpha:0.0];
4155 }
4156 completion:^(BOOL finished) {
4157 // This can happen if one quickly resigns the omnibox and then taps
4158 // on the omnibox again during this animation. If the animation is
4159 // interrupted and the toolbar controller is first responder, it's safe
4160 // to assume the |_typingShield| shouldn't be hidden here.
sczsf1620e52017-10-02 22:54:464161 if (!finished && [_toolbarCoordinator isOmniboxFirstResponder])
sdefresnee65fd872016-12-19 13:38:134162 return;
4163 [_typingShield setHidden:YES];
4164 }];
4165 [[OmniboxGeolocationController sharedInstance]
4166 locationBarDidResignFirstResponder:_browserState];
4167
4168 // If a load was cancelled by an omnibox edit, but nothing is loading when
4169 // editing ends (i.e., editing was cancelled), restart the cancelled load.
4170 if (_locationBarEditCancelledLoad) {
4171 _locationBarEditCancelledLoad = NO;
liaoyuke563dc4a2017-03-17 18:36:294172
4173 web::WebState* webState = [_model currentTab].webState;
4174 if (!_toolbarModelIOS->IsLoading() && webState)
4175 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4176 false /* check_for_repost */);
sdefresnee65fd872016-12-19 13:38:134177 }
4178}
4179
Gauthier Ambard45963ce22017-11-17 15:49:114180- (void)locationBarBeganEdit {
sdefresnee65fd872016-12-19 13:38:134181 // On handsets, if a page is currently loading it should be stopped.
4182 if (!IsIPadIdiom() && _toolbarModelIOS->IsLoading()) {
Mark Coganb9aac6432017-07-07 13:26:354183 [self.dispatcher stopLoading];
sdefresnee65fd872016-12-19 13:38:134184 _locationBarEditCancelledLoad = YES;
4185 }
4186}
4187
sdefresnee65fd872016-12-19 13:38:134188- (ToolbarModelIOS*)toolbarModelIOS {
4189 return _toolbarModelIOS.get();
4190}
4191
sdefresnee65fd872016-12-19 13:38:134192- (void)willUpdateToolbarSnapshot {
4193 [[_model currentTab].overscrollActionsController clear];
4194}
4195
4196- (CardView*)addCardViewInFullscreen:(BOOL)fullScreen {
4197 CGRect frame = [_contentArea frame];
4198 if (!fullScreen) {
4199 // Changing the origin here is unnecessary, it's set in page_animation_util.
4200 frame.size.height -= [self headerHeight];
4201 }
4202
4203 CGFloat shortAxis = frame.size.width;
4204 CGFloat shortInset = kCardImageInsets.left + kCardImageInsets.right;
Sylvain Defresneed8c0db2017-08-31 16:29:524205 shortAxis -= shortInset + 2 * page_animation_util::kCardMargin;
sdefresnee65fd872016-12-19 13:38:134206 CGFloat aspectRatio = frame.size.height / frame.size.width;
4207 CGFloat longAxis = std::floor(aspectRatio * shortAxis);
4208 CGFloat longInset = kCardImageInsets.top + kCardImageInsets.bottom;
4209 CGSize cardSize = CGSizeMake(shortAxis + shortInset, longAxis + longInset);
4210 CGRect cardFrame = {frame.origin, cardSize};
4211
4212 CardView* card =
stkhapuginf58b10d02017-04-10 13:36:174213 [[CardView alloc] initWithFrame:cardFrame isIncognito:_isOffTheRecord];
sdefresnee65fd872016-12-19 13:38:134214 card.closeButtonSide = IsPortrait() ? CardCloseButtonSide::TRAILING
4215 : CardCloseButtonSide::LEADING;
4216 [_contentArea addSubview:card];
4217 return card;
4218}
4219
Mark Cogan6ebbde02017-07-07 12:50:134220#pragma mark - BrowserCommands
4221
4222- (void)goBack {
4223 [[_model currentTab] goBack];
4224}
4225
4226- (void)goForward {
4227 [[_model currentTab] goForward];
4228}
4229
Mark Coganb9aac6432017-07-07 13:26:354230- (void)stopLoading {
4231 [_model currentTab].webState->Stop();
4232}
4233
4234- (void)reload {
4235 web::WebState* webState = [_model currentTab].webState;
4236 if (webState) {
4237 // |check_for_repost| is true because the reload is explicitly initiated
4238 // by the user.
4239 webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
4240 true /* check_for_repost */);
4241 }
4242}
4243
Mark Cogan8e791022017-07-10 09:55:354244- (void)bookmarkPage {
4245 [self initializeBookmarkInteractionController];
4246 [_bookmarkInteractionController
4247 presentBookmarkForTab:[_model currentTab]
sczs19e8f3d2017-10-03 17:54:064248 currentlyBookmarked:_toolbarModelIOS->IsCurrentTabBookmarkedByUser()];
Mark Cogan8e791022017-07-10 09:55:354249}
4250
Mark Cogan6acee7f2017-07-11 09:01:404251- (void)showToolsMenu {
4252 DCHECK(_browserState);
4253 DCHECK(self.visible || self.dismissingModal);
4254
4255 // Record the time this menu was requested; to be stored in the configuration
4256 // object.
4257 NSDate* showToolsMenuPopupRequestDate = [NSDate date];
4258
4259 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:464260 [_toolbarCoordinator cancelOmniboxEdit];
Mark Cogan6acee7f2017-07-11 09:01:404261 // Dismiss the soft keyboard (if open).
4262 [[_model currentTab].webController dismissKeyboard];
4263 // Dismiss Find in Page focus.
4264 [self updateFindBar:NO shouldFocus:NO];
4265
4266 ToolsMenuConfiguration* configuration =
edchin8e4cfe032017-10-25 13:25:544267 [[ToolsMenuConfiguration alloc] initWithDisplayView:[self view]
4268 baseViewController:self];
Mark Cogan6acee7f2017-07-11 09:01:404269 configuration.requestStartTime =
4270 showToolsMenuPopupRequestDate.timeIntervalSinceReferenceDate;
4271 if ([_model count] == 0)
4272 [configuration setNoOpenedTabs:YES];
4273
4274 if (_isOffTheRecord)
4275 [configuration setInIncognito:YES];
4276
4277 if (!_readingListMenuNotifier) {
4278 _readingListMenuNotifier = [[ReadingListMenuNotifier alloc]
4279 initWithReadingList:ReadingListModelFactory::GetForBrowserState(
4280 _browserState)];
4281 }
Cooper Knaake4f495cf2017-07-27 23:30:034282
4283 feature_engagement::Tracker* engagementTracker =
4284 feature_engagement::TrackerFactory::GetForBrowserState(_browserState);
4285 if (engagementTracker->ShouldTriggerHelpUI(
4286 feature_engagement::kIPHBadgedReadingListFeature)) {
4287 [configuration setShowReadingListNewBadge:YES];
4288 [configuration setEngagementTracker:engagementTracker];
4289 }
Mark Cogan6acee7f2017-07-11 09:01:404290 [configuration setReadingListMenuNotifier:_readingListMenuNotifier];
4291
4292 [configuration setUserAgentType:self.userAgentType];
4293
Helen Yang9175bd52017-08-12 00:28:404294 if (self.incognitoTabTipBubblePresenter.triggerFollowUpAction) {
4295 [configuration setHighlightNewIncognitoTabCell:YES];
4296 [self.incognitoTabTipBubblePresenter setTriggerFollowUpAction:NO];
4297 }
4298
4299 if (self.incognitoTabTipBubblePresenter.isUserEngaged) {
4300 base::RecordAction(UserMetricsAction("NewIncognitoTabTipTargetSelected"));
4301 }
4302
sczsf1620e52017-10-02 22:54:464303 [_toolbarCoordinator showToolsMenuPopupWithConfiguration:configuration];
Mark Cogan6acee7f2017-07-11 09:01:404304
4305 ToolsPopupController* toolsPopupController =
sczsf1620e52017-10-02 22:54:464306 [_toolbarCoordinator toolsPopupController];
Mark Cogan6acee7f2017-07-11 09:01:404307 if ([_model currentTab]) {
4308 BOOL isBookmarked = _toolbarModelIOS->IsCurrentTabBookmarked();
4309 [toolsPopupController setIsCurrentPageBookmarked:isBookmarked];
4310 [toolsPopupController setCanShowFindBar:self.canShowFindBar];
Mark Cogan6acee7f2017-07-11 09:01:404311 [toolsPopupController setCanShowShareMenu:self.canShowShareMenu];
4312
4313 if (!IsIPadIdiom())
4314 [toolsPopupController setIsTabLoading:_toolbarModelIOS->IsLoading()];
4315 }
4316}
4317
Mark Cogandfcdea72017-07-18 13:47:384318- (void)openNewTab:(OpenNewTabCommand*)command {
4319 if (self.isOffTheRecord != command.incognito) {
edchin3ab78ff2017-11-13 19:13:144320 // Must take a snapshot of the tab before we switch the incognito mode
4321 // because the currentTab will change after the switch.
4322 Tab* currentTab = [_model currentTab];
4323 if (currentTab) {
4324 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4325 }
Mark Cogandfcdea72017-07-18 13:47:384326 // Not for this browser state, send it on its way.
4327 [self.dispatcher switchModesAndOpenNewTab:command];
4328 return;
4329 }
4330
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004331 // Either send or don't send the "New Tab Opened" or "Incognito Tab Opened"
Tommy Nyquistc1d6dea12017-07-26 20:37:234332 // events to the feature_engagement::Tracker based on |command.userInitiated|
4333 // and |command.incognito|.
4334 feature_engagement::NotifyNewTabEventForCommand(_browserState, command);
Cooper Knaak9ae6b4f4a2017-07-25 18:56:004335
Mark Cogandfcdea72017-07-18 13:47:384336 NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
4337 BOOL offTheRecord = self.isOffTheRecord;
Olivier Robind508a5632017-07-19 16:29:494338 ProceduralBlock oldForegroundTabWasAddedCompletionBlock =
4339 self.foregroundTabWasAddedCompletionBlock;
Mark Cogandfcdea72017-07-18 13:47:384340 self.foregroundTabWasAddedCompletionBlock = ^{
Olivier Robind508a5632017-07-19 16:29:494341 if (oldForegroundTabWasAddedCompletionBlock) {
4342 oldForegroundTabWasAddedCompletionBlock();
4343 }
Mark Cogandfcdea72017-07-18 13:47:384344 double duration = [NSDate timeIntervalSinceReferenceDate] - startTime;
4345 base::TimeDelta timeDelta = base::TimeDelta::FromSecondsD(duration);
4346 if (offTheRecord) {
4347 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewIncognitoTabPresentationDuration",
4348 timeDelta);
4349 } else {
4350 UMA_HISTOGRAM_TIMES("Toolbar.Menu.NewTabPresentationDuration", timeDelta);
4351 }
4352 };
4353
4354 [self setLastTapPoint:command];
Rohit Rao2e22b8d2017-11-07 19:54:544355 // When the tab switcher presentation experiment is enabled, the new tab can
4356 // be opened before BVC has been made visible onscreen. Test for this case by
4357 // checking if the parent container VC is currently in the process of being
4358 // presented.
4359 DCHECK(self.visible || self.dismissingModal ||
4360 (TabSwitcherPresentsBVCEnabled() &&
4361 self.parentViewController.isBeingPresented));
edchin3ab78ff2017-11-13 19:13:144362
4363 // In most cases, we want to take a snapshot of the current tab before opening
4364 // a new tab. However, if the current tab is not fully visible (did not finish
4365 // |-viewDidAppear:|, then we must not take an empty snapshot, replacing an
4366 // existing snapshot for the tab. This can happen when a new regular tab is
4367 // opened from an incognito tab. A different BVC is displayed, which may not
4368 // have enough time to finish appearing before a snapshot is requested.
Mark Cogandfcdea72017-07-18 13:47:384369 Tab* currentTab = [_model currentTab];
edchin3ab78ff2017-11-13 19:13:144370 if (currentTab && self.viewVisible) {
Mark Cogandfcdea72017-07-18 13:47:384371 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4372 }
4373 [self addSelectedTabWithURL:GURL(kChromeUINewTabURL)
4374 transition:ui::PAGE_TRANSITION_TYPED];
4375}
4376
Mark Cogan123895002017-07-20 12:54:064377- (void)printTab {
4378 Tab* currentTab = [_model currentTab];
4379 // The UI should prevent users from printing non-printable pages. However, a
4380 // redirection to an un-printable page can happen before it is reflected in
4381 // the UI.
4382 if (![currentTab viewForPrinting]) {
4383 TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeError);
edchineeb4d422017-10-02 17:39:364384 [self showSnackbar:l10n_util::GetNSString(IDS_IOS_CANNOT_PRINT_PAGE_ERROR)];
Mark Cogan123895002017-07-20 12:54:064385 return;
4386 }
4387 DCHECK(_browserState);
4388 if (!_printController) {
4389 _printController = [[PrintController alloc]
4390 initWithContextGetter:_browserState->GetRequestContext()];
4391 }
4392 [_printController printView:[currentTab viewForPrinting]
4393 withTitle:[currentTab title]
4394 viewController:self];
4395}
4396
Mark Coganfa25b052017-07-20 17:31:034397- (void)addToReadingList:(ReadingListAddCommand*)command {
4398 [self addToReadingListURL:[command URL] title:[command title]];
4399}
4400
sczs3a8c8602017-08-01 20:14:084401- (void)showReadingList {
4402 _readingListCoordinator = [[ReadingListCoordinator alloc]
4403 initWithBaseViewController:self
4404 browserState:self.browserState
4405 loader:self];
4406
4407 [_readingListCoordinator start];
4408}
4409
Jean-François Geyelinedef9552017-08-07 09:56:564410- (void)preloadVoiceSearch {
4411 // Preload VoiceSearchController and views and view controllers needed
4412 // for voice search.
4413 [self ensureVoiceSearchControllerCreated];
4414 _voiceSearchController->PrepareToAppear();
4415}
4416
edchinc5720722017-08-14 22:06:314417#if !defined(NDEBUG)
4418- (void)viewSource {
4419 Tab* tab = [_model currentTab];
4420 DCHECK(tab);
4421 CRWWebController* webController = tab.webController;
4422 NSString* script = @"document.documentElement.outerHTML;";
4423 __weak Tab* weakTab = tab;
4424 __weak BrowserViewController* weakSelf = self;
4425 web::JavaScriptResultBlock completionHandlerBlock = ^(id result, NSError*) {
4426 Tab* strongTab = weakTab;
4427 if (!strongTab)
4428 return;
4429 if (![result isKindOfClass:[NSString class]])
4430 result = @"Not an HTML page";
4431 std::string base64HTML;
4432 base::Base64Encode(base::SysNSStringToUTF8(result), &base64HTML);
4433 GURL URL(std::string("data:text/plain;charset=utf-8;base64,") + base64HTML);
Sylvain Defresnee7f2c8a2017-10-17 02:39:194434 web::Referrer referrer(strongTab.webState->GetLastCommittedURL(),
edchinc5720722017-08-14 22:06:314435 web::ReferrerPolicyDefault);
4436
4437 [[weakSelf tabModel]
4438 insertTabWithURL:URL
4439 referrer:referrer
4440 transition:ui::PAGE_TRANSITION_LINK
4441 opener:strongTab
4442 openedByDOM:YES
4443 atIndex:TabModelConstants::kTabPositionAutomatically
4444 inBackground:NO];
4445 };
4446 [webController executeJavaScript:script
4447 completionHandler:completionHandlerBlock];
4448}
4449#endif // !defined(NDEBUG)
4450
edchin2134c042017-08-18 13:57:354451// TODO(crbug.com/634507) Remove base::TimeXXX::ToInternalValue().
4452- (void)showRateThisAppDialog {
4453 DCHECK(!_rateThisAppDialog);
4454
4455 // Store the current timestamp whenever this dialog is shown.
4456 _browserState->GetPrefs()->SetInt64(prefs::kRateThisAppDialogLastShownTime,
4457 base::Time::Now().ToInternalValue());
4458
Gregory Chatzinofff39ec5162017-10-05 20:28:534459 // iOS11 no longer supports the itms link to the app store. So, use a deep
4460 // link for iOS11 and the itms link for prior versions.
4461 NSURL* storeURL;
4462 if (base::ios::IsRunningOnIOS11OrLater()) {
4463 storeURL =
4464 [NSURL URLWithString:(@"https://itunes.apple.com/us/app/"
4465 @"google-chrome-the-fast-and-secure-web-browser/"
4466 @"id535886823?action=write-review")];
4467 } else {
4468 storeURL = [NSURL
4469 URLWithString:(@"itms-apps://itunes.apple.com/WebObjects/"
4470 @"MZStore.woa/wa/"
4471 @"viewContentsUserReviews?type=Purple+Software&id="
4472 @"535886823&pt=9008&ct=rating")];
4473 }
edchin2134c042017-08-18 13:57:354474
4475 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDialogShown"));
Elodie Banelaa5ab432017-09-28 14:42:014476 [self clearPresentedStateWithCompletion:nil dismissOmnibox:YES];
edchin2134c042017-08-18 13:57:354477
4478 _rateThisAppDialog = ios::GetChromeBrowserProvider()->CreateAppRatingPrompt();
4479 [_rateThisAppDialog setAppStoreURL:storeURL];
4480 [_rateThisAppDialog setDelegate:self];
4481 [_rateThisAppDialog show];
4482}
4483
Gregory Chatzinoff3f40c1542017-08-30 07:50:044484- (void)showFindInPage {
4485 if (!self.canShowFindBar)
4486 return;
4487
4488 if (!_findBarController) {
4489 _findBarController =
4490 [[FindBarControllerIOS alloc] initWithIncognito:_isOffTheRecord];
4491 _findBarController.dispatcher = self.dispatcher;
4492 }
4493
4494 Tab* tab = [_model currentTab];
4495 DCHECK(tab);
4496 auto* helper = FindTabHelper::FromWebState(tab.webState);
4497 DCHECK(!helper->IsFindUIActive());
4498 helper->SetFindUIActive(true);
4499 [self showFindBarWithAnimation:YES selectText:YES shouldFocus:YES];
4500}
4501
4502- (void)closeFindInPage {
4503 __weak BrowserViewController* weakSelf = self;
4504 Tab* currentTab = [_model currentTab];
4505 if (currentTab) {
4506 FindTabHelper::FromWebState(currentTab.webState)->StopFinding(^{
4507 [weakSelf updateFindBar:NO shouldFocus:NO];
4508 });
4509 }
4510}
4511
4512- (void)searchFindInPage {
4513 DCHECK([_model currentTab]);
4514 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4515 __weak BrowserViewController* weakSelf = self;
4516 helper->StartFinding(
4517 [_findBarController searchTerm], ^(FindInPageModel* model) {
4518 BrowserViewController* strongSelf = weakSelf;
4519 if (!strongSelf) {
4520 return;
4521 }
4522 [strongSelf->_findBarController updateResultsCount:model];
4523 });
4524
4525 if (!_isOffTheRecord)
4526 helper->PersistSearchTerm();
4527}
4528
4529- (void)findNextStringInPage {
4530 Tab* currentTab = [_model currentTab];
4531 DCHECK(currentTab);
4532 // TODO(crbug.com/603524): Reshow find bar if necessary.
4533 FindTabHelper::FromWebState(currentTab.webState)
4534 ->ContinueFinding(FindTabHelper::FORWARD, ^(FindInPageModel* model) {
4535 [_findBarController updateResultsCount:model];
4536 });
4537}
4538
4539- (void)findPreviousStringInPage {
4540 Tab* currentTab = [_model currentTab];
4541 DCHECK(currentTab);
4542 // TODO(crbug.com/603524): Reshow find bar if necessary.
4543 FindTabHelper::FromWebState(currentTab.webState)
4544 ->ContinueFinding(FindTabHelper::REVERSE, ^(FindInPageModel* model) {
4545 [_findBarController updateResultsCount:model];
4546 });
4547}
4548
edchinf84b2502017-08-31 21:30:454549- (void)showHelpPage {
4550 GURL helpUrl(l10n_util::GetStringUTF16(IDS_IOS_TOOLS_MENU_HELP_URL));
4551 [self webPageOrderedOpen:helpUrl
4552 referrer:web::Referrer()
4553 inBackground:NO
4554 appendTo:kCurrentTab];
4555}
4556
edchinb59b5602017-09-01 15:00:204557- (void)showBookmarksManager {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104558 if (!PresentNTPPanelModally()) {
edchinb59b5602017-09-01 15:00:204559 [self showAllBookmarks];
4560 } else {
4561 [self initializeBookmarkInteractionController];
4562 [_bookmarkInteractionController presentBookmarks];
4563 }
4564}
4565
edchin8ee0807d2017-09-01 23:52:474566- (void)showRecentTabs {
Gauthier Ambard5bb5f7a2017-09-06 12:58:104567 if (!PresentNTPPanelModally()) {
edchin8ee0807d2017-09-01 23:52:474568 [self showNTPPanel:ntp_home::RECENT_TABS_PANEL];
4569 } else {
4570 if (!self.recentTabsCoordinator) {
4571 self.recentTabsCoordinator = [[RecentTabsHandsetCoordinator alloc]
4572 initWithBaseViewController:self];
4573 self.recentTabsCoordinator.loader = self;
4574 self.recentTabsCoordinator.dispatcher = self.dispatcher;
4575 self.recentTabsCoordinator.browserState = _browserState;
4576 }
4577 [self.recentTabsCoordinator start];
4578 }
4579}
4580
Mark Cogan6de7e9a2017-09-06 12:57:214581- (void)requestDesktopSite {
4582 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::DESKTOP];
4583}
4584
4585- (void)requestMobileSite {
4586 [[_model currentTab] reloadWithUserAgentType:web::UserAgentType::MOBILE];
4587}
4588
sdefresnee65fd872016-12-19 13:38:134589#pragma mark - Command Handling
4590
sdefresnee65fd872016-12-19 13:38:134591- (void)closeCurrentTab {
4592 Tab* currentTab = [_model currentTab];
4593 NSUInteger tabIndex = [_model indexOfTab:currentTab];
4594 if (tabIndex == NSNotFound)
4595 return;
4596
jif7fed8122017-02-08 13:15:254597 // TODO(crbug.com/688003): Evaluate if a screenshot of the tab is needed on
4598 // iPad.
sdefresnee65fd872016-12-19 13:38:134599 UIImageView* exitingPage = [self pageOpenCloseAnimationView];
4600 exitingPage.image =
4601 [currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
4602
4603 // Close the actual tab, and add its image as a subview.
4604 [_model closeTabAtIndex:tabIndex];
4605
4606 // Do not animate close in iPad.
4607 if (!IsIPadIdiom()) {
4608 [_contentArea addSubview:exitingPage];
Sylvain Defresneed8c0db2017-08-31 16:29:524609 page_animation_util::AnimateOutWithCompletion(
sdefresnee65fd872016-12-19 13:38:134610 exitingPage, 0, YES, IsPortrait(), ^{
4611 [exitingPage removeFromSuperview];
4612 });
4613 }
4614}
4615
Elodie Banelaa5ab432017-09-28 14:42:014616- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
4617 dismissOmnibox:(BOOL)dismissOmnibox {
Rohit Rao01e0e002017-08-14 20:49:434618 [_activityServiceCoordinator cancelShare];
sdefresnee65fd872016-12-19 13:38:134619 [_bookmarkInteractionController dismissBookmarkModalControllerAnimated:NO];
4620 [_bookmarkInteractionController dismissSnackbar];
Elodie Banelaa5ab432017-09-28 14:42:014621 if (dismissOmnibox) {
sczsf1620e52017-10-02 22:54:464622 [_toolbarCoordinator cancelOmniboxEdit];
Elodie Banelaa5ab432017-09-28 14:42:014623 }
sdefresnee65fd872016-12-19 13:38:134624 [_dialogPresenter cancelAllDialogs];
Gregory Chatzinoffdf93d692017-09-09 01:32:274625 [self.dispatcher hidePageInfo];
Cooper Knaakd0a974cd2017-08-10 18:05:474626 [self.tabTipBubblePresenter dismissAnimated:NO];
sdefresnee65fd872016-12-19 13:38:134627 if (_voiceSearchController)
4628 _voiceSearchController->DismissMicPermissionsHelp();
rohitraob2bf3cb2017-02-10 14:10:364629
4630 Tab* currentTab = [_model currentTab];
4631 [currentTab dismissModals];
4632
rohitrao005a6432017-03-16 20:52:424633 if (currentTab) {
4634 auto* findHelper = FindTabHelper::FromWebState(currentTab.webState);
4635 if (findHelper) {
4636 findHelper->StopFinding(^{
4637 [self updateFindBar:NO shouldFocus:NO];
4638 });
4639 }
4640 }
rohitraob2bf3cb2017-02-10 14:10:364641
sdefresnee65fd872016-12-19 13:38:134642 [_paymentRequestManager cancelRequest];
sdefresnee65fd872016-12-19 13:38:134643 [_printController dismissAnimated:YES];
stkhapuginc9eee7b2017-04-10 15:49:274644 _printController = nil;
sczsf1620e52017-10-02 22:54:464645 [_toolbarCoordinator dismissToolsMenuPopup];
sdefresnee65fd872016-12-19 13:38:134646 [_contextMenuCoordinator stop];
4647 [self dismissRateThisAppDialog];
4648
4649 if (self.presentedViewController) {
4650 // Dismisses any other modal controllers that may be present, e.g. Recent
4651 // Tabs.
Rohit Raoa1f1bac2017-11-07 16:27:514652 //
sdefresnee65fd872016-12-19 13:38:134653 // Note that currently, some controllers like the bookmark ones were already
4654 // dismissed (in this example in -dismissBookmarkModalControllerAnimated:),
Rohit Raoa1f1bac2017-11-07 16:27:514655 // but are still reported as the presentedViewController. Calling
4656 // |dismissViewControllerAnimated:completion:| again would dismiss the BVC
4657 // itself, so instead check the value of |self.dismissingModal| and only
4658 // call dismiss if one of the above calls has not already triggered a
4659 // dismissal.
4660 //
4661 // To ensure the completion is called, nil is passed to the call to dismiss,
4662 // and the completion is called explicitly below.
4663 if (!TabSwitcherPresentsBVCEnabled() || !self.dismissingModal) {
4664 [self dismissViewControllerAnimated:NO completion:nil];
4665 }
sdefresnee65fd872016-12-19 13:38:134666 // Dismissed controllers will be so after a delay. Queue the completion
4667 // callback after that.
4668 if (completion) {
4669 dispatch_after(
4670 dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)),
4671 dispatch_get_main_queue(), ^{
4672 completion();
4673 });
4674 }
4675 } else if (completion) {
4676 // If no view controllers are presented, we should be ok with dispatching
4677 // the completion block directly.
4678 dispatch_async(dispatch_get_main_queue(), completion);
4679 }
4680}
4681
sdefresnee65fd872016-12-19 13:38:134682#pragma mark - Find Bar
4683
4684- (void)hideFindBarWithAnimation:(BOOL)animate {
4685 [_findBarController hideFindBarView:animate];
4686}
4687
4688- (void)showFindBarWithAnimation:(BOOL)animate
4689 selectText:(BOOL)selectText
4690 shouldFocus:(BOOL)shouldFocus {
4691 DCHECK(_findBarController);
4692 Tab* tab = [_model currentTab];
4693 DCHECK(tab);
4694 CRWWebController* webController = tab.webController;
4695
4696 CGRect referenceFrame = CGRectZero;
4697 if (IsIPadIdiom()) {
4698 referenceFrame = webController.visibleFrame;
4699 referenceFrame.origin.y -= kIPadFindBarOverlap;
4700 } else {
4701 referenceFrame = _contentArea.frame;
4702 }
4703
sczsf1620e52017-10-02 22:54:464704 CGRect omniboxFrame = [_toolbarCoordinator visibleOmniboxFrame];
sdefresnee65fd872016-12-19 13:38:134705 [_findBarController addFindBarView:animate
4706 intoView:self.view
4707 withFrame:referenceFrame
4708 alignWithFrame:omniboxFrame
4709 selectText:selectText];
4710 [self updateFindBar:YES shouldFocus:shouldFocus];
4711}
4712
sdefresnee65fd872016-12-19 13:38:134713- (void)updateFindBar:(BOOL)initialUpdate shouldFocus:(BOOL)shouldFocus {
stkhapugin098a1ea2017-06-20 14:47:324714 // TODO(crbug.com/731045): This early return temporarily replaces a DCHECK.
4715 // For unknown reasons, this DCHECK sometimes was hit in the wild, resulting
4716 // in a crash.
4717 if (![_model currentTab]) {
4718 return;
4719 }
rohitrao005a6432017-03-16 20:52:424720 auto* helper = FindTabHelper::FromWebState([_model currentTab].webState);
4721 if (helper && helper->IsFindUIActive()) {
sdefresnee65fd872016-12-19 13:38:134722 if (initialUpdate && !_isOffTheRecord) {
rohitrao005a6432017-03-16 20:52:424723 helper->RestoreSearchTerm();
sdefresnee65fd872016-12-19 13:38:134724 }
4725
4726 [self setFramesForHeaders:[self headerViews]
4727 atOffset:[self currentHeaderOffset]];
rohitrao005a6432017-03-16 20:52:424728 [_findBarController updateView:helper->GetFindResult()
sdefresnee65fd872016-12-19 13:38:134729 initialUpdate:initialUpdate
4730 focusTextfield:shouldFocus];
4731 } else {
4732 [self hideFindBarWithAnimation:YES];
4733 }
4734}
4735
4736- (void)showAllBookmarks {
4737 DCHECK(self.visible || self.dismissingModal);
4738 GURL URL(kChromeUIBookmarksURL);
4739 Tab* tab = [_model currentTab];
4740 web::NavigationManager::WebLoadParams params(URL);
4741 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234742 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134743}
4744
Gauthier Ambardf520c022017-08-29 07:42:234745- (void)showNTPPanel:(ntp_home::PanelIdentifier)panel {
sdefresnee65fd872016-12-19 13:38:134746 DCHECK(self.visible || self.dismissingModal);
4747 GURL url(kChromeUINewTabURL);
4748 std::string fragment(NewTabPage::FragmentFromIdentifier(panel));
4749 if (fragment != "") {
4750 GURL::Replacements replacement;
4751 replacement.SetRefStr(fragment);
4752 url = url.ReplaceComponents(replacement);
4753 }
4754 Tab* tab = [_model currentTab];
4755 web::NavigationManager::WebLoadParams params(url);
4756 params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
sdefresne7d699dd2017-04-05 13:05:234757 [tab navigationManager]->LoadURLWithParams(params);
sdefresnee65fd872016-12-19 13:38:134758}
4759
sdefresnee65fd872016-12-19 13:38:134760- (void)dismissRateThisAppDialog {
stkhapuginc9eee7b2017-04-10 15:49:274761 if (_rateThisAppDialog) {
sdefresnee65fd872016-12-19 13:38:134762 base::RecordAction(base::UserMetricsAction(
4763 "IOSRateThisAppDialogDismissedProgramatically"));
4764 [_rateThisAppDialog dismiss];
stkhapuginc9eee7b2017-04-10 15:49:274765 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:134766 }
4767}
4768
Jean-François Geyelin5d2e184c2017-07-28 19:48:004769- (void)startVoiceSearchWithOriginView:(UIView*)originView {
4770 _voiceSearchButton = originView;
sdefresnee65fd872016-12-19 13:38:134771 // Delay Voice Search until new tab animations have finished.
kkhorimotoa44349c12017-04-12 23:02:124772 if (self.inNewTabAnimation) {
sdefresnee65fd872016-12-19 13:38:134773 _startVoiceSearchAfterNewTabAnimation = YES;
4774 return;
4775 }
4776
4777 // Keyboard shouldn't overlay the ecoutez window, so dismiss find in page and
4778 // dismiss the keyboard.
4779 [self closeFindInPage];
4780 [[_model currentTab].webController dismissKeyboard];
4781
4782 // Ensure that voice search objects are created.
4783 [self ensureVoiceSearchControllerCreated];
4784 [self ensureVoiceSearchBarCreated];
4785
4786 // Present voice search.
4787 [_voiceSearchBar prepareToPresentVoiceSearch];
4788 _voiceSearchController->StartRecognition(self, [_model currentTab]);
sczsf1620e52017-10-02 22:54:464789 [_toolbarCoordinator cancelOmniboxEdit];
sdefresnee65fd872016-12-19 13:38:134790}
4791
4792#pragma mark - ToolbarOwner
4793
Gauthier Ambard04ddb512017-11-07 09:14:164794- (CGRect)toolbarFrame {
sczs42f7f7482017-11-08 01:13:274795 return _toolbarCoordinator.toolbarViewController.view.frame;
Gauthier Ambard04ddb512017-11-07 09:14:164796}
4797
Gauthier Ambard996d9b12017-11-06 09:39:214798- (id<ToolbarSnapshotProviding>)toolbarSnapshotProvider {
4799 id<ToolbarSnapshotProviding> toolbarSnapshotProvider = nil;
sczs42f7f7482017-11-08 01:13:274800 if (_toolbarCoordinator.toolbarViewController.view.hidden) {
Gauthier Ambard996d9b12017-11-06 09:39:214801 Tab* currentTab = [_model currentTab];
4802 if (currentTab.webState &&
4803 UrlHasChromeScheme(currentTab.webState->GetLastCommittedURL())) {
4804 // Use the native content controller's toolbar when the BVC's is hidden.
4805 id nativeController = [self nativeControllerForTab:currentTab];
4806 if ([nativeController conformsToProtocol:@protocol(ToolbarOwner)]) {
4807 toolbarSnapshotProvider = [nativeController toolbarSnapshotProvider];
4808 }
4809 }
4810 } else {
4811 toolbarSnapshotProvider = _toolbarCoordinator;
4812 }
4813 return toolbarSnapshotProvider;
4814}
4815
sdefresnee65fd872016-12-19 13:38:134816#pragma mark - TabModelObserver methods
4817
4818// Observer method, tab inserted.
4819- (void)tabModel:(TabModel*)model
4820 didInsertTab:(Tab*)tab
4821 atIndex:(NSUInteger)modelIndex
4822 inForeground:(BOOL)fg {
4823 DCHECK(tab);
4824 [self installDelegatesForTab:tab];
4825
4826 if (fg) {
Mohamad Ahmadi7d09ec32017-07-11 22:32:194827 [_paymentRequestManager setActiveWebState:tab.webState];
sdefresnee65fd872016-12-19 13:38:134828 }
4829}
4830
4831// Observer method, active tab changed.
4832- (void)tabModel:(TabModel*)model
4833 didChangeActiveTab:(Tab*)newTab
4834 previousTab:(Tab*)previousTab
4835 atIndex:(NSUInteger)index {
4836 // TODO(rohitrao): tabSelected expects to always be called with a non-nil tab.
4837 // Currently this observer method is always called with a non-nil |newTab|,
4838 // but that may change in the future. Remove this DCHECK when it does.
4839 DCHECK(newTab);
stkhapuginc9eee7b2017-04-10 15:49:274840 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304841 DCHECK(newTab.webState);
4842 infobars::InfoBarManager* infoBarManager =
4843 InfoBarManagerImpl::FromWebState(newTab.webState);
sdefresnee65fd872016-12-19 13:38:134844 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4845 }
4846 [self updateVoiceSearchBarVisibilityAnimated:NO];
4847
Mohamad Ahmadi7d09ec32017-07-11 22:32:194848 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresnee65fd872016-12-19 13:38:134849
4850 [self tabSelected:newTab];
sdefresnee65fd872016-12-19 13:38:134851}
4852
4853// Observer method, tab changed.
4854- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
4855 DCHECK(tab && ([_model indexOfTab:tab] != NSNotFound));
4856 if (tab == [_model currentTab]) {
4857 [self updateToolbar];
sdefresnee65fd872016-12-19 13:38:134858 }
4859}
4860
sdefresne49cf2862017-03-15 13:46:144861// Observer method, tab replaced.
4862- (void)tabModel:(TabModel*)model
4863 didReplaceTab:(Tab*)oldTab
4864 withTab:(Tab*)newTab
4865 atIndex:(NSUInteger)index {
4866 [self uninstallDelegatesForTab:oldTab];
4867 [self installDelegatesForTab:newTab];
kkhorimotofa0844cc2017-03-20 17:01:264868
michaeldo79909fb2017-05-09 23:42:504869 if (_infoBarContainer) {
Rohit Raoaf46af92017-08-10 12:52:304870 infobars::InfoBarManager* infoBarManager = nullptr;
4871 if (newTab) {
4872 DCHECK(newTab.webState);
4873 infoBarManager = InfoBarManagerImpl::FromWebState(newTab.webState);
4874 }
michaeldo79909fb2017-05-09 23:42:504875 _infoBarContainer->ChangeInfoBarManager(infoBarManager);
4876 }
4877
kkhorimotofa0844cc2017-03-20 17:01:264878 // Add |newTab|'s view to the hierarchy if it's the current Tab.
4879 if (self.active && model.currentTab == newTab)
4880 [self displayTab:newTab isNewSelection:NO];
Mohamad Ahmadibec07eb2017-09-12 19:38:464881
4882 if (newTab)
4883 [_paymentRequestManager setActiveWebState:newTab.webState];
sdefresne49cf2862017-03-15 13:46:144884}
4885
sdefresnee65fd872016-12-19 13:38:134886// A tab has been removed, remove its views from display if necessary.
4887- (void)tabModel:(TabModel*)model
4888 didRemoveTab:(Tab*)tab
4889 atIndex:(NSUInteger)index {
sdefresne49cf2862017-03-15 13:46:144890 [self uninstallDelegatesForTab:tab];
4891
kkhorimoto496fdd72017-06-12 19:56:314892 // Cancel dialogs for |tab|'s WebState.
4893 [self.dialogPresenter cancelDialogForWebState:tab.webState];
4894
sdefresnee65fd872016-12-19 13:38:134895 // Ignore changes while the tab stack view is visible (or while suspended).
4896 // The display will be refreshed when this view becomes active again.
4897 if (!self.visible || !model.webUsageEnabled)
4898 return;
4899
4900 // Remove the find bar for now.
4901 [self hideFindBarWithAnimation:NO];
4902}
4903
4904- (void)tabModel:(TabModel*)model willRemoveTab:(Tab*)tab {
4905 if (tab == [model currentTab]) {
4906 [_contentArea displayContentView:nil];
sczsf1620e52017-10-02 22:54:464907 [_toolbarCoordinator selectedTabChanged];
sdefresnee65fd872016-12-19 13:38:134908 }
4909
Mohamad Ahmadi7d09ec32017-07-11 22:32:194910 [_paymentRequestManager stopTrackingWebState:tab.webState];
4911
sdefresnee65fd872016-12-19 13:38:134912 [[UpgradeCenter sharedInstance] tabWillClose:tab.tabId];
4913 if ([model count] == 1) { // About to remove the last tab.
Mohamad Ahmadi7d09ec32017-07-11 22:32:194914 [_paymentRequestManager setActiveWebState:nullptr];
sdefresnee65fd872016-12-19 13:38:134915 }
4916}
4917
4918// Called when the number of tabs changes. Update the toolbar accordingly.
4919- (void)tabModelDidChangeTabCount:(TabModel*)model {
4920 DCHECK(model == _model);
sczsf1620e52017-10-02 22:54:464921 [_toolbarCoordinator setTabCount:[_model count]];
sdefresnee65fd872016-12-19 13:38:134922}
4923
4924#pragma mark - Upgrade Detection
4925
4926- (void)showUpgrade:(UpgradeCenter*)center {
4927 // Add an infobar on all the open tabs.
stkhapuginc9eee7b2017-04-10 15:49:274928 for (Tab* tab in _model) {
sdefresnee65fd872016-12-19 13:38:134929 NSString* tabId = tab.tabId;
Rohit Raoaf46af92017-08-10 12:52:304930 DCHECK(tab.webState);
4931 infobars::InfoBarManager* infoBarManager =
4932 InfoBarManagerImpl::FromWebState(tab.webState);
4933 DCHECK(infoBarManager);
4934 [center addInfoBarToManager:infoBarManager forTabId:tabId];
sdefresnee65fd872016-12-19 13:38:134935 }
4936}
4937
sdefresnee65fd872016-12-19 13:38:134938
4939#pragma mark - InfoBarControllerDelegate
4940
4941- (void)infoBarContainerStateChanged:(bool)isAnimating {
4942 InfoBarContainerView* infoBarContainerView = _infoBarContainer->view();
4943 DCHECK(infoBarContainerView);
4944 CGRect containerFrame = infoBarContainerView.frame;
4945 CGFloat height = [infoBarContainerView topmostVisibleInfoBarHeight];
4946 containerFrame.origin.y = CGRectGetMaxY(_contentArea.frame) - height;
4947 containerFrame.size.height = height;
4948 BOOL isViewVisible = self.visible;
4949 [UIView animateWithDuration:0.1
4950 animations:^{
4951 [infoBarContainerView setFrame:containerFrame];
4952 }
4953 completion:^(BOOL finished) {
4954 if (!isViewVisible)
4955 return;
4956 UIAccessibilityPostNotification(
4957 UIAccessibilityLayoutChangedNotification, infoBarContainerView);
4958 }];
4959}
4960
4961- (BOOL)shouldAutorotate {
4962 if (_voiceSearchController && _voiceSearchController->IsVisible()) {
4963 // Don't rotate if a voice search is being presented or dismissed. Once the
4964 // transition animations finish, only the Voice Search UIViewController's
4965 // |-shouldAutorotate| will be called.
4966 return NO;
4967 } else if (_sideSwipeController && ![_sideSwipeController shouldAutorotate]) {
4968 // Don't auto rotate if side swipe controller view says not to.
4969 return NO;
4970 } else {
4971 return [super shouldAutorotate];
4972 }
4973}
4974
4975// Always return yes, as this tap should work with various recognizers,
4976// including UITextTapRecognizer, UILongPressGestureRecognizer,
4977// UIScrollViewPanGestureRecognizer and others.
4978- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
4979 shouldRecognizeSimultaneouslyWithGestureRecognizer:
4980 (UIGestureRecognizer*)otherGestureRecognizer {
4981 return YES;
4982}
4983
4984// Tap gestures should only be recognized within |_contentArea|.
4985- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gesture {
4986 CGPoint location = [gesture locationInView:self.view];
4987
4988 // Only allow touches on descendant views of |_contentArea|.
4989 UIView* hitView = [self.view hitTest:location withEvent:nil];
4990 return (![hitView isDescendantOfView:_contentArea]) ? NO : YES;
4991}
4992
4993#pragma mark - SideSwipeController Delegate Methods
4994
4995- (void)sideSwipeViewDismissAnimationDidEnd:(UIView*)sideSwipeView {
4996 DCHECK(!IsIPadIdiom());
4997 // Update frame incase orientation changed while |_contentArea| was out of
4998 // the view hierarchy.
4999 [_contentArea setFrame:[sideSwipeView frame]];
5000
Justin Cohen16ad60e2017-11-10 14:56:265001 [self.view insertSubview:_contentArea aboveSubview:_fakeStatusBarView];
sdefresnee65fd872016-12-19 13:38:135002 [self updateVoiceSearchBarVisibilityAnimated:NO];
5003 [self updateToolbar];
5004
5005 // Reset horizontal stack view.
5006 [sideSwipeView removeFromSuperview];
5007 [_sideSwipeController setInSwipe:NO];
5008 [_infoBarContainer->view() setHidden:NO];
5009}
5010
5011- (UIView*)contentView {
5012 return _contentArea;
5013}
5014
sdefresnee65fd872016-12-19 13:38:135015- (BOOL)preventSideSwipe {
sczsf1620e52017-10-02 22:54:465016 if ([_toolbarCoordinator toolsPopupController])
sdefresnee65fd872016-12-19 13:38:135017 return YES;
5018
5019 if (_voiceSearchController && _voiceSearchController->IsVisible())
5020 return YES;
5021
sdefresnee65fd872016-12-19 13:38:135022 if (!self.active)
5023 return YES;
5024
5025 return NO;
5026}
5027
5028- (void)updateAccessoryViewsForSideSwipeWithVisibility:(BOOL)visible {
5029 if (visible) {
5030 [self updateVoiceSearchBarVisibilityAnimated:NO];
5031 [self updateToolbar];
5032 [_infoBarContainer->view() setHidden:NO];
5033 } else {
5034 // Hide UI accessories such as find bar and first visit overlays
5035 // for welcome page.
5036 [self hideFindBarWithAnimation:NO];
5037 [_infoBarContainer->view() setHidden:YES];
5038 [_voiceSearchBar setHidden:YES];
5039 }
5040}
5041
5042- (BOOL)verifyToolbarViewPlacementInView:(UIView*)views {
5043 BOOL seenToolbar = NO;
5044 BOOL seenInfoBarContainer = NO;
5045 BOOL seenContentArea = NO;
5046 for (UIView* view in views.subviews) {
sczs42f7f7482017-11-08 01:13:275047 if (view == _toolbarCoordinator.toolbarViewController.view)
sdefresnee65fd872016-12-19 13:38:135048 seenToolbar = YES;
5049 else if (view == _infoBarContainer->view())
5050 seenInfoBarContainer = YES;
5051 else if (view == _contentArea)
5052 seenContentArea = YES;
5053 if ((seenToolbar && !seenInfoBarContainer) ||
5054 (seenInfoBarContainer && !seenContentArea))
5055 return NO;
5056 }
5057 return YES;
5058}
5059
5060#pragma mark - PreloadControllerDelegate methods
5061
rohitraoeeb5293b2017-06-15 14:40:025062- (BOOL)preloadShouldUseDesktopUserAgent {
liaoyukeb8453e12017-02-24 22:08:445063 return [_model currentTab].usesDesktopUserAgent;
sdefresnee65fd872016-12-19 13:38:135064}
5065
rohitraoeeb5293b2017-06-15 14:40:025066- (BOOL)preloadHasNativeControllerForURL:(const GURL&)url {
5067 return [self hasControllerForURL:url];
5068}
5069
sdefresnee65fd872016-12-19 13:38:135070#pragma mark - BookmarkBridgeMethods
5071
5072// If an added or removed bookmark is the same as the current url, update the
5073// toolbar so the star highlight is kept in sync.
5074- (void)bookmarkNodeModified:(const BookmarkNode*)node {
Sylvain Defresnee7f2c8a2017-10-17 02:39:195075 if ([_model currentTab].webState &&
5076 node->url() == [_model currentTab].webState->GetLastCommittedURL()) {
sdefresnee65fd872016-12-19 13:38:135077 [self updateToolbar];
kkhorimotob110b262017-06-01 18:38:255078 }
sdefresnee65fd872016-12-19 13:38:135079}
5080
5081// If all bookmarks are removed, update the toolbar so the star highlight is
5082// kept in sync.
5083- (void)allBookmarksRemoved {
5084 [self updateToolbar];
5085}
5086
sdefresnee65fd872016-12-19 13:38:135087- (void)showErrorAlertWithStringTitle:(NSString*)title
5088 message:(NSString*)message {
5089 // Dismiss current alert.
5090 [_alertCoordinator stop];
5091
stkhapuginc9eee7b2017-04-10 15:49:275092 _alertCoordinator = [_dependencyFactory alertCoordinatorWithTitle:title
5093 message:message
5094 viewController:self];
sdefresnee65fd872016-12-19 13:38:135095 [_alertCoordinator start];
5096}
5097
edchineeb4d422017-10-02 17:39:365098- (void)showSnackbar:(NSString*)text {
5099 MDCSnackbarMessage* message = [MDCSnackbarMessage messageWithText:text];
5100 message.accessibilityLabel = text;
5101 message.duration = 2.0;
5102 message.category = kBrowserViewControllerSnackbarCategory;
5103 [self.dispatcher showSnackbarMessage:message];
5104}
5105
sdefresnee65fd872016-12-19 13:38:135106#pragma mark - Show Mail Composer methods
5107
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575108- (void)netExportTabHelper:(NetExportTabHelper*)tabHelper
5109 showMailComposerWithContext:(ShowMailComposerContext*)context {
sdefresnee65fd872016-12-19 13:38:135110 if (![MFMailComposeViewController canSendMail]) {
5111 NSString* alertTitle =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575112 l10n_util::GetNSString([context emailNotConfiguredAlertTitleId]);
sdefresnee65fd872016-12-19 13:38:135113 NSString* alertMessage =
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575114 l10n_util::GetNSString([context emailNotConfiguredAlertMessageId]);
sdefresnee65fd872016-12-19 13:38:135115 [self showErrorAlertWithStringTitle:alertTitle message:alertMessage];
5116 return;
5117 }
stkhapuginc9eee7b2017-04-10 15:49:275118 MFMailComposeViewController* mailViewController =
5119 [[MFMailComposeViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135120 [mailViewController setModalPresentationStyle:UIModalPresentationFormSheet];
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575121 [mailViewController setToRecipients:[context toRecipients]];
5122 [mailViewController setSubject:[context subject]];
5123 [mailViewController setMessageBody:[context body] isHTML:NO];
sdefresnee65fd872016-12-19 13:38:135124
Gregory Chatzinoff5f9f7f02017-09-19 02:04:575125 const base::FilePath& textFile = [context textFileToAttach];
sdefresnee65fd872016-12-19 13:38:135126 if (!textFile.empty()) {
5127 NSString* filename = base::SysUTF8ToNSString(textFile.value());
5128 NSData* data = [NSData dataWithContentsOfFile:filename];
5129 if (data) {
5130 NSString* displayName =
5131 base::SysUTF8ToNSString(textFile.BaseName().value());
5132 [mailViewController addAttachmentData:data
5133 mimeType:@"text/plain"
5134 fileName:displayName];
5135 }
5136 }
5137
5138 [mailViewController setMailComposeDelegate:self];
5139 [self presentViewController:mailViewController animated:YES completion:nil];
5140}
5141
5142#pragma mark - MFMailComposeViewControllerDelegate methods
5143
5144- (void)mailComposeController:(MFMailComposeViewController*)controller
5145 didFinishWithResult:(MFMailComposeResult)result
5146 error:(NSError*)error {
5147 [self dismissViewControllerAnimated:YES completion:nil];
5148}
5149
5150#pragma mark - StoreKitLauncher methods
5151
5152- (void)productViewControllerDidFinish:
5153 (SKStoreProductViewController*)viewController {
5154 [self dismissViewControllerAnimated:YES completion:nil];
5155}
5156
5157- (void)openAppStore:(NSString*)appId {
5158 if (![appId length])
5159 return;
5160 NSDictionary* product =
5161 @{SKStoreProductParameterITunesItemIdentifier : appId};
stkhapuginc9eee7b2017-04-10 15:49:275162 SKStoreProductViewController* storeViewController =
5163 [[SKStoreProductViewController alloc] init];
sdefresnee65fd872016-12-19 13:38:135164 [storeViewController setDelegate:self];
5165 [storeViewController loadProductWithParameters:product completionBlock:nil];
5166 [self presentViewController:storeViewController animated:YES completion:nil];
5167}
5168
5169#pragma mark - TabDialogDelegate methods
5170
sdefresnee65fd872016-12-19 13:38:135171- (void)cancelDialogForTab:(Tab*)tab {
5172 [self.dialogPresenter cancelDialogForWebState:tab.webState];
5173}
5174
5175#pragma mark - FKFeedbackPromptDelegate methods
5176
5177- (void)userTappedRateApp:(UIView*)view {
5178 base::RecordAction(base::UserMetricsAction("IOSRateThisAppRateChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275179 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135180}
5181
5182- (void)userTappedSendFeedback:(UIView*)view {
5183 base::RecordAction(base::UserMetricsAction("IOSRateThisAppFeedbackChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275184 _rateThisAppDialog = nil;
edchin9eaf25f52017-10-26 02:42:205185 [self.dispatcher showReportAnIssueFromViewController:self];
sdefresnee65fd872016-12-19 13:38:135186}
5187
5188- (void)userTappedDismiss:(UIView*)view {
5189 base::RecordAction(base::UserMetricsAction("IOSRateThisAppDismissChosen"));
stkhapuginc9eee7b2017-04-10 15:49:275190 _rateThisAppDialog = nil;
sdefresnee65fd872016-12-19 13:38:135191}
5192
5193#pragma mark - VoiceSearchBarDelegate
5194
5195- (BOOL)isTTSEnabledForVoiceSearchBar:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275196 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135197 [self ensureVoiceSearchControllerCreated];
5198 return _voiceSearchController->IsTextToSpeechEnabled() &&
5199 _voiceSearchController->IsTextToSpeechSupported();
5200}
5201
5202- (void)voiceSearchBarDidUpdateButtonState:(id<VoiceSearchBar>)voiceSearchBar {
stkhapuginc9eee7b2017-04-10 15:49:275203 DCHECK_EQ(_voiceSearchBar, voiceSearchBar);
sdefresnee65fd872016-12-19 13:38:135204 [self.tabModel.currentTab updateSnapshotWithOverlay:YES visibleFrameOnly:YES];
5205}
5206
5207#pragma mark - VoiceSearchPresenter
5208
5209- (UIView*)voiceSearchButton {
5210 return _voiceSearchButton;
5211}
5212
5213- (id<LogoAnimationControllerOwner>)logoAnimationControllerOwner {
5214 return [self currentLogoAnimationControllerOwner];
5215}
5216
Rohit Rao01e0e002017-08-14 20:49:435217#pragma mark - ActivityService Providers
5218
5219- (void)presentActivityServiceViewController:(UIViewController*)controller {
5220 [self presentViewController:controller animated:YES completion:nil];
5221}
5222
5223- (void)activityServiceDidEndPresenting {
5224 self.presenting = NO;
5225 [self.dialogPresenter tryToPresent];
5226}
5227
Rohit Raocda0a992017-08-16 15:37:115228#pragma mark - QRScanner Requirements
5229
5230- (void)presentQRScannerViewController:(UIViewController*)controller {
5231 [self presentViewController:controller animated:YES completion:nil];
5232}
5233
5234- (void)dismissQRScannerViewController:(UIViewController*)controller
5235 completion:(void (^)(void))completion {
5236 DCHECK_EQ(controller, self.presentedViewController);
5237 [self dismissViewControllerAnimated:YES completion:completion];
5238}
5239
sczsdd860eba2017-08-10 01:55:385240#pragma mark - TabHistoryPresenter
5241
sczs0a726d22017-08-21 22:40:135242- (UIView*)viewForTabHistoryPresentation {
5243 return self.view;
5244}
5245
sczsdd860eba2017-08-10 01:55:385246- (void)prepareForTabHistoryPresentation {
5247 DCHECK(self.visible || self.dismissingModal);
5248 [[self.tabModel currentTab].webController dismissKeyboard];
sczsf1620e52017-10-02 22:54:465249 [_toolbarCoordinator cancelOmniboxEdit];
sczsdd860eba2017-08-10 01:55:385250}
5251
Mike Doughertya1ec26402017-08-23 19:46:315252#pragma mark - CaptivePortalDetectorTabHelperDelegate
5253
Mike Dougherty4620cf8e2017-10-31 23:37:095254- (void)captivePortalDetectorTabHelper:
5255 (CaptivePortalDetectorTabHelper*)tabHelper
5256 connectWithLandingURL:(const GURL&)landingURL {
Mike Dougherty66e58812017-11-03 06:54:285257 [self addSelectedTabWithURL:landingURL transition:ui::PAGE_TRANSITION_TYPED];
Mike Doughertya1ec26402017-08-23 19:46:315258}
5259
Gregory Chatzinoffdf93d692017-09-09 01:32:275260#pragma mark - PageInfoPresentation
5261
Gregory Chatzinoffb6a01f72017-09-20 20:06:395262- (void)presentPageInfoView:(UIView*)pageInfoView {
5263 [pageInfoView setFrame:self.view.bounds];
5264 [self.view addSubview:pageInfoView];
Gregory Chatzinoffdf93d692017-09-09 01:32:275265}
5266
5267- (void)prepareForPageInfoPresentation {
5268 // Dismiss the omnibox (if open).
sczsf1620e52017-10-02 22:54:465269 [_toolbarCoordinator cancelOmniboxEdit];
Gregory Chatzinoffdf93d692017-09-09 01:32:275270}
5271
Gregory Chatzinoffb6a01f72017-09-20 20:06:395272- (CGPoint)convertToPresentationCoordinatesForOrigin:(CGPoint)origin {
5273 return [self.view convertPoint:origin fromView:nil];
5274}
5275
Sylvain Defresnecacc3a52017-09-12 13:51:045276#pragma mark - WebStatePrinter
5277
5278- (void)printWebState:(web::WebState*)webState {
5279 if (webState == [_model currentTab].webState)
5280 [self printTab];
5281}
5282
Eugene But35ded552017-09-13 23:31:595283#pragma mark - RepostFormTabHelperDelegate
5284
5285- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
Sylvain Defresnee3c698122017-11-17 11:16:325286 presentRepostFormDialogForWebState:(web::WebState*)webState
5287 dialogAtPoint:(CGPoint)location
5288 completionHandler:(void (^)(BOOL))completion {
5289 _repostFormCoordinator =
5290 [[RepostFormCoordinator alloc] initWithBaseViewController:self
5291 dialogLocation:location
5292 webState:webState
5293 completionHandler:completion];
Eugene But35ded552017-09-13 23:31:595294 [_repostFormCoordinator start];
5295}
5296
5297- (void)repostFormTabHelperDismissRepostFormDialog:
5298 (RepostFormTabHelper*)helper {
5299 _repostFormCoordinator = nil;
5300}
5301
edchinf5150c682017-09-18 02:50:035302#pragma mark - TabStripPresentation
5303
5304- (BOOL)isTabStripFullyVisible {
5305 return ([self currentHeaderOffset] == 0.0f);
5306}
5307
5308- (void)showTabStripView:(UIView*)tabStripView {
5309 DCHECK([self isViewLoaded]);
5310 DCHECK(tabStripView);
5311 self.tabStripView = tabStripView;
5312 CGRect tabStripFrame = [self.tabStripView frame];
5313 tabStripFrame.origin = CGPointZero;
5314 // TODO(crbug.com/256655): Move the origin.y below to -setUpViewLayout.
5315 // because the CGPointZero above will break reset the offset, but it's not
5316 // clear what removing that will do.
5317 tabStripFrame.origin.y = [self headerOffset];
5318 tabStripFrame.size.width = CGRectGetWidth([self view].bounds);
5319 [self.tabStripView setFrame:tabStripFrame];
5320 [[self view] addSubview:tabStripView];
5321}
5322
edchincd32fdf2017-10-25 12:45:455323#pragma mark - ManageAccountsDelegate
5324
5325- (void)onManageAccounts {
5326 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5327 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5328 ->GetState());
edchin5b8aa052017-10-30 23:27:285329 [self.dispatcher showAccountsSettingsFromViewController:self];
edchincd32fdf2017-10-25 12:45:455330}
5331
5332- (void)onAddAccount {
5333 signin_metrics::LogAccountReconcilorStateOnGaiaResponse(
5334 ios::AccountReconcilorFactory::GetForBrowserState(self.browserState)
5335 ->GetState());
edchinb082b2982017-11-03 19:41:215336 [self.dispatcher showAddAccountFromViewController:self];
edchincd32fdf2017-10-25 12:45:455337}
5338
5339- (void)onGoIncognito:(const GURL&)url {
5340 // The user taps on go incognito from the mobile U-turn webpage (the web page
5341 // that displays all users accounts available in the content area). As the
5342 // user chooses to go to incognito, the mobile U-turn page is no longer
5343 // neeeded. The current solution is to go back in history. This has the
5344 // advantage of keeping the current browsing session and give a good user
5345 // experience when the user comes back from incognito.
5346 [self.tabModel.currentTab goBack];
5347
5348 if (url.is_valid()) {
5349 OpenUrlCommand* command = [[OpenUrlCommand alloc]
5350 initWithURL:url
5351 referrer:web::Referrer() // Strip referrer when switching modes.
5352 inIncognito:YES
5353 inBackground:NO
5354 appendTo:kLastTab];
5355 [self.dispatcher openURL:command];
5356 } else {
5357 [self.dispatcher openNewTab:[OpenNewTabCommand command]];
5358 }
5359}
5360
edchin95c927072017-11-04 00:35:075361#pragma mark - SyncPresenter
5362
5363- (void)showReauthenticateSignin {
5364 [self.dispatcher
edchin3b46e8d2017-11-07 22:48:125365 showSignin:
5366 [[ShowSigninCommand alloc]
5367 initWithOperation:AUTHENTICATION_OPERATION_REAUTHENTICATE
5368 accessPoint:signin_metrics::AccessPoint::
5369 ACCESS_POINT_UNKNOWN]
5370 baseViewController:self];
edchin95c927072017-11-04 00:35:075371}
5372
5373- (void)showSyncSettings {
edchina14d7182017-11-06 18:37:505374 [self.dispatcher showSyncSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075375}
5376
5377- (void)showSyncPassphraseSettings {
edchinec723062017-11-06 20:03:545378 [self.dispatcher showSyncPassphraseSettingsFromViewController:self];
edchin95c927072017-11-04 00:35:075379}
5380
edchin9e7a1112017-11-07 18:28:035381#pragma mark - SigninPresenter
5382
5383- (void)showSignin:(ShowSigninCommand*)command {
edchin3b46e8d2017-11-07 22:48:125384 [self.dispatcher showSignin:command baseViewController:self];
edchin9e7a1112017-11-07 18:28:035385}
5386
sdefresnee65fd872016-12-19 13:38:135387@end